0

I'm trying to get a DOM element that is horizontally scrollable and set it's scroll position. However the property doesn't seem to update it's value even if I output it's value in a console.log() immediately after the assignment.

<script setup>
import {ref, watch} from 'vue';

const container = ref();

watch(container, () => {
  console.log(container.value.scrollWidth); // Output: 1500
  console.log(container.value.scrollLeft); // Output: 0
  container.value.scrollLeft = 33;
  console.log(container.value.scrollLeft); // ❌ Output: 0, Expected: 33
})
</script>

<template>
  <div ref="container"></div>
</template>

FYI, I'm using watch() instead of onMounted() because I don't know what the scroll position should be until after I make a fetch() call in the parent component. The full version of the <script> section is given below, however the above version can be thrown in a vue playground and will run showing the issue.

I've googled this nine ways to Sunday. I can find articles about Vue 2, Vue 3 Options API, or Vue 3 Composition. I am new to Vue and have only ever used Vue 3 Composition API. Either way, all the articles or stack overflow questions seem to be about accessing (reading) DOM elements using template refs, but they don't show how to assign a value to a DOM element property. The above code get's me as far as reading the properties, but when I try to assign a new value to a property that property doesn't get updated.

<script setup lang="ts">
import { ref, watch } from "vue";
import type { ProbeRequest } from "@/types/classes/ProbeRequest";
import type { ProbeRequestStatus } from "@/types/classes/ProbeRequestStatus";
import { buildTracker, getScrollPosition } from "./utils";

interface Props {
  probeRequest: null | ProbeRequest;
}

const props = withDefaults(defineProps<Props>(), { probeRequest: null });
const tracker = ref<(ProbeRequestStatus | string)[]>([]);
const trackerContainer = ref();

watch(
  () => props.probeRequest,
  (probeRequest) => {
    tracker.value = probeRequest ? buildTracker(probeRequest) : [];
    if (tracker.value.length > 0) {
      console.log(trackerContainer.value.scrollWidth);
      console.log(trackerContainer.value.scrollLeft);
      const scrollLeft = getScrollPosition(
        tracker.value,
        trackerContainer.value.scrollWidth
      );
      trackerContainer.value.scrollLeft = scrollLeft;
      console.log(trackerContainer.value.scrollLeft);
    }
  }
);
</script>
2
  • The documentation for watch may give you a clue, i'm surprised that's not one of the things you mentioned googling for Commented Feb 22, 2023 at 1:24
  • I did read the documentation. Sorry I didn't call that out explicitly. If there is a clue in there I'm not seeing it. As far as I can tell I'm doing everything I'm supposed to. Commented Feb 22, 2023 at 1:32

2 Answers 2

2

So the problem was that on the first render, the container was not being overflowed because there was no content (I'm waiting on tracker.value to be an array with multiple elements; and that is waiting on props.probeRequest to have a value). That means that scrollLeft had to be 0. (It's worth noting that is also why my contrived example didn't work because it was never scrollable)

Once the container had content I then needed to wait for it to be rendered before I could assign a value to scrollLeft. So I had to add an await nextTick() (didn't know that was a thing).

Code now looks like this:

<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
import type { ProbeRequest } from "@/types/classes/ProbeRequest";
import type { ProbeRequestStatus } from "@/types/classes/ProbeRequestStatus";
import { buildTracker, getScrollPosition } from "./utils";

interface Props {
  probeRequest: null | ProbeRequest;
}

const props = withDefaults(defineProps<Props>(), { probeRequest: null });
const tracker = ref<(ProbeRequestStatus | string)[]>([]);
const trackerContainer = ref<HTMLInputElement | null>(null);

watch(
  () => props.probeRequest,
  async (probeRequest) => {
    tracker.value = probeRequest ? buildTracker(probeRequest) : [];

    await nextTick() // ✅ Here is the magic sauce

    if (trackerContainer.value && tracker.value.length > 0) {
      console.log(trackerContainer.value.scrollWidth);
      console.log(trackerContainer.value.scrollLeft);
      const scrollLeft = getScrollPosition(
        tracker.value,
        trackerContainer.value.scrollWidth
      );
      trackerContainer.value.scrollLeft = scrollLeft;
      console.log(trackerContainer.value.scrollLeft);
    }
  }
);
</script>

Thanks @tachibana-shin for the inspiration!

Sign up to request clarification or add additional context in comments.

6 Comments

Glad you figured it out. By the way there's a way to shorten your if statement, you can now write if (trackerContainer.value?.length > 0)
the first condition is checking the value of trackerContainer while the second condition is checking the value of tracker. But otherwise a noteworthy comment if the were both trackerContainer
You seriously didn't think I knew what your code was doing? I've receommended to you some new Javascript syntax and it is absolutely on point, your comment notwithstanding. Did you even try it? I'm trying to help you, and you insult me
Sorry, I thought you misread and saw this(trackeContainer.value && trackerContainer.value.length instead of what I put. Now I understand you're saying the second check isn't needed because if tracker.value.length > 0, then it would follow that trackerContainer.value would have children elements and I could check that. However, your code is still wrong because it's not trackerContainer.value?.length but rather trackerContainer.value?.children?.length. Good idea, thanks!
That said, the idea still doesn't quite work because trackerContainer.value?.children?.length > 0 gives a typescript error trackerContainer.value.children.length is possibly 'undefined'
|
1

This is not a dom problem i bet your ref='container' is not scrollable: More: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft

3 Comments

Not a bad callout. In my example above that works in the playground that might actually be the issue However, in the full code I have the container overflow-x set to auto, and it has a scrollWidth of 1500. I open the page in a browser with a width of 720px, and the element shows a scroll bar that works when viewed
I just tried setting overflow-x to scroll, gave it a width of 500px knowing the content is larger, and ran it. still same issue.
I tried updated the code snippet above giving it a width, overflow-x scroll, and gave it overflow content, and it worked. I'm betting that my container is the old container before i give it overflowing content. It's too late for me to test now, but first thing in the morning I'll give it a shot. Thanks for the input, I think it might have lead me where i need to go :D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.