如何实现一个动画
我们来实现一个最简单的需求,将一个元素从屏幕左边均匀地移动到屏幕右边。
下面是效果:
(1)css animation
用 css 实现是最合理也是最高效的。
1 | @keyframes move_animation1 { |
注:
transform:translateZ(0);用来开启 chrome GPU 加速,解决动画”卡顿”。
在动画中使用 transform 比 left/top 性能更好,能减少浏览器 repaint。
(2)假如用 JS 实现呢
首先想到的是 setInterval/setTimeout,原理就是利用人眼的视觉残留和电脑屏幕的刷新,让元素以连贯平滑的方式逐步改变位置,最终实现动画的效果。
常用的屏幕刷新频率为 60Hz,一些电竞屏幕则为 144Hz。我们以常用的刷新频率为例,60Hz 意味着屏幕每 1000 / 60 ≈ 16.7ms 刷新一次,所以我们设置 setInterval 的间隔为 16.7ms:
1 | const animateDiv = document.querySelector('.animate-div') |
setInterval/setTimeout 存在两个问题:
- setTimeout 的执行时间并不是确定的。在 Javascript 中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。
- 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout 只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致 setTimeout 的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象。 虽然在上述代码中我们将时间间隔设置为 16.7ms,但是还是不能完全避免丢帧的现象。
(3)requestAnimationFrame
requestAnimationFrame 与 setTimeout/setInterval 最大的区别是由系统自己的刷新机制来决定什么时候调用动画函数,开发者只需要定义好动画函数,这个函数会在浏览器重绘之前调用。
requestAnimationFrame 简介
requestAnimationFrame 接收一个回调函数作为参数,DOMHighResTimeStamp,指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。回调函数中传入时间戳作为参数,该时间戳是一个十进制数,单位毫秒,最小精度为 1ms。
1 | const animateDiv = document.querySelector('.animate-div') |
requestAnimationFrame 优势
除了精准控制调用时机以外,requestAnimationFrame 还有两大优点:
- 运行在后台标签页或者隐藏的 iframe 里时,requestAnimationFrame() 暂停调用以提升性能和电池寿命。
- 函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用 requestAnimationFrame 可保证每个刷新间隔内,函数只被执行一次。
cancelAnimationFrame
取消一个先前通过调用 window.requestAnimationFrame()方法返回的动画帧请求。
1 | const animateDiv = document.querySelector('.animate-div') |
优雅降级
requestAnimationFrame 目前还存在兼容性问题,使用 requestAnimationFrame polyfill 来进行优雅降级。
1 | if (!Date.now) |