# Web的一些常用API

# window.requestAnimationFrame()

执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

WARNING

若想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

API返回一个long整数,请求ID,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给window.cancelAnimationFrame()以取消回调函数。

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}

let a = 0
setInterval(timer => {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

首先 requestAnimationFrame 自带函数节流功能,基本可以保证在 16.7 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,当然你也可以通过该函数来实现 setTimeout

TIP

利用setTimeout实现动画效果,容易出现卡顿,抖动的现象。原因是:setTimeout任务被放入异步队列,只有当主线程任务执行后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚;setTimeout的固定时间间隔不一定与屏幕刷新时间相同,会引起丢帧。

requestAnimationFrame:优势:由系统决定回调函数的执行时机。60Hz的刷新频率,那么每次刷新的间隔中会执行一次回调函数,不会引起丢帧,不会卡顿。在高频率事件(resize/scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,可以保证每个刷新时间间隔内,函数只被执行一次,这样既能保证流畅性,也能更好节省函数执行的开销。

使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。在大多数浏览器中 setTimeoutsetInterval 未被激活的tabs的定时最小延迟>=1000ms,浏览器为了优化后台tab的加载损耗(以及降低耗电量),在未被激活的tab中定时器的最小延时限制为1S(1000ms)。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的 iframe 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

实现window.requestAnimationFramepolyfill

if(!window.requestAnimationFrame) {
  window.requestAnimationFrame = (callback,element) => {
    const id = window.setTimeout(() => {
      callback()
    },1000/60)
    return id
  }
}
if(!window.cancelAnimationFrame) {
  window.cancelAnimationFrame = id => {
    clearTimeout(id)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13