vxe-list vue下拉框的虚拟列表
vxe-table vxe-list vue 实现下拉框的虚拟列表
虚拟列表的实现原理
只渲染可视区的 dom 节点,其余不可见的数据卷起来,只会渲染可视区域的 dom 节点,提高渲染性能及流畅性,优点是支持海量数据的渲染;当然也会有缺点:滚动效果相对略差(海量数据与滚动效果的取舍问题就看自己的需求喽);
<div class="my-select"> <input type="text" class="my-select-input" readonly> <vxe-list class="my-select-wrapper" :loading="loading" :data="list"> <template v-slot="{ items }"> <div class="my-select-option" v-for="item in items" :key="item.value">{{ item.label }}</div> </template> </vxe-list> </div>
export default { data () { return { loading: false, list: [] } }, created () { this.loading = true setTimeout(() => { const startTime = Date.now() var list = [] for(var i=0;i<100000;i++){ list.push({ label: '选项'+i, value: i }) } this.list = list this.loading = false this.$nextTick(() => { this.$XModal.message({ message: `渲染 ${list.length} 行,用时 ${Date.now() - startTime}毫秒`, status: 'info' }) }) }, 200) } }
.my-select { width: 200px; position: relative; background-color: #fff; } .my-select-input { width: 100%; height: 24px; border: 1px solid #dcdfe6; } .my-select-wrapper { position: absolute; left: 0; top: 26px; width: 100%; height: 200px; background-color: #fff; border: 1px solid #dcdfe6; } .my-select-option:hover { background-color: #f5f7fa; cursor: pointer; }
接下来测试一下
渲染 1w 条只需要 150 毫秒左右
渲染 5w 条只需要 300 毫秒左右
渲染 10w 条只需要 500 毫秒左右
具体用法可以去看 官方文档,在线运行 http://jsrun.net/CW2Kp/edit
vue虚拟列表实现原理
应用场景
前端的业务开发中会遇到不使用分页方式来加载长列表的需求。如在数据长度大于 1000 条情况,DOM 元素的创建和渲染需要的时间成本很高,完整渲染列表所需要的时间不可接受,同时会存在滚动时卡顿问题;
解决该卡顿问题的重点在于如何降低长列表DOM渲染成本问题,文章将介绍通过虚拟列表渲染的方式解决该问题。
为什么需要虚拟列表
虚拟列表是对长列表的一种优化方案。在前端开发中,会碰到一些不能使用分页方式来加载列表数据的业务形态,我们称这种列表叫做长列表。比如,手机端,淘宝商品展示,美团外卖等,数据量特别庞大,不适合分页,以及懒加载,这时候我们可以采用虚拟列表,只展示可视区域数据。
实现思路
虚拟列表的核心思想为可视区域渲染,在页面滚动时对数据进行截取、复用DOM进行展示的渲染方式。
实现虚拟列表就是处理滚动条滚动后的可见区域的变更,其中具体步骤如下:
1.计算当前可见区域起始数据的 startIndex
2.计算当前可见区域结束数据的 endIndex
3.计算当前可见区域的数据,并渲染到页面中
4.计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上
基础实现
我们首先要考虑的是虚拟列表的 HTML、CSS 如何实现:
- 列表元素(.list-view)使用相对定位
- 使用一个不可见元素(.list-view-phantom)撑起这个列表,让列表的滚动条出现
- 列表的可见元素(.list-view-content)使用绝对定位,left、right、top 设置为 0
html:
<template> <div class="list-view" :style="{ height: `${height}px` }" @scroll="handleScroll"> <div class="list-view-phantom" :style="{ height: contentHeight }"> </div> <ul ref="content" class="list-view-content"> <li class="list-view-item" :style="{ height: itemHeight + 'px' }" v-for="(item, index) in visibleData" :key="index"> {{ item }} </li> </ul> </div> </template>
script:
<script> export default { name: 'ListView', props: { data: { type: Array, default: function() { const list = [] for (let i = 0; i < 1000000; i++) { list.push('列表' + i) } return list } }, height: { type: Number, default: 400 }, itemHeight: { type: Number, default: 30 }, }, computed: { contentHeight() { return this.data.length * this.itemHeight + 'px'; } }, mounted() { this.updateVisibleData(); }, data() { return { visibleData: [] }; }, methods: { updateVisibleData(scrollTop) { scrollTop = scrollTop || 0; const visibleCount = Math.ceil(this.$el.clientHeight / this.itemHeight); // 取得可见区域的可见列表项数量 const start = Math.floor(scrollTop / this.itemHeight); // 取得可见区域的起始数据索引 const end = start + visibleCount; // 取得可见区域的结束数据索引 this.visibleData = this.data.slice(start, end); // 计算出可见区域对应的数据,让 Vue.js 更新 this.$refs.content.style.webkitTransform = `translate3d(0, ${ start * this.itemHeight }px, 0)`; // 把可见区域的 top 设置为起始元素在整个列表中的位置(使用 transform 是为了更好的性能) }, handleScroll() { const scrollTop = this.$el.scrollTop; this.updateVisibleData(scrollTop); } } } </script>
css:
<style lang="scss" scoped> .list-view { overflow: auto; position: relative; border: 1px solid #aaa; width: 200px; } .list-view-phantom { position: absolute; left: 0; top: 0; right: 0; z-index: -1; } .list-view-content { left: 0; right: 0; top: 0; position: absolute; } .list-view-item { padding: 5px; color: #666; line-height: 30px; box-sizing: border-box; } </style>
以上为个人经验,希望能给大家一个参考,也希望大家多多支持阿兔在线工具。