如何实现渲染几万条数据并且页面不卡顿?requestAnimationFrame

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现渲染几万条数据并且页面不卡顿?requestAnimationFrame相关的知识,希望对你有一定的参考价值。

参考技术A

链接:https://blog.csdn.net/m0_37631322/article/details/89678875

window.requestAnimationFrame API在动画方面有非常大的应用价值。

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行;

注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须 再次 调用window.requestAnimationFrame()

当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是 每秒60次 ,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示由RequestAnimationFrame()排队的回调开始触发的时间。指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

性能优化:虚拟列表,如何渲染10万条数据的dom,页面同时不卡顿

最近做的一个需求,当列表大概有2万条数据,又不让做成分页,如果页面直接渲染2万条数据,在一些低配电脑上可能会照成页面卡死,基于这个需求,我们来手写一个虚拟列表

思路

  1. 列表中固定只显示少量的数据,比如60条
  2. 在列表滚动的时候不断的去插入删除dom
  3. startIndex、endIndex,不断的改变这个值来获取最新的显示列表
  4. paddingTop、paddingBottom撑开容器的滚动区域

首先看一下当直接插入2万条列表时,页面的性能
技术图片
可以看到火焰图中已经有了红色的部分了,dom渲染也耗时也有1s多

再来看一下当使用虚拟列表时页面的性能
技术图片
从火焰图中可以看出,火焰图中一篇绿油油的,这就证明,通过虚拟列表来进行渲染使页面性能得到了极大的提升

简单的虚拟列表demo实现

我们假设有一个容器,高度为600px,列表项每个高度为30px,那么根据列表的length我们就可以计算出滚动容器的总高度,也可以知道显示60条数据的高度,我们此时可以给容器加一个paddingBottom,来撑开容器,来模拟页面应该滚动的高度

this.paddingBottom = this.allHeight - this.scrollList.length * 30

容器同时还需要paddingTop用做当容器滚动顶部数据移除后撑起scrollTop

最后我们需要监听容器的滚动事件来不断的修改paddingTop、paddingBottom、startIndex、endIndex

最终效果
技术图片

最后附上所有代码

<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .container 
            width: 300px;
            height: 600px;
            overflow: auto;
            border: 1px solid;
            margin: 100px auto;
        
        .item 
            height: 29px;
            line-height: 30px;
            border-bottom: 1px solid #aaa;
            padding-left: 20px;
        
    </style>
</head>
<body>
<div id="app">
    <button @click="add">增加</button>
    <div class="container" ref="container">
        <div class="scroll-wrapper" :style="style">
            <div v-for="(item, index) in scrollList" :key="index" class="item">item</div>
        </div>
    </div>
</div>
<script src="./vue.js"></script>
<script>
    new Vue(
        el: '#app',
        data: 
            list: [
                '测试数据'
            ],
            startIndex: 0,
            endIndex: 60,
            paddingTop: 0,
            paddingBottom: 0,
            allHeight: 0
        ,
        computed: 
            scrollList() 
                return this.list.slice(this.startIndex, this.endIndex)
            ,
            style() 
                return 
                    paddingTop: this.paddingTop + 'px',
                    paddingBottom: this.paddingBottom + 'px'
                
            
        ,
        watch: 
            list(val) 
                const valLen = val.length
                this.allHeight = valLen * 30
                this.paddingBottom = this.allHeight - this.scrollList.length * 30
            
        ,
        mounted() 
            const container = this.$refs.container
            container.addEventListener('scroll', () => 
                const top = container.scrollTop
                this.startIndex = Math.floor(top / 30)
                this.endIndex = this.startIndex + 60

                this.paddingTop = top
                if (this.endIndex >= this.list.length - 1) 
                    this.paddingBottom = 0
                    return
                
                this.paddingBottom = this.allHeight - 600 - top
            )
        ,
        methods: 
            add() 
                let arr = new Array(50000).fill(0)
                arr = arr.map((item, index) => 
                    return index
                )
                this.list = [
                    ...this.list,
                    ...arr
                ]
            
        
    )
</script>
</body>
</html>

以上是关于如何实现渲染几万条数据并且页面不卡顿?requestAnimationFrame的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript如何一次性展示几万条数据

JavaScript如何一次性展示几万条数据

性能优化:虚拟列表,如何渲染10万条数据的dom,页面同时不卡顿

antd多选框选项太多数据量太大出现页面卡顿怎么解决

PHPExecl导出大量数据卡顿问题解决(Laravel实现)

web技术分享| 虚拟列表实现