大量数据渲染卡顿的解决方法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
动画setInterval
或setTimeout
定时器(比如不停地更改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的应用场景
-
监听 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()
-
大量数据渲染
比如对十万条数据进行渲染,主要由以下几种方法:
-
使用定时器
//需要插入的容器 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)
-
使用 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)
-
-
监控卡顿方法
每秒中计算一次网页的 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 即可认为网页存在卡顿。