跳至主要內容

raf 定时器

wzCoding大约 3 分钟综合知识raf 定时器

raf 定时器

requestAnimationFrame

requestAnimationFrame() 是用于在浏览器下一次 重绘 之前请求执行一个动画帧的方法。

requestAnimationFrame() 接收一个回调函数作为参数,回调函数会在浏览器下一次重绘之前被调用。它返回一个整数,代表 requestAnimationFrame() 在任务队列中的 ID 标识,可以使用这个 ID 作为参数调用 window.cancelAnimationFrame() 方法来取消回调函数的执行。回调函数中会被传入一个 timestamp (时间戳) 参数,表示当前开始执行回调的时间。

requestAnimationFrame() 是一次性的,就是说它只会在下一帧中执行传入的回调,不会重复执行,如果想要连续的执行回调,就需要在每一帧中使用 requestAnimationFrame() 方法。

function animate() {
    // 动画代码
    console.log('动画帧');

    // 在下一次重绘之前再次调用 animate
    requestAnimationFrame(animate);
}

// 启动动画
requestAnimationFrame(animate);
ℹ️提示

requestAnimationFrame() 的执行次数通常是每秒 60 次,即大约 16.6ms 执行一次。这个执行次数通常与浏览器屏幕刷新次数相匹配。它相比于 setTimeout() 定时器的优点是:

  • 更高的帧率和更流畅的动画requestAnimationFrame() 会在每一帧动画开始时被调用,而且它会自动调整以匹配浏览器的刷新率。这意味着动画会以更高的帧率(通常是 60 帧每秒)运行,从而创建更流畅的动画。
  • 更好的性能requestAnimationFrame() 只在浏览器准备好绘制新的帧时才会运行,这可以避免不必要的计算和重绘,从而提高性能。
  • 自动的时间间隔requestAnimationFrame() 会自动计算出下一帧的最佳时间间隔,你不需要手动设置时间间隔,这可以避免因为间隔设置不当导致的动画卡顿。

定时器

在简单了解了 requestAnimationFrame() 的基本用法以及特点后,我们可以利用它的特点来模拟实现 setTimeout()setInterval() 的功能。

以下是具体实现代码:

class Timer {
    constructor() {
        //记录定时器数据
        this.record = {}
        this.minInterval = 1000 / 60
    }
    
    loop(type, callback, delay) {
        if (!['interval', 'timeout'].includes(type)) return;
        if (!this.record[type]) this.record[type] = {}

        //记录开始时间
        let startTime = Date.now();
        const record = Symbol(type);

        //循环执行计算时间间隔
        const loopStart = () => {

            this.setRecord(record, type, loopStart);
            //获取当前时间
            let currentTime = Date.now();
     
            if (currentTime - startTime >= delay) {
        
                if (type == "timeout") {

                    //此条件执行timeout
                    callback && callback(currentTime);
                    this.clear({ record, type })

                } else if (type == "interval") {

                    //此条件执行interval
                    startTime = Date.now();
                    callback && callback(currentTime);

                }

            }
        }
        
        //定时! 启动!
        this.setRecord(record, type, loopStart);

        return { record, type }

    }
    
    //记录 raf 的id
    setRecord(record, type, callback) {
        const id = window.requestAnimationFrame(callback)
        this.record[type][record] = id;
    }
    //模拟 setInterval 方法
    interval(callback, delay = 1000) {
        delay = delay < this.minInterval ? this.minInterval : delay;
        return this.loop("interval", callback, delay);
    }
    //模拟 setTimeout 方法
    timeout(callback, delay = 1000) {
        delay = delay < this.minInterval ? this.minInterval : delay;
        return this.loop("timeout", callback, delay);
    }
    //清除定时器方法
    clear(timer) {
        if(!timer) return
        const { record, type } = timer
        window.cancelAnimationFrame(this.record[type][record]);
        this.record[type][record] = null
    }
}

//使用
const timer = new Timer()
let count = 0
let id1 = timer.interval((stamp)=>{
   console.log(stamp)
   count++
   if(count > 5){
      timer.clear(id1)
      console.log('clear interval')
   }
},1000)

let id2 = timer.timeout((stamp)=>{
    console.log('hello')
    timer.clear(id2)
    console.log('clear timeout')
},3000)
ℹ️提示

更多 requestAnimationFrame() 的相关信息请看 window.requestAnimationFrame()