大量数据渲染卡顿的解决方法

大量数据渲染卡顿的解决方法1——requestAnimationFrame

关于页面帧数、渲染顺序的问题

import { ref } from 'vue';

// 延时加载js
// 使用方法
// 1、导入import { useDefer } from './defer';
// 2、定义let defer = useDefer(非必填);
// 3、使用<div v-if="defer(10)">数值越大加载顺序越靠后
export function useDefer(maxFrameCount = 1000) {
    const frameCount = ref(0);
    const refreshFrameCount = () => {
        // 核心就是这里,页面每帧执行一次,只有当帧数大于阈值时,才会执行,否则就继续循环
        requestAnimationFrame(() => {
            frameCount.value++;
            if (frameCount.value >= maxFrameCount) {
                refreshFrameCount();
            }
        })
    };
    refreshFrameCount();
    return function (showInFrameCount) {
        return frameCount.value >= showInFrameCount;
    }
}

requestAnimationFrame 即请求动画帧,它是一个浏览器的宏任务。简单的说,这个api主要是用来做动画的。

1. 前端动画的方案

  • css动画
    • transition:过渡动画
    • animation:直接动画(搭配@keyframes
  • js动画
    • setIntervalsetTimeout定时器(比如不停地更改dom元素的位置,使其运动起来)
    • canvas动画,搭配js中的定时器去运动起来(canvas只是一个画笔,然后我们通过定时器会使用这个画笔去画画-动画)
    • requestAnimationFrame动画(js动画中的较好方案)

这里有一个示例 可以清楚的看出定时器动画和请求帧动画的区别

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>requestAnimationFrame_yyds</title>
	<style>
		body {
			box-sizing: border-box;
			background-color: #ccc;
		}

		.box1,
		.box2 {
			position: absolute;
			width: 160px;
			height: 160px;
			line-height: 160px;
			text-align: center;
			color: #fff;
			font-size: 13px;
		}

		.box1 {
			top: 40px;
			background: red;
		}

		.box2 {
			top: 210px;
			background: green;
		}
	</style>
</head>

<body>
	<button class="btn">👉 let's go!</button>
	<div class="box1">定时器动画</div>
	<div class="box2">请求动画帧</div>
	<script>
		// 动画思路:不断修改dom元素的left值,使其运动起来(动画)
		let box1 = document.querySelector('.box1')
		let box2 = document.querySelector('.box2')

		// setInterval定时器方式
		function setIntervalFn() {
			let timer = null
			box1.style.left = '0px'
			timer = setInterval(() => {
				let leftVal = parseInt(box1.style.left)
				if (leftVal >= 720) {
					clearInterval(timer)
				} else {
					box1.style.left = leftVal + 1 + 'px'
				}
			}, 17)
		}

		// requestAnimationFrame请求动画帧方式
		function requestAnimationFrameFn() {
			let timer = null // 可注掉
			box2.style.left = '0px'
			function callbackFn() {
				let leftVal = parseInt(box2.style.left)
				if (leftVal >= 720) {
					// 不再继续递归调用即可,就不会继续执行了,下面这个加不加都无所谓,因为影响不到
					// cancelAnimationFrame取消请求动画帧,用的极少,看下,下文中的回到顶部组件
					// 大家会发现并没有使用到这个api(这样写只是和clearInterval做一个对比)
					// 毕竟,正常情况下,requestAnimationFrame会自动停下来
					cancelAnimationFrame(timer) // 可注掉(很少用到)
				} else {
					box2.style.left = leftVal + 1 + 'px'
					window.requestAnimationFrame(callbackFn)
				}
			}
			window.requestAnimationFrame(callbackFn)
		}

		// 动画绑定
		let btn = document.querySelector('.btn')
		btn.addEventListener('click', () => {
			setIntervalFn()
			requestAnimationFrameFn()
		})
	</script>
</body>

</html>

2.requestAnimationFrame的应用场景

  1. 监听 scroll 函数

    页面滚动事件(scroll)的监听函数,就很适合用这个 api,推迟到下一次重新渲染。

$(window).on('scroll', function () {
  window.requestAnimationFrame(scrollHandler)
})

平滑滚动到页面顶部

const scrollToTop = () => { 
  const c = document.documentElement.scrollTop || document.body.scrollTop 
  if (c > 0) {  
    window.requestAnimationFrame(scrollToTop) 
    window.scrollTo(0, c - c / 8) 
  }
}

scrollToTop()
  1. 大量数据渲染

    比如对十万条数据进行渲染,主要由以下几种方法:

    1. 使用定时器

      //需要插入的容器
      let ul = document.getElementById('container')
      // 插入十万条数据
      let total = 100000
      // 一次插入 20 条
      let once = 20
      //总页数
      let page = total / once
      //每条记录的索引
      let index = 0
      //循环加载数据
      function loop(curTotal, curIndex) { 
        if (curTotal <= 0) {  
          return false 
        }  
        //每页多少条
        let pageCount = Math.min(curTotal, once) 
        setTimeout(() => {  
          for (let i = 0; i < pageCount; i++) { 
            let li = document.createElement('li')    
            li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)    
            ul.appendChild(li)  
          }  
          loop(curTotal - pageCount, curIndex + pageCount) 
        }, 0)
      }
      loop(total, index)
      
    2. 使用 requestAnimationFrame

      //需要插入的容器
      let ul = document.getElementById('container')
      // 插入十万条数据
      let total = 100000
      // 一次插入 20 条
      let once = 20
      //总页数
      let page = total / once
      //每条记录的索引
      let index = 0
      //循环加载数据
      function loop(curTotal, curIndex) {
        if (curTotal <= 0) {
          return false
        }
        //每页多少条
        let pageCount = Math.min(curTotal, once)
        window.requestAnimationFrame(function () {
          for (let i = 0; i < pageCount; i++) {
            let li = document.createElement('li')
            li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
            ul.appendChild(li)
          }
          loop(curTotal - pageCount, curIndex + pageCount)
        })
      }
      loop(total, index)
      
  2. 监控卡顿方法
    每秒中计算一次网页的 FPS,获得一列数据,然后分析。通俗地解释就是,通过 requestAnimationFrame API 来定时执行一些 JS 代码,如果浏览器卡顿,无法很好地保证渲染的频率,1s 中 frame 无法达到 60 帧,即可间接地反映浏览器的渲染帧率。

    var lastTime = performance.now()
    var frame = 0
    var lastFameTime = performance.now()
    var loop = function (time) {
      var now = performance.now()
      var fs = now - lastFameTime
      lastFameTime = now
      var fps = Math.round(1000 / fs)
      frame++
      if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime))
        frame = 0
        lastTime = now
      }
      window.requestAnimationFrame(loop)
    }
    

    我们可以定义一些边界值,比如连续出现 3 个低于 20 的 FPS 即可认为网页存在卡顿。

  • 49
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值