使用 Vue3 构建虚拟滚动

1,220 阅读1分钟

虚拟滚动是一种非常常见的优化技术,那么本篇文章将介绍如何在 Vue3 中构建虚拟滚动。

原理

虚拟滚动的原理就是一次只渲染固定个的可视区域,每当滚动时,可视区域不会重新渲染,而是复用已经创建完毕的区域,并把滚动导致的数据变化渲染在复用区域。

实现

通过 Composition-api 实现相关功能可以说是非常简单,核心逻辑就是过滤得到已渲染区域对应的数据。已渲染区域一般会比可视区域要更高,这样是为了滚动的时候防止内容出现白条。

下面可以直接阅读核心逻辑是如何实现的。

//defined items array.
//defined itemHeight function

let defaultHeight = 100;
let totalHeight = 0;
let beforeHeight = 0;
const displayItemsRef = shallowRef([]);
const containerRef = ref(null);

const handleChange = () => {
  // no container, no handler
  const container = containerRef.value;
  if (!container) {
    return;
  }
  // 获得滚动条的上部分距离
  const scrollTop = container.scrollTop;
  const containerHeight = container.clientHeight;
  // 需要一点边界空间
  const buffer = 2 * defaultHeight;
  let beforeHeight = 0;
  let contentHeight = 0;
  let afterHeight = 0;
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const height = itemHeight?.(item) || defaultHeight;
    if (beforeHeight < scrollTop - buffer) {
      // 顶部
      beforeHeight += height;
      activeTags[i] = false;
    } else if (contentHeight < containerHeight + 3 * buffer) {
      // 可渲染部分
      contentHeight += height;
      activeTags[i] = true;
    } else {
      // 底部
      afterHeight += height;
      activeTags[i] = false;
    }
  }
  beforeHeight = beforeHeight;
  totalHeight = beforeHeight + afterHeight + contentHeight;
  displayItemsRef.value = items.filter((el, index) => activeTags[index]);
};

然后,通过 onMounted 和 onUnmounted,这两个函数触发,并添加相对应的滚动事件。

onMounted(() => {
  handleChange();
  containerRef.value?.addEventListener?.('scroll', handleChange);
});
onUnmounted(() => {
  containerRef.value?.removeEventListener?.('scroll', handleChange);
})

最后,在把对用的 containerRef 添加到相对应的结点上,并把可渲染结点渲染上。

()=>(
  <div ref={containerRef} style={{ position: 'relative', overflow: 'auto' }}>
    <div style={{ height: totalHeight + 'px' }} />
    <div
      style={{
        position: 'absolute',
        width: '100%',
        left: 0,
        top: beforeHeight + 'px',
      }}
    >
      {dispalyItemsRef.value.map((item, key) => (
        <div
          key={key}
          style={{
            backgroundColor: 'black',
            color: 'white',
            padding: '10px',
            height: `${defaultHeight}px`,
           }}
        >
          {el.name}
        </div>
      )}
    </div>
  </div>
)

大功告成!

小结

使用 Vue3 提供的 Composition-api 来进行一些组件的编写可以很简单的把一些逻辑抽离,形成可复用的逻辑组件,希望能给你带来新的理解。源码地址:power-ui/components/cdk/scrolling