使用 requestAnimationFrame 实现定时器,解决 setInterval 执行次数丢失问题

来看这样一个场景:使用 setInterval 定时器倒计时,突然来了一个长达三秒的任务,定时器会有一次不准,两次丢失回调,导致少两次计算时间。

// 在控制台上输入下面四行
var second = 0
setInterval(function() {
  console.log(`setInterval ${++second}`, new Date().getTime())
}, 1000)

// 几秒之后输入下面代码
function sleep(ms) {
  const end = new Date().getTime() + ms
  console.log('sleep start')
  while (new Date().getTime() < end) {}
  console.log('sleep end')
}
sleep(3000)
使用 requestAnimationFrame 实现定时器,解决 setInterval 执行次数丢失问题

如图所示,少两次回调的执行。


requestAnimationFrame 实现定时器

requestAnimationFrame 传入一个回调函数,该回调函数会在浏览器下一次重绘之前执行,详情查看MDN文档 window.requestAnimationFrame

/**
 * 设置精度定时器
 * @param {function} 回调函数
 * @param {number}   延迟时间
 * @return {number}  定时器ID
 */
function setIntervalPrecision(callback, delay) {
  // 生成并记录定时器ID
  let obj = window.interValPrecisionObj || (window.interValPrecisionObj = { num: 0 })
  obj.num++
  obj['n' + obj.num] = true
  var intervalId = obj.num
  // 开始时间
  var startTime = +new Date()
  // 已执行次数
  var count = 0
  // 延迟时间
  delay = delay || 0


  ;(function loop() {
    // 定时器被清除,则终止
    if (!obj['n' + intervalId]) return

    // 满足条件执行回调
    if (+new Date() > startTime + delay * (count + 1)) {
      count++
      callback(count)
    }

    requestAnimationFrame(loop)
  })()

  return intervalId
}

/**
 * 清除精度定时器
 * @param {number} 定时器ID
 */
function clearIntervalPrecision(intervalId) {
  if (window.interValPrecisionObj) {
    delete window.interValPrecisionObj['n' + intervalId]
  }
}

测试

// 在控制台上输入下面四行
setIntervalPrecision(function(val) {
  console.log(`setIntervalPrecision ${val}`, new Date().getTime())
}, 1000)

// 几秒之后输入下面代码
function sleep(ms) {
  const end = new Date().getTime() + ms
  console.log('sleep start')
  while (new Date().getTime() < end) {}
  console.log('sleep end')
}
sleep(3000)
使用 requestAnimationFrame 实现定时器,解决 setInterval 执行次数丢失问题

任务阻塞结束后,会瞬间执行阻塞期间需要执行次数的回调,虽然倒计时页面会卡三秒(js特性),但实际剩余秒数不会出错。


whosmeya.com

上一篇:js清除所有定时器(包括未知定时器)


下一篇:js用setTimeout模拟setInterval的好处_优点