vue3 无限自滚动列表的实现

最近有个类似虚拟列表的需求,但是要求自己滚动。

实现

我琢磨了一下,用了以下的方法实现。

  • 不管一共有多少数据,每一时刻实际渲染的数据量应该是固定的,我这里用了两屏的数据
  • 这里的滚动没有用滚动条,用的是top的偏移量来实现,因为我这里的需求只需要自己滚动,不用手动滚动,用滚动条的话,相同的原理,应该也可以实现,但是还要把滚动条隐藏了,麻烦。
  • 由于实际的渲染量一定,所以当top 的偏移量等于一屏数据的高度的时候,应该把top 的值重置为零,同时要把已经消失在视野的那一屏数据删除,这样的配合下,top 的改变就不会引起页面的闪动,因为此时的位置下top就是等于零。
  • 在上面删除了已经看不见的一屏数据的时候,就一定要在下面添加上新的一屏数据,这样,就能保证页面上始终有且只有两屏数据,也能确保滚动的这个效果。
  • 最后一屏的数据量可能不够一屏的,这里没有做处理,好处是知道这一轮的数据结束了,当然也可以做个精细化的处理,实现真正的无限滚动,这个看具体的业务需求。

优化项

  • 给页面中元素添加子元素的时候,没有直接添加,而是用的createDocumentFragment,能减少元素的插入次数
  • 这里的滚动走的不是setTimeout ,而是用的requestAnimationFrame

效果

这里可以看见,容器的top值一直在变动,同时里面的两个子元素也是动态变化的。

代码

js 复制代码
<template>
  <div ref="list" class="rollList_content">
    <div ref="vlist" class="vabsolute_content">
      <div class="fragment_content"></div>
      <div class="fragment_content"></div>
    </div>
  </div>
</template>

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

const list = ref()
const vlist = ref()
const containerHeight = ref(600)
let perCount = 0
const lineHight = 20
const count = 40
const data = ref([])
const currentData = ref([])
let startIndex = 0
let endIndex = 0

for (let i = count; i >= 0; i--) {
  data.value.push(i)
}

const fillEle = (ele, startIndex, endIndex) => {
  const fragment = document.createDocumentFragment()
  for (let i = startIndex; i < endIndex; i++) {
    const newLine = document.createElement('div')
    newLine.className = 'roll_line'
    newLine.textContent = data.value[i]
    fragment.appendChild(newLine)
  }
  ele.appendChild(fragment)
}

const loop = () => {
  if (startIndex > data.value.length) {
    startIndex = endIndex = 0
    vlist.value.firstElementChild.innerHTML = ''
    vlist.value.childNodes[1].innerHTML = ''
  }
  if (startIndex === endIndex) {
    let firstFragment = vlist.value.firstElementChild

    endIndex += perCount
    fillEle(firstFragment, startIndex, endIndex)
    const secondChildNode = vlist.value.childNodes[1]
    startIndex = endIndex
    endIndex += perCount
    fillEle(secondChildNode, startIndex, endIndex)
  } else {
    let currentTop = parseInt(vlist.value.style?.top, 10) || 0
    if (Math.abs(currentTop) >= lineHight * perCount) {
      vlist.value.removeChild(vlist.value.firstElementChild)
      vlist.value.style.top = 0
      const newFragment = document.createElement('div')
      newFragment.className = 'fragment_content'
      startIndex = endIndex
      endIndex += perCount
      fillEle(newFragment, startIndex, endIndex)
      vlist.value.appendChild(newFragment)
    } else {
      vlist.value.style.top = `${currentTop - 1}px`
    }
  }

  window.requestAnimationFrame(loop)
}

onMounted(() => {
  containerHeight.value = list.value.offsetHeight
  perCount = ~~(containerHeight.value / lineHight)
  loop()
})
</script>

<style lang="scss" scoped>
.rollList_content {
  height: 100%;
  overflow-y: hidden;
  position: relative;
  .vabsolute_content {
    position: absolute;
    top: 0;
    .roll_line {
      display: flex;
      height: 20px;
      color: #fff;
    }
  }
}
</style>
<style>
.roll_line {
  display: flex;
  height: 20px;
  color: #fff;
}
</style>
相关推荐
墨渊君3 分钟前
React Native 跨平台组件库实践: GlueStack UI 上手指南
前端
晓得迷路了10 分钟前
栗子前端技术周刊第 84 期 - Vite v7.0 beta、Vitest 3.2、Astro 5.9...
前端·javascript·vite
独立开阀者_FwtCoder13 分钟前
最全301/302重定向指南:从SEO到实战,一篇就够了
前端·javascript·vue.js
Moment22 分钟前
给大家推荐一个超好用的 Marsview 低代码平台 🤩🤩🤩
前端·javascript·github
小满zs26 分钟前
Zustand 第三章(状态简化)
前端·react.js
普宁彭于晏28 分钟前
元素水平垂直居中的方法
前端·css·笔记·css3
恋猫de小郭39 分钟前
为什么跨平台框架可以适配鸿蒙,它们的技术原理是什么?
android·前端·flutter
云浪43 分钟前
元素变形记:CSS 缩放函数全指南
前端·css
明似水1 小时前
用 Melos 解决 Flutter Monorepo 的依赖冲突:一个真实案例
前端·javascript·flutter
独立开阀者_FwtCoder1 小时前
stagewise:让AI与代码编辑器无缝连接
前端·javascript·github