requestAnimationFrame
2022年8月6日
requestAnimationFrame
它是什么?
window.requestAnimationFrame()
告诉浏览器————你希望执行一个动画,并且要求浏览器在下次重绘或者回流前
调用指定的回调函数更新动画。 设置这个 API 的目的是为了让各种网页动画效果(DOM 动画、Canvas 动画、SVG 动画、WebGl 动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
它的优点
requestAnimationFrame
对比setTimeout
、setInterval
的优势主要有两点:
requestAnimationFrame
会把每一帧所有 dom 操作集中起来,在一次重绘或回流中完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。- 在隐藏或者不可见的元素中,
requestAnimationFrame
将不会进行重绘或者回流,这就意味着更少的 cpu,gpu 和内存的使用量。比如页面隐藏就不会再触发。
使用和取消
requestAnimationFrame
要想达到类似setInterval
的效果 需要使用类似递归
的方式
为什么说是类似递归呢?递归的含义为
自己调用自己
,而该方法中间有一层requestAnimationFrame
调用,不是严格意义上的递归
下面是一个示例,包含如果取消requestAnimationFrame
let i = 0
let frameId = null // requestAnimationFrame返回的id
function addition(time) {
console.log(++i)
frameId = requestAnimationFrame(addition)
// 1自加到达200 取消requestAnimationFrame
if (i === 200) cancelAnimationFrame(frameId)
}
frameId = requestAnimationFrame(addition)
上面的代码中 requestAnimationFrame
会返回一个整数 id,传入requestAnimationFrame()
即可取消 requestAnimationFrame
的 callback 执行时,它的参数是系统传入的一个高精度时间戳(performance.now()的返回值
),单位是毫秒,表示距离网页加载的时间。
注意点
- 使用类似递归的方式,
requestAnimationFrame
的运行频率和屏幕的帧率有关,无法像setInterval
一样指定触发间隔。 - 屏幕只决定了每秒的最大运行频率,当计算量过大时,会出现掉帧的情况,无法达到屏幕帧率的运行频率。
常见的屏幕帧率和运行频率
屏幕帧率 | 运行频率 |
---|---|
60hz | 16.66ms |
90hz | 11.11ms |
120hz | 8.33ms |
运行频率(ms) = 1000 / 屏幕帧率
自定义工具类
特点
- 支持自定义触发间隔
- 支持暂停播放,也可以设置是否自动播放
- 支持动态更改 触发间隔 触发函数
工具代码
class MyRequestAnimationFrame {
_frameId // 帧id
_prevFrameTime = 0 // 上一次执行时间 ms
_recordTime = 0 // 时间记录
_frameSpeed // 触发时间间隔 ms
_callback
constructor(callback, frameSpeed, { autoPlay = true, now = false } = {}) {
this._callback = callback
this._frameSpeed = frameSpeed
if (autoPlay) this.play()
if (now) this._callback(performance.now())
}
steCallback(callback) {
this._callback = callback
}
steFrameSpeed(frameSpeed) {
this._frameSpeed = frameSpeed
}
_step(pageTime) {
if (this._frameSpeed) {
let frameTime = pageTime - this._prevFrameTime // 每次触发的间隔
this._prevFrameTime = pageTime
this._recordTime += frameTime
if (this._recordTime >= this._frameSpeed) {
this._callback(pageTime)
this._recordTime = this._recordTime % this._frameSpeed // 修正时间差
}
} else {
this._callback(pageTime)
}
this._frameId = window.requestAnimationFrame(this._step.bind(this))
}
play() {
this._prevFrameTime = performance.now()
if (!this._frameId) this._frameId = window.requestAnimationFrame(this._step.bind(this))
}
stop() {
if (this._frameId) {
cancelAnimationFrame(this._frameId)
this._frameId = null
}
}
}
使用示例
const myAnimation = new MyRequestAnimationFrame(
pageTime => {
console.log(pageTime)
},
200,
{
autoPlay: false,
now: true,
},
)
// 播放动画
myAnimation.play()
// 两秒后调整动画速度
setTimeout(() => {
myAnimation.steFrameSpeed(500)
// 再两秒后暂停动画
setTimeout(() => {
myAnimation.stop()
}, 2000)
}, 2000)
ts 类型说明
interface IMyRequestAnimationFrame {
callback: () => number // 回调函数 返回值表示距离网页加载的时间,单位ms(requestAnimationFrame传入callback的参数)
frameSpeed?: number // 触发间隔 ms
config?: {
autoPlay?: boolean // 【true】 是否自动播放
now?: boolean // 【false】 是否立即执行一次
}
}