Vue3 滚动hook的封装和使用

52 min read
import type { Ref } from 'vue'
import { nextTick, ref } from 'vue'

type ScrollElement = HTMLDivElement | null

interface ScrollReturn {
  scrollRef: Ref<ScrollElement>
  scrollToBottom: () => Promise<void>
  scrollToTop: () => Promise<void>
  scrollToBottomIfAtBottom: () => Promise<void>
}

export function useScroll(): ScrollReturn {
  const scrollRef = ref<ScrollElement>(null)

  const scrollToBottom = async () => {
    await nextTick()
    if (scrollRef.value)
      scrollRef.value.scrollTop = scrollRef.value.scrollHeight
  }

  const scrollToTop = async () => {
    await nextTick()
    if (scrollRef.value)
      scrollRef.value.scrollTop = 0
  }

  const scrollToBottomIfAtBottom = async () => {
    await nextTick()
    if (scrollRef.value) {
      const threshold = 100 // Threshold, indicating the distance threshold to the bottom of the scroll bar.
      const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
      if (distanceToBottom <= threshold)
        scrollRef.value.scrollTop = scrollRef.value.scrollHeight
    }
  }

  return {
    scrollRef,
    scrollToBottom,
    scrollToTop,
    scrollToBottomIfAtBottom,
  }
}

1. 滚动到元素的底部

自动滚动到一个可滚动元素的底部(例如,显示聊天窗口中的最新消息),可以将 scrollTop 设置为 scrollHeight

const element = document.getElementById('scrollable-element');
element.scrollTop = element.scrollHeight;

在 Vue 中,使用 ref 来引用元素,代码会是这样的:

// Vue Composition API
const scrollRef = ref(null);

// ...在模板中绑定 scrollRef 到元素

// 方法:滚动到底部
const scrollToBottom = () => {
  nextTick(() => {
    if (scrollRef.value) {
      scrollRef.value.scrollTop = scrollRef.value.scrollHeight;
    }
  });
};

2. 检查是否滚动到底部

想知道用户是否已经滚动到元素的底部。这可以通过比较 scrollTop, clientHeightscrollHeight 来实现:

const isScrolledToBottom = element.scrollTop + element.clientHeight === element.scrollHeight;

在 Vue 中,您可能会在一个事件处理器或者 watchEffect 中进行这样的检查。

3. 保持滚动位置在底部

在某些实时更新内容的应用中(如聊天应用),在新内容添加时保持滚动位置在底部是常见需求。这可以通过在添加新内容后调用一个方法来实现,该方法检查当前滚动位置并在需要时滚动到底部。

const scrollToBottomIfNeeded = () => {
  nextTick(() => {
    if (scrollRef.value) {
      const { scrollTop, scrollHeight, clientHeight } = scrollRef.value;
      if (scrollTop + clientHeight >= scrollHeight - threshold) {
        scrollRef.value.scrollTop = scrollHeight;
      }
    }
  });
};

// 每次添加新内容后调用 scrollToBottomIfNeeded

scrollHeight 是处理滚动相关逻辑时非常有用的 DOM 属性。通过结合 scrollTopclientHeight,您可以实现各种滚动相关的功能,如自动滚动到底部、检测滚动位置等。