requestAnimationFrame动画性能好在哪?这是我6年前面试腾讯的一道面试题,上面ChatGPT的回答已经解释的挺清楚了,拿来应付面试基本足够,但是有些细节比较模糊,比如第二行能够在合适的时间内重复执行动画帧,提升渲染性能,什么是合适的时间,与定时器的触发时机有什么不同。定时器(setTimeout、setInterval)是在指定的时间后调用执行,而requestAnimationFrame会在下一帧渲染之前调用,可以理解为是监听帧变化事件,通过性能面板可以观察出requestAnimationFrame与定时器的区别。
requestAnimationFrame
定时器
可以发现两者区别在于
回调触发与页面渲染之间的间隔,requestAnimationFrame与帧变化是紧密联系的,回调触发与页面渲染之间是几乎是连续执行的,而定时器与页面渲染之间会有间隔,这样子带来的性能问题是什么?理论上定时器也可以设置每17ms执行一次,这样也能保证1s内接近60次渲染,从而确保动画流畅,为了验证两者的区别,使用requestAnimationFrame和定时器setInterval分别实现水平移动的动画效果。
上面的绿色长条是
requestAnimationFrame动画,下面的红色长条是setInterval实现的定时器动画,仔细观察,定时器动画在移动的过程中会有一点抖动,requestAnimationFrame动画移动的过程相对比较平稳,而定时器动画肉眼出现抖动,说明1s内渲染次数小于60次,也就是帧与帧之间大于17ms,为了进一步验证,我们在代码中记录了动画耗时、卡顿次数、最长卡顿间隔,这三个指标数值越大说明动画性能越差。
// 开始动画按钮的点击事件
function start() {
window.startTime = Date.now()
animationByRaf()
animationBySetInterval()
}
// requestAnimationFrame动画
function animationByRaf(val = 1, now = Date.now(), times = 0, maxPeriod = 0){
requestAnimationFrame(() => {
if(Date.now() - now > 20) {
// 帧间隔时长大于20ms,认为卡顿
// 收集卡顿次数
times++
// 收集最大卡顿间隔
maxPeriod = Math.max(maxPeriod, Date.now() - now)
}
// 更新动画的开始时间,与下一帧动画的Date.now()相减计算出帧间隔时长
now = Date.now()
// 水平移动动画,每帧移动1px
document.querySelector(`#requestAnimationFrameSelector`).style.left = `${val}px`;
if(val >= 500) {
// 移动500px后动画停止,打印日志
console.log(`requestAnimationFrame动画耗时${Math.floor(Date.now() - startTime)},卡顿次数${times},最长卡顿${maxPeriod}ms`)
return
}
// 递归调用实现动画
animationByRaf(val+1, now, times, maxPeriod)
})
}
// setInterval动画
function animationBySetInterval(){
let val = 1
let now = Date.now()
let times = 0
let maxPeriod =0
const interval = setInterval(() => {
if(Date.now() - now > 20) {
// 帧间隔大于20ms,认为卡顿
times++
maxPeriod = Math.max(maxPeriod, Date.now() - now)
}
now = Date.now()
document.querySelector(`#setIntervalSelector`).style.left = `${val++}px`
if(val > 500) {
// 动画结束,打印日志
console.log(`setInterval动画耗时${Date.now() - startTime},卡顿次数${times},最长卡顿${maxPeriod}ms`)
clearInterval(interval)
return
}
}, 17)
}
上图是动画结束后的日志,执行500次动画,
requestAnimationFrame只有一次卡顿,而setInterval却有53次,理论上setInterval动画应该在17*500=8350ms的时候结束,但实际却耗时8502ms,延迟了172ms。
通过性能面板也能发现setInterval某些帧的触发时机并不稳定,导致动画卡顿,原因就是定时器设置的延迟时间,不是回调函数执行的延迟时间,而是加入事件队列的延迟时间,加入事件队列后需要等待主线程空闲,再将事件队列中的任务加入执行栈执行,由于定时器不能稳定触发,所以无法保证每一帧能够准时渲染,而requestAnimationFrame与帧变化紧密联系,是在每一帧渲染前触发,所以能够在合适的时间内重复执行动画帧,提升渲染性能。