How to automatically scroll to bottom of an element after toggling its visibility in Vue 3.

Nov 22, 2024 10:26AM

When you need to automatically scroll an element to its bottom after toggling it into view in Vue 3, you would notice things doesn't actually work properly if you call the scroll function immediately after toggling the element's visibility.

Let's consider this scenario where you have a scrollable element that is hidden by default and a user has to click on a checkbox to make it visible. If there is vital information at the bottom of the element, in most cases you want to automatically scroll to the botton as soon as the element is visible.

Below is a code snippet of how you might implement this.

<script setup lang="ts">
import { ref } from "vue";

const scrollEl = ref<HTMLDivElement | null>(null);
const isVisible = ref(false);

const toggleVisibility = () => {
  isVisible.value = !isVisible.value;

  if (isVisible.value && scrollEl.value) {
    scrollEl.value.scrollTop = scrollEl.value.scrollHeight;
  }
};
</script>

<template>
  <button type="button" @click="toggleVisibility">Toggle Me</button>
  <div ref="scrollEl">Element with long scrollable content...</div>
</template>

You would notice the above code doesn't produce the desired output.

As mentioned in the Vue documentation:

When you mutate reactive state in Vue, the resulting DOM updates are not applied synchronously. Instead Vue buffers them until the "next tick" to ensure that each component updates only once no matter how many state changes you have made.

So a simple fix for the above issue is to call the nextTick utility function before the scrolling part of the code.

Here is an updated version of the code:

<script setup lang="ts">
import { ref, nextTick } from "vue"; // Updated import

const scrollEl = ref<HTMLDivElement | null>(null);
const isVisible = ref(false);

// Function now marked as async
const toggleVisibility = async () => {
  isVisible.value = !isVisible.value;

  await nextTick(); // Notice the new change here

  if (isVisible.value && scrollEl.value) {
    scrollEl.value.scrollTop = scrollEl.value.scrollHeight;
  }
};
</script>

<template>
  <button type="button" @click="toggleVisibility">Toggle Me</button>
  <div ref="scrollEl">Element with long scrollable content...</div>
</template>