基于Vue3的极简虚拟滚动实现方案,无额外插件引用,附有完整demo

不多bb,直接开整

定义变量

const demo = ref(null) // 外框盒子
const showNumber = 20 // 当前视窗展示条数
const itemHeight = 20 // 每一条内容的高度
const data = createData(1000) // 实际数据
let startNum = ref(0) // 当前视窗范围内第一个元素下标
let positionTop = ref(0) // 当前视窗范围内第一个元素偏移量
复制代码

滚动

虚拟滚动第一条件首先是要能滚动,那么外框盒子高度固定,设置overflow: auto;

<div
    ref="demo"
    class="scroll-box demo"
    :style="`height: ${showNumber * itemHeight}px;`"
></div>
复制代码

然后外框盒子内部内置一个高度为理论上渲染全部内容的空div占位,用于展示滚动条

<div
    class="scroll-blank"
    :style="`height: ${data.length * itemHeight}px;`"
></div>
复制代码

虚拟

虚拟就是仅渲染当前视窗内的内容,而对于超出的部分则进行移除

<template>
    <div class="scroll-data" :style="`top: ${positionTop}px;`">
        <div
            v-for="(item, index) in activeList"
            :key="item"
            class="scroll-item"
        >
            {{ item }}
        </div>
    </div>
</template>
<script>
const activeList = computed(() => {
    const start = startNum.value
    return data.slice(start, start + showNumber)
})
</script>
复制代码

什么时候对渲染的数据进行替换?

对外框盒子添加 scroll 的监听事件,在滚动的时候获取scrollTop的值并计算当前视窗范围内第一个元素的下标

const scrollEvent = (event) => {
    const { scrollTop } = event.target
    startNum.value = parseInt(scrollTop / itemHeight)
    positionTop.value = scrollTop
}

onMounted(() => {
    demo.value.addEventListener('scroll', scrollEvent)
})
onUnmounted(() => {
    if (!demo.value) return
    demo.value.removeEventListener('scroll', scrollEvent)
    demo.value = null
})
复制代码

最后

贴上完整demo

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
    <title>VirtualScroll</title>
  </head>
  <body>
    <div id="app">
      <div
        ref="demo"
        class="scroll-box demo"
        :style="`height: ${showNumber * itemHeight}px;`"
      >
        <div
          class="scroll-blank"
          :style="`height: ${data.length * itemHeight}px;`"
        ></div>
        <div class="scroll-data" :style="`top: ${positionTop}px;`">
          <div
            v-for="(item, index) in activeList"
            :key="item"
            class="scroll-item"
          >
            {{ item }}
          </div>
        </div>
      </div>
    </div>
    <script>
      const { computed, onMounted, onUnmounted, ref } = Vue

      const createData = (length) => {
        return Object.keys(new Array(length).fill(''))
      }
      const App = {
        setup() {
          const demo = ref(null) // 外框盒子
          const showNumber = 20 // 当前视窗展示条数
          const itemHeight = 20 // 每一条内容的高度
          const data = createData(1000) // 实际数据
          let startNum = ref(0) // 当前视窗范围内第一个元素下标
          let positionTop = ref(0) // 当前视窗范围内第一个元素偏移量

          // 计算当前视窗内实际要渲染的内容
          const activeList = computed(() => {
            const start = startNum.value
            return data.slice(start, start + showNumber)
          })

          // 滚动的时候计算当前视窗范围内第一个元素下标
          const scrollEvent = (event) => {
            const { scrollTop } = event.target
            startNum.value = parseInt(scrollTop / itemHeight)
            positionTop.value = scrollTop
          }

          onMounted(() => {
            demo.value.addEventListener('scroll', scrollEvent)
          })
          onUnmounted(() => {
            if (!demo.value) return
            demo.value.removeEventListener('scroll', scrollEvent)
            demo.value = null
          })

          return {
            showNumber,
            itemHeight,
            demo,
            positionTop,
            data,
            activeList,
          }
        },
      }

      const app = Vue.createApp(App)
      app.mount('#app')
    </script>
    <style>
      .scroll-box {
        position: relative;
        overflow: auto;
        width: 400px;
        border: 1px solid rgb(0, 0, 0);
      }
      .scroll-data {
        position: absolute;
        width: 100%;
      }
      .scroll-item {
        height: 20px;
      }
      .scroll-item:hover {
        background: rgb(104, 111, 211);
        color: #fff;
      }
    </style>
  </body>
</html>
复制代码

猜你喜欢

转载自juejin.im/post/7054088878877048869