了解虚拟列表背后原理,轻松实现虚拟列表

更新日期: 2022-05-11阅读量: 176标签: 原理

比如 umy-ui (ux-table)虚拟列表table组件, vue-virtual-scroller 以及 react-virtualized 这些优秀的插件快速满足业务需要。为了理解插件背后的原理机制,我们实现一个自己简易版的虚拟列表,希望在实际业务项目中能带来一些思考和帮助。

正文开始...


虚拟列表是什么

在大数据渲染中,选择一段可视区域显示对应数据。

我们先初步看一个图

在这张展示图中,我们可以看到我们展示的始终是 红色线虚线 展示的部分,每一个元素固定高度,被一个 很大高度的元素 包裹着,并且 最外层 有一个固定的高度容器,并且设置可以滚动。

新建一个 index.html 对应结构如下

...
<div class="vitual-list-wrap" ref="listWrap">
<div class="content" :style="contentStyle">
<div class="item" v-for="(item, index) in list"
:key="index" :style="item.style">
{{item.content}}
</div>
</div>
</div>

对应的 css

*{
padding:0px;
margin: 0px;
}
#app {
width:300px;
border: 1px solid #e5e5e5;
}
/*外部容器给一个固定的可视高度,并且设置可以滚动*/
.vitual-list-wrap {
position: relative;
height: 800px;
overflow-y: auto;
}
/*真实容器的区域*/
.content {
position: relative;
}
/*固定高度的每个元素*/
.item {
height: 60px;
padding: 10px 5px;
border-bottom: 1px solid #111;
position: absolute;
left:0;
right: 0;
line-height: 60px;
}

从对应页面结构与 css 中我们的思路大致是这样

  • 确定外层固定的高度,并且设置纵向滚动条

  • 真实容器设置相对定位,并且根据显示总数动态设置一个装载容器的高度

  • 每个元素设置绝对定位,且是固定高度

有了对应设置的结构,因为我们每个元素是绝对定位的,所以我们现在的思路就是:

1、确定可视区域 item 显示的条数 limit

2、向上滑动的当前位置 起始位 与 最后位置 ,确定显示元素范围

3、确定每个元素的 top ,当向上滑动时,确定当前的位置与最后元素的位置索引,根据当前位置与最后元素位置,渲染 可视区域

具体逻辑代码如下

<div id="app">
<h3>虚拟列表</h3>
<div class="vitual-list-wrap" ref="list-wrap">
<div class="content" :style="contentStyle">
<div class="item" v-for="(item, index) in list"
:key="index" :style="item.style">
{{item.content}}
</div>
</div>
</div>
</div>
<!--引入vue3组件库-->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.33/vue.global.min.js"></script>
<script src="./index.js"></script>

我们具体看下 index.js

  // index.js
const { createApp, reactive, toRefs, computed, onMounted, ref } = Vue;
const vm = createApp({
setup() {
const listWrap = ref(null);
const viewData = reactive({
list: [],
total: 1000, // 数据总条数
height: 600, // 可视区域的高度
rowHeight: 60, // 每条item的高度
startIndex: 0, // 初始位置
endIndex: 0, // 结束位置
timer: false,
bufferSize: 5 // 做一个缓冲

});
const contentStyle = computed(() => {
return {
height: `${viewData.total * viewData.rowHeight}px`,
position: 'relative',
}
});
// todo 设置数据
const renderData = () => {
viewData.list = [];
const {rowHeight, height, startIndex, total, bufferSize} = viewData;
// 当前可视区域的row条数
const limit = Math.ceil(height/rowHeight);
console.log(limit, '=limit');
// 可视区域的最后一个位置
viewData.endIndex = Math.min(startIndex + limit + bufferSize, total -1);
for (let i=startIndex; i<viewData.endIndex; i++) {
viewData.list.push({
content: i,
style: {
top: `${i * rowHeight}px`
}
})
}
}
// todo 监听滚动,设置statIndex与endIndex
const handleScroll = (callback) => {
// console.log(listWrap.value)
listWrap.value && listWrap.value.addEventListener('scroll', (e) => {
if (this.timer) {
return;
}
const { rowHeight, startIndex, bufferSize } = viewData;
const { scrollTop } = e.target;
// 计算当前滚动的位置,获取当前开始的起始位置
const currentIndex = Math.floor(scrollTop / rowHeight);
viewData.timer = true;
// console.log(startIndex, currentIndex);
// 做一个简单的节流处理
setTimeout(() => {
viewData.timer = false;
// 如果滑动的位置不是当前位置
if (currentIndex !== startIndex) {
viewData.startIndex = Math.max(currentIndex - bufferSize, 0);
callback();
}
}, 500)
})
}
onMounted(() => {
renderData();
handleScroll(renderData);
})
return {
...toRefs(viewData),
contentStyle,
renderData,
listWrap
}
},
})
vm.mount('#app')

看下页面,已经ok了,每次上滑都只会固定高度加载对应的数据。

注意我们在 css 中有一段这样的代码

#app {
width:300px;
border: 1px solid #e5e5e5;
opacity: 0;
}
...
[>opacity: 1 !important;
}

这样处理主要是为了插值表达式在未渲染的时候,让用户看不到未渲染前的模版内容。如果不先隐藏,那么会打开页面的时候会有插值表达式, vue 中提供了一个 v-cloak ,但是貌似这里不管用,在 vue2 中是可以的。

本篇是非常简易的虚拟列表实现,了解虚拟列表背后的实现思想,更多可以参考 vue-virtual-scroller 与 react-virtualized源码的实现,具体应用示例可以查看之前写的一篇偏应用的文章测试脚本把页面搞崩了。


总结

  • 了解虚拟列表到底是什么,在大数据渲染中,选择一段可视区域显示对应数据

  • 实现虚拟列表的背后原理,最外层给定一个固定的高度,然后设置纵向 Y轴 滚动,然后每个元素的父级设置相对定位,设置真实展示数据的高度,根据 item 固定高度( rowHeight ),根据可视区域和 rowHeight 计算可显示的 limit 数目。

  • 当滚动条上滑时,计算出滚动的距离 scrollTop ,通过 currentIndex = Math.floor(scrollTop/rowHeight) 计算出当前起始索引

  • 根据 endIndex = Math.min(currentIndex+limit, total-1) 计算出最后可显示的索引

  • 最后根据 startIndex 与结束位置 endIndex ,根据 startIndex 与 endIndex 渲染可视区域

  • 本文示例代码 code example 

  • 本文参考相关文章 如何实现一个高度自适应的虚拟列表 ,这是 react 版本的

来自:https://mp.weixin.qq.com/s/JJ7Lq5d3eMbvg58JZuGqHw

链接: https://www.fly63.com/article/detial/11483

CSS定位之BFC背后的神奇原理

BFC已经是一个耳听熟闻的词语了,网上有许多关于 BFC 的文章,介绍了如何触发 BFC 以及 BFC 的一些用处(如清浮动,防止 margin 重叠等)。BFC直译为\"块级格式化上下文\"。它是一个独立的渲染区域,只有Block-level box参与

天天都在使用CSS,那么CSS的原理是什么呢?

作为前端,我们每天都在与CSS打交道,那么CSS的原理是什么呢?开篇,我们还是不厌其烦的回顾一下浏览器的渲染过程,学会使用永远都是最基本的标准,但是懂得原理,你才能触类旁通,超越自我。

JavaScript 中的函数式编程原理

做了一些研究,我发现了函数式编程概念,如不变性和纯函数。 这些概念使你能够构建无副作用的功能,而函数式编程的一些优点,也使得系统变得更加容易维护。我将通过 JavaScript 中的大量代码示例向您详细介绍函数式编程和一些重要概念。

Angular ZoneJS 原理

如果你阅读过关于Angular 2变化检测的资料,那么你很可能听说过zone。Zone是一个从Dart中引入的特性并被Angular 2内部用来判断是否应该触发变化检测

Vue.js响应式原理

updateComponent在更新渲染组件时,会访问1或多个数据模版插值,当访问数据时,将通过getter拦截器把componentUpdateWatcher作为订阅者添加到多个依赖中,每当其中一个数据有更新,将执行setter函数

new运算符的原理

一个继承自 Foo.prototype 的新对象被创建;使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数时,Foo 不带任何参数调用的情况

彻底弄懂HTTP缓存机制及原理

Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知识技能。

https的基本原理

HTTPS = HTTP + TLS/SSL,简单理解 HTTPS 其实就是在 HTTP 上面加多了一层安全层。HTTP 可以是 Http2.0 也可以是 Http1.1,不过现在 Http2.0 是强制要求使用 Https 的。使用非对称密钥(即公钥私钥))和对称密钥)(即共享密钥)相结合

Node中的Cookie和Session

HTTP是无状态协议。例:打开一个域名的首页,进而打开该域名的其他页面,服务器无法识别访问者。即同一浏览器访问同一网站,每次访问都没有任何关系。Cookie的原理是

理解Promise原理

Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!