使用 vue-virtual-collection 优化滚动性能

6,516 阅读3分钟
原文链接: zhuanlan.zhihu.com

这段时间写了一个 Vue 的滚动组件:starkwang/vue-virtual-collection,现在正式宣传一下~

类似的 vue 列表滚动组件已经有好几个了,但一直没有针对瀑布流的 vue 滚动组件,也就是类似这样的滚动组件:

点击下面可以直接看 demo 👇:

vue-virtual-collection​starkwang.github.io

欢迎任何形式的 PR、Issue ~


使用

用起来非常简单:

import Vue from 'vue'
import VirtualCollection from 'vue-virtual-collection'

Vue.use(VirtualCollection)

然后就可以在你的代码中使用了,下面是一个简单的例子:

<template>
    <div>
        <VirtualCollection :cellSizeAndPositionGetter="cellSizeAndPositionGetter" :collection="items" :height="500" :width="330">
            <div slot="cell" slot-scope="props">{{props.data}}</div>
        </VirtualCollection>
    </div>
</template>

<script>
    export default {
        data () {
            return {
                /**
                 * This will create 1000 items like:
                 * [
                 *   { data: '#0' },
                 *   { data: '#1' },
                 *   ...
                 *   { data: '#999' }
                 * ]
                 */
                items: new Array(1000).fill(0).map((_, index) => ({ data: '#' + index }))
            }
        },
        methods: {
            cellSizeAndPositionGetter(item, index) {
                // compute size and position
                return {
                    width: 100,
                    height: 150,
                    x: (index % 2) * 110,
                    y: parseInt(index / 2) * 160
                }
            }
        }
    }
</script>

实现

现在自测大概可以无卡顿支持 100W+ 的数据渲染,大部分场景下肯定是够用了。原理上就是局部渲染DOM 回收,不会渲染全部数据,而是把当前 viewport 中展示的 Cell 渲染出来,所以性能上比渲染全量数据要快太多了。

用代码简略表示一下就是:

const DisplayCells = Cells.filter(isInViewPort)

但这样实现的话会有问题,如果遍历所有数据一个一个去计算 isInViewPort,在数据很多很多的时候,性能非常差(然而现在大部分 infinite scroll 组件都是这么做的= =)。

为了高效率地 viewport 中有哪些 Cell 需要渲染,我们需要改用“块渲染”的思想。我们可以定义一个“块”为 200 * 200 的正方形,所有与这个块有重叠的 Cell 都会在这个块中记录下来

这些“块”被保存在一个 Map 中,当滚动发生时,我们只需要计算当前该展示哪些块的数据,然后去这些块中找到对应的 Cell 就可以了,而不需要去遍历所有的 Cell。

下面是一个画出来的例子:

此时,Map中记录的应该是:

{
  "0.0": [1, 2, 3, 5], // 0.0块与1,2,3,5号Cell有重叠,下同
  "0.1": [5, 3, 6, 7],
  "0.2": [7, 6, 8, 9],
  "1.0": [2, 3, 4],
  "1.1": [3, 4, 6],
  "1.2": [6, 9]
}

当我们滚动了页面,根据滚动的距离、viewPort 的宽高,可以很容易计算出当前需要渲染哪些块

比如上面这个例子中,我们需要渲染 0.0、0.1、1.0、1.1 这四个块,然后我们只需要去 Map 中找到这些块包含的 Cell,就可以高效率地渲染了,而不是去遍历所有的 Cell 暴力搜索。

这也是另一个十分流行的开源组件 react-virtualize 的核心思想,可以在下面看具体的 SectionManager 是如何实现的:

github.com/bvaughn/rea…github.com

类似的思想同样可以用到列表、表格、格栅系统上。