本文已参与「新人创作礼」活动,一起开启掘金创作之路。 点击查看活动详情
写在前面
用setInterval
实现动画有很多缺点,首先是精度问题,如果有大量的dom
操作,动画间隔是不准确的。
requestAnimationFrame
会将所有操作集中起来,跟随浏览器的刷新频率(60帧)一次完成,会区分隐藏不可见的元素,性能更好。
但是requestAnimationFrame
和setInterval
对时间的处理方式是不一样的,setInterval
直接传递一个时间间隔(ms单位),以此执行回调函数
setInterval(() => {
//
}, 1000)
而requestAnimationFrame
会传递一个时间戳timestamp
,表示自页面加载以来,回调函数被执行的时刻,一直增大。
13.502
30.098
46.995
63.45 ...
requestAnimationFrame时间间隔处理
通过时间戳可以得到时间间隔从而进行处理,也就是说每个动画还需要一个变量来记录时间来和timestamp
进行对比。
let time = 0;
function A(t) {
requestAnimationFrame(A)
console.log("距离上一次执行的时间间隔", t - time)
time = t
}
A()
// 距离上一次执行的时间间隔 16.64300000000003
// 距离上一次执行的时间间隔 16.663000000000466
// 距离上一次执行的时间间隔 16.87199999999939
// 距离上一次执行的时间间隔 16.472000000000662
// 距离上一次执行的时间间隔 16.639999999999418
// 距离上一次执行的时间间隔 16.649000000000342
// 距离上一次执行的时间间隔 16.842999999999847
// 距离上一次执行的时间间隔 16.509000000000015
// 距离上一次执行的时间间隔 16.61999999999989
// 距离上一次执行的时间间隔 16.646999999999935
// 距离上一次执行的时间间隔 16.789999999999964
// 距离上一次执行的时间间隔 16.605999999999767
// 距离上一次执行的时间间隔 17.220000000000255
// 距离上一次执行的时间间隔 16.038000000000466
// 距离上一次执行的时间间隔 16.787999999999556
// 距离上一次执行的时间间隔 16.576000000000022
// 距离上一次执行的时间间隔 16.699999999999818
// 距离上一次执行的时间间隔 16.634000000000015
// 距离上一次执行的时间间隔 16.72400000000016
// 距离上一次执行的时间间隔 16.775000000000546
可以看出,requestAnimationFrame
需要自行控制执行的时间间隔,假如要实现一个秒计时器,通过计算执行时间差来判断是否执行
let interval = 1000
let lastTime = 0
function SecondTimer(t) {
requestAnimationFrame(SecondTimer)
if(t - lastTime >= interval) {
console.log("1s到了")
lastTime = t
}
}
SecondTimer()
封装定时器
我们将timestamp
的处理逻辑抽取出来,调用的时候只需要考虑时间间隔和回调,这样使用方式就和setInterval
一样了
class Timer {
constructor(fn, interval) {
this.interval = interval
this.fn = fn
this.lastTime = 0
this.loop(0)
}
loop(timestamp){
this.timer = requestAnimationFrame(Timer.prototype.loop.bind(this))
if(timestamp - this.lastTime > this.interval) {
this.lastTime = timestamp;
typeof this.fn == "function" && this.fn()
}
}
clear() {
cancelAnimationFrame(this.timer)
this.timer = null
}
}
// 调用
let t = new Timer(() => {
console.log("timer 1s到了")
}, 1000)
let t2 = setInterval(() => {
console.log("setinterval 1s到了")
}, 1000)
中间我把浏览器页面切走了,过几秒再切回来,可以看到,timer是没有执行的,而setInterval是一直在执行的,由于他俩执行机制的不一样,所以最好不要混用。
实现元素动画
前面我们实现了一个简单的canvas库 100行代码写个canvas库 ,里面舞台的刷新就是用的requestAnimationFrame
,默认以16.67ms
的频率更新舞台
render() {
requestAnimationFrame(Stage.prototype.render.bind(this));
this.clear();
}
但是很明显,每个单独的元素动画的频率肯定是不一样的,比如小人500ms
动一次,而小车100ms
就要动一次了,所以在处理元素动画的时候,就可以用上封装的这个定时器了
class Person {
//
move() {
this.timer = new Timer(() => {
this.run()
}, 500)
}
}
class Car {
//
move() {
this.timer = new Timer(() => {
this.run()
}, 200)
}
}