requestAnimationFrame方法允许我们在下一帧开始时调用指定的函数。但是很多人可能不知道,直接在requestAnimationFrame的回调函数中绘制动画无论发生什么情况都是有问题的。问题是什么?要理解这个问题,首先要理解requestAnimationFrame的一个知识点。requestAnimationFrame不管理回调函数的知识点是requestAnimationFrame不管理回调函数。这在w3c中有明确说明。另请注意,使用相同的回调多次调用requestAnimationFrame(在调用回调和清除列表之前)将导致列表中的多个条目具有相同的回调,因此将导致该回调被多次调用动画帧。—w3c在回调执行前多次调用同一个回调函数的requestAnimationFrame,会导致回调在同一帧执行多次。我们可以通过一个简单的例子来模拟同一帧多次调用requestAnimationFrame的场景:constanimation=timestamp=>console.log('animationcalledat',timestamp)window.requestAnimationFrame(animation)window.requestAnimationFrame(animation)//animationcalledat320.7559999991645//animationcalledat320.7559999991645我们连续两次调用requestAnimationFrame,模拟在同一帧调用两次requestAnimationFrame。例子中的时间戳是通过requestAnimationFrame传递给回调函数的,表示触发回调队列的时间。从输出可以看出动画函数在同一帧执行了两次,也就是动画绘制了两次。但是在同一帧画两次动画显然是多余的,相当于画了一张图,然后在图上画了同一张图。Question那么在什么场景下,requestAnimationFrame会在一帧中被调用多次呢?熟悉事件的同学应该第一时间想到mousemove、scroll这样的事件。所以我们前面提到的问题是:因为requestAnimationFrame没有管理回调函数,在滚动、触摸等触发频率高的事件回调中,如果调用requestAnimationFrame再绘制动画,可能会造成冗余计算和绘制。例如:window.addEventListener('scroll',e=>{window.requestAnimationFrame(timestamp=>{animation(timestamp)})})上面代码中,scroll事件可能在一帧内触发多次,所以动画函数可能会在一帧内重复绘制,造成不必要的计算和渲染。解决方案对于这种高频事件,一般的解决方案是使用节流功能。但是这里使用节流功能并不能完美的解决问题。因为节流函数是通过时间来管理队列的,requestAnimationFrame的触发时间是不固定的,在高刷新率的显示器上时间会小于16.67ms,如果页面推送到后台,时间可能会大于16.67ms。完美的方案是通过requestAnimationFrame来管理队列。思路是保证requestAnimationFrame的队列中只有一个回调函数。原理图代码如下:constonScroll=e=>{if(scheduledAnimationFrame){return}scheduledAnimationFrame=truewindow.requestAnimationFrame(timestamp=>{scheduledAnimationFrame=falseanimation(timestamp)})}window.addEventListener('scroll',onScroll)但是每次都要写这么一堆代码也有点麻烦。所以我开源了raf-plus库来解决这个问题,有需要的同学可以使用~结论requestAnimationFrame没有管理回调函数队列,滚动、触摸等高触发频率事件的回调可能会在里面触发重复同一帧。所以使用requestAnimationFrame的正确姿势是管理好requestAnimationFrame在同一帧可能被多次调用时的回调函数,防止动画重复绘制。
