1、什么是requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
简单来说,requestAnimationFrame 就是约定在下一次浏览器刷新前执行的一个定时器。类似setTimeout
requestAnimationFrame特点
- requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,如果系统绘制率是 60Hz,那么回调函数就会16.7ms再 被执行一次,如果绘制频率是75Hz,那么这个间隔时间就变成了 1000/75=13.3ms。换句话说就是,requestAnimationFrame的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
- 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
- requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
//注册
var id= window.requestAnimationFrame(callback);
//取消
window.cancelAnimationFrame(id)
var btn = document.getElementById('btn')
var btnLength = 200
var raf
var btnFn = function(){
btnLength++
btn.style.width = btnLength + 'px'
if (btnLength === 1000) {
cancelAnimationFrame(raf)
}else{
raf = requestAnimationFrame(btnFn)
}
}
raf = requestAnimationFrame(btnFn)
使用 requestAnimationFrame 替换 setTimeout 和 setInterva 可提升web性能。
setTimeout 和 setInterva 实现动画的缺点
- 时间不精确,通常实际执行时间都要比其设定的时间晚一些。
- 实现的动画在某些低端机上会出现卡顿、抖动的现象。(不同设备的屏幕刷新频率不同,而 setTimeout 和 setInterva 只能设置一个固定的时间间隔,这个时间和屏幕的刷新时间不同时,就会出现掉帧,而导致页面卡顿、抖动)
requestAnimationFrame 实现动画的优势
requestAnimationFrame 采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
使用场景:
- 动画效果
当需要实现平滑的动画效果时,requestAnimationFrame是一个理想的选择。通过使用requestAnimationFrame,可以在每个浏览器刷新帧之前更新动画的状态,并在合适的时机进行渲染。这样可以确保动画的流畅性,并减少不必要的渲染操作。例如,实现平滑的过渡效果、动态的图表展示等都可以使用requestAnimationFrame来实现。
- 平滑滚动
<button onclick="scrollToTop()">回到顶部</button>
function scrollToTop() {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, c - c / 8);
}
}
- 大量数据的渲染
比如一百万条数据的渲染,使用 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)