虚拟滚动列表(动态高)

描述:动态高度需要一个positionData的数组先行存储全部数据量的位置信息。第一次初始化由于不知道cell的具体高度有多少,所以需要先自行预估一个大致的高度cellHeight,然后全部存储在positionData中。存储完成后获取可视区域第一行要渲染的数据的对应索引,即可开始渲染数据。数据渲染完成后,由于是预估的高度,使用的绝对定位导致cell互相重叠,所以在updated生命钩子中,对存储的位置信息positionData数组进行重新校对。
注意点:
(1)在html中,cell使用了绝对定位,定位的top值是由positionData中的top值来决定的,所以校对了positionData数组中的top值,html也会跟着进行位置的重新定位
(2)盒子的滚动区域高度是由positionData数组的最后一项数据的boxHeight属性决定的,在开始时,只有预估的高度,所以盒子的滚动高度也是预估的。在positionData数组重新校对后,数组的每一项boxHeight也跟着校对,所以滚动盒子的真实高度也会根据实际的cell盒子高度变化而变化
在这里插入图片描述
在head中导入vue

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

css

<style>
  * {
    margin: 0;
    padding: 0;
  }

  .container {
    width: 280px;
    height: 500px;
    margin: 50px auto;
    border: 1px solid #ccc;
    overflow: auto;
  }

  .contentBox {
    position: relative;
  }

  .cell {
    position: absolute;
    left: 0;
    line-height: 25px;
  }
</style>

html

<div id="app">
  <div class="container">
    <div class="contentBox" :style="{height: contentBoxHeight}">
      <p 
        ref="cell"
        class="cell"
        v-for="(item, index) in showList"
        :key="index"
        :data-index="item.index"
        :style="{top: positionData[item.index].top + 'px'}"
      >{{item.data}}</p>
    </div>
  </div>
</div>

js

<script>
  window.onload =function () {
    var vue = new Vue({
      el: '#app',
      data: {
        // 全部数据量
        list: new Array(1000).fill(0).map((item, index) => {
          // 显示的内容随机重复1-30次,模拟动态高
          return ('内容' + (index + 1)).repeat(Math.floor(Math.random() * 30) + 1)
        }),
        // 展示的数据
        showList: [
          // {
          //   index,  // 位置索引
          //   data: '暂无数据'
          // }
        ],
        // 每行高度(预估高度)
        cellHeight: 50,
        // 可视框显示的数据量
        showCount: 0,
        // 存储每一项的位置信息
        positionData: [
          // {
          //   index: 0,  // 位置索引
          //   top: 0, // 距离顶部的位置
          //   height: 0,  // 当前项的高度
          //   // 顶部的位置 + 当前项的高度 = 滚动盒子应用的高度
          //   boxHeight: 0
          // }
        ]
      },
      computed: {
        // 需要滚动的内容高度
        contentBoxHeight() {
          return this.positionData[this.positionData.length - 1]?.boxHeight + 'px'
        }
      },
      methods: {
        // 初始化基本参数
        initData() {
          let containerDom = document.querySelector('.container')
          // 盒子高度
          let containerDomHeight = containerDom.clientHeight
          // 可视框需要显示的数据量
          this.showCount = Math.ceil(containerDomHeight / this.cellHeight)

          // 预估的位置信息
          this.positionData = this.list.map((item, index) => {
            return {
              index: index,
              top: index * this.cellHeight,
              height: this.cellHeight,
              // 当前项为最后一条数据,滚动盒子的预估高度
              // top值加上本身的高度,就能得知滚动盒子的预估高度
              // boxHeight: index * this.cellHeight + this.cellHeight
              boxHeight: (index + 1) * this.cellHeight
            }
          })

          // 实现虚拟滚动
          this.initScrolLoad(containerDom)
          containerDom.onscroll = () => {
            this.initScrolLoad(containerDom)
          }
        },
        // 实现虚拟滚动
        initScrolLoad(containerDom) {
          // 滚动出去的距离
          let scrollTop = containerDom.scrollTop
          // 可视区域第一行索引
          let startIndex = Math.max(this.getStartIndex(scrollTop) - 2, 0)
          // 可视区域最后一行索引
          let endIndex = Math.min(startIndex + this.showCount + 4, this.list.length)
          
          // 获取需要渲染的数据
          this.showList = []
          for (let i = startIndex; i < endIndex; i++) {
            this.showList.push({
              data: this.list[i],
              index: this.positionData[i].index,
            })
          }
        },
        // 获取可视区域第一行索引
        getStartIndex(scrollTop) {
          // 暴力搜索
          // let startIndex = this.positionData.findIndex(item => {
          //   return item.boxHeight > scrollTop
          // })
          // 二分查找
          let left = 0, right = this.positionData.length - 1, res = right
          while(left < right) {
            let mid = Math.floor((right - left) / 2) + left
            if(this.positionData[mid].boxHeight === scrollTop) {
              // console.log(mid)
              return mid + 1
            } else if(this.positionData[mid].boxHeight > scrollTop) {
              res = Math.min(mid, res)
              right = right - 1
            } else if(this.positionData[mid].boxHeight < scrollTop) {
              left = mid + 1
            }
          }
          return res
        }
      },
      mounted() {
        // 初始化
        this.initData()
      },
      // 等预估的位置信息渲染后,更新实际的信息
      updated() {
        let cellNode = this.$refs.cell
        cellNode.forEach((node) => {
          // 实际高度
          let height = node.getBoundingClientRect().height
          let index = +node.dataset.index
          let position = this.positionData[index]
          let cha = height - position.height
          // console.log(cha)
          // 当前node的实际高度,跟预估高度不一致
          if(cha) {
            // 更新当前node的位置信息
            this.positionData[index].height = height
            this.positionData[index].boxHeight += cha
            for(let i = index + 1; i < this.positionData.length; i++) {
              // 更新当前node后面所有项的位置信息
              this.positionData[i].top = this.positionData[i - 1].boxHeight
              this.positionData[i].boxHeight += cha
            }
          }
        })
      }
    })
  }
</script>

如列表项高度固定,可使用虚拟滚动列表(固定高),固定高的实现相对动态高简单的多。

参考文章
https://juejin.cn/post/6844903982742110216

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3提供了一个新的组件API,使得构建虚拟列表变得更加容易。下面是一个简单的实现示例: 1. 安装依赖: ```bash npm install vue@next npm install vue3-virtual-scroll-list ``` 2. 在组件中使用虚拟列表: ```vue <template> <virtual-scroll-list :size="50" :remain="20" :data-key="'id'" :data-sources="items" @scroll="handleScroll" > <template v-slot="{ data }"> <div v-for="item in data" :key="item.id">{{ item.text }}</div> </template> </virtual-scroll-list> </template> <script> import { ref } from 'vue' import VirtualScrollList from 'vue3-virtual-scroll-list' export default { components: { VirtualScrollList, }, setup() { const items = ref([]) // 初始化数据 for (let i = 0; i < 10000; i++) { items.value.push({ id: i, text: `Item ${i}`, }) } const handleScroll = (scrollTop) => { // 处理滚动事件 console.log(scrollTop) } return { items, handleScroll, } }, } </script> ``` 在这个示例中,我们使用 `vue3-virtual-scroll-list` 组件来实现虚拟列表。这个组件需要传入一些参数,包括: - `size`:每个项的高度 - `remain`:上下额外渲染项的数量 - `data-key`:数据中每个项的唯一标识符 - `data-sources`:数据源 - `scroll`:滚动事件的回调函数 在模板中,我们使用插槽来渲染每个项。同时,组件还会将已经渲染的项缓存起来,以提性能。 在 `setup` 函数中,我们初始化了一个 `items` 的响应式变量,并将它传入 `data-sources` 中。我们还定义了一个 `handleScroll` 函数来处理滚动事件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值