携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
超长的数组
如果在面试的时候被问到:“后端一次性返回10000条数据,前端要怎么渲染呢”,这个时候先稳住,先不要拔出你那40米的大刀,让我们慢慢分析分析。
如果直接把这10000条数据直接去渲染
const renderList = async () => {
const list = await getList()
// 直接遍历数据,处理元素插入
list.forEach(item => {
const div = document.createElement('div') div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
container.appendChild(div)
})
}
renderList()
一次渲染 100,000 条记录大约需要 12 秒,这显然是不可取的。
我们再试下用 setTimeout ?
首先想到的应该是分批处理,比如每次处理10条或者20条,然后处理的逻辑放到setTimeout里面,这样不会阻塞主逻辑和页面渲染。
const renderList = async () => {
const list = await getList()
// 总数据
const total = list.length
// 当前分页下标
const page = 0
// 每次处理数据
const limit = 200
const totalPage = Math.ceil(total / limit)
// 处理元素插入
const render = (page) => {
if (page >= totalPage) return
requestAnimationFrame(() => {
for (let i = page * limit; i < page * limit + limit; i++) {
const item = list[i]
const div = document.createElement('div')
div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
container.appendChild(div)
}
render(page + 1)
})
}
render(page)
}
还有requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
加上虚拟列表试试?
其核心思想就是在处理用户滚动时,只改变列表在可视区域的渲染部分,具体步骤为:
先计算可见区域起始数据的索引值startIndex和当前可见区域结束数据的索引值endIndex,假如元素的高度是固定的,那么startIndex的算法很简单,即startIndex = Math.floor(scrollTop/itemHeight),endIndex = startIndex + (clientHeight/itemHeight) - 1,再根据startIndex 和endIndex取相应范围的数据,渲染到可视区域,然后再计算startOffset(上滚动空白区域)和endOffset(下滚动空白区域),这两个偏移量的作用就是来撑开容器元素的内容,从而起到缓冲的作用,使得滚动条保持平滑滚动,并使滚动条处于一个正确的位置
- 不把长列表数据一次性全部直接渲染在页面上
- 截取长列表一部分数据用来填充可视区域
- 长列表数据不可视部分使用空白占位填充(下图中的
startOffset和endOffset区域) - 监听滚动事件根据滚动位置动态改变可视列表
- 监听滚动事件根据滚动位置动态改变空白填充
- 计算当前可见区域起始数据的 startIndex
- 计算当前可见区域结束数据的 endIndex
- 计算当前可见区域的数据,并渲染到页面中
- 计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset ,并设置到列表上
但是虚拟列表有个问题就是滚动太快的时候js正在计算,出现空白区域。
有没有插件可以实现?
有一个三方库 Vue Virtual Scroll List,它在 Github 上又 2.5k+ 的 stars。
大量的 DOM 元素会使得我们的网页非常“重”。当 DOM 元素超过 1500 至 2000 个的时候,页面就开始又延迟,尤其是在小型的、性能差的设备上尤为明显。
想象一下,有一个无线滚动的页面,你不断的下拉,它实际上可能形成了上万个 DOM 元素,每个元素还包含子节点,这样将消耗巨大的性能。
Virtual scrollers 正是来解决这个问题的。
如上图,已经表示的很清楚了。列表分为可见区域和缓冲区域,超出这个范围的列表 DOM 都将被删除。
试下vue-virtual-scroll-list的效果如何
npm install vue-virtual-scroll-list --save
Root component:
<template>
<div>
<virtual-list style="height: 360px; overflow-y: auto;" // make list scrollable
:data-key="'uid'"
:data-sources="items"
:data-component="itemComponent"
/>
</div>
</template>
<script>
import Item from './Item'
import VirtualList from 'vue-virtual-scroll-list'
export default {
name: 'root',
data () {
return {
itemComponent: Item,
items: [{uid: 'unique_1', text: 'abc'}, {uid: 'unique_2', text: 'xyz'}, ...]
}
},
components: { 'virtual-list': VirtualList }
}
</script>
Item component:
<template>
<div>{{ index }} - {{ source.text }}</div>
</template>
<script>
export default {
name: 'item-component',
props: {
index: { // index of current item
type: Number
},
source: { // here is: {uid: 'unique_1', text: 'abc'}
type: Object,
default () {
return {}
}
}
}
}
</script>
可以看到使用了虚拟列表之后,前端处理后端传过来的大量数据就变得轻量很多,页面也不会有很重的感觉。