一个有关 Animation 的小小分享。
浏览器中的动画类型
声明式
声明式动画,如 CSS3 Animation。 CSS3中的动画大多以 Animation 和 Transition 的形式来组成。
优点:
- 动画与应用分离,易于维护
- 计算消耗小
- GPU硬件加速
缺点:
- 受限于 cubic-bezier 曲线或 keyframes, 动画控制能力弱
- 只能满足基本的动画效果
命令式
命令式动画,如 Javascript 动画、Canvas、WebGL 动画。主要特点是显式调用动画函数。
优点:
- 可以进行逐帧控制,实现一些高级动画效果,如物理模拟
- JavsScript 动画可兼容低等级浏览器
缺点:
- JavaScript 动画性能易出现问题,Canvas 和 WebGL 动画可以使用GPU加速,性能还不错
- 耦合
- Canvas、WebGL 可以实现像素级绘制与图像分析
常见的两种 JavaScript 动画算法
基于帧的动画算法(Frame-based)
- 使用浏览器的定时器 (setInterval/setTimeout)
- 以一定时间间隔执行动画函数
- 时间间隔决定FPS,当时间间隔为(1000/60)毫秒时,FPS为60
- JS定时器存在精度问题
- 性能问题,帧间隔不可控
示例代码:
方块下移400像素,时长1秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| window.onload = function() { var div1 = document.getElementById('test1'); var top = 0; var endTime; var startTime = Date.now(); var ani1 = setInterval(function(){ if(top <= 400){ div1.style.top = top + 'px'; top += 400/60; }else { clearInterval(ani1); endTime = Date.now(); console.log(endTime - startTime); } }, 1000/60); };
|
可以看到这个动画的执行过程是:
每1/60秒执行1次,每次移动400/60像素,共移动400像素。如果帧间隔能稳定在1/60秒,那么总执行时长就是稳定1秒。
可惜帧间隔是无法稳定的,当你执行别的操作(比如点点面板显示按钮触发一下重绘)还会带来严重的性能损失。最终动画总执行时长总是略多于1秒,性能损失严重时可能达到1.5 ~ 2秒。
基于时间的动画算法(Time-based)
- “弃帧保时”
- 依然可以使用setInterval()/setTimeout()
- 根据动画已进行时间计算进度,由进度决定动画状态
- 终态处理
- 保证时间准确
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| window.onload = function() { var div1 = document.getElementById('test1'); var top = 0; var start = null; var ani1 = setInterval(function(){ if(!start) {start = Date.now();} var progress = Date.now() - start; if(progress <= 1000){ div1.style.top = progress/1000*400 + 'px'; }else { div1.style.top = '400px'; clearInterval(ani1); console.log(progress); } }, 1000/60); };
|
这个动画的执行过程:
每1/60秒执行一次,执行时先算出此时刻的动画已进行时长,折算为动画进行百分比,根据百分比决定移动位置。
由于是基于时间的动画算法,执行总时长一定是1秒(误差非常小)。
进化版:使用requestAnimationFrame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| window.onload = function() { var div1 = document.getElementById('test1'); var div2 = document.getElementById('test2'); var top = 0; var start = null; var ani1 = setInterval(function(){ if(!start) {start = Date.now();} var progress = Date.now() - start; if(progress <= 1000){ div1.style.top = progress/1000*400 + 'px'; }else { div1.style.top = '400px'; clearInterval(ani1); console.log(progress); } }, 1000/60); var start2 = null; function step(timestamp) { if (!start2) start2 = timestamp; var progress = timestamp - start2; div2.style.top = progress/1000*400 + "px"; if (progress <= 1000) { window.requestAnimationFrame(step); }else{ div2.style.top = '400px'; } } window.requestAnimationFrame(step); };
|
使用 requestAnimationFrame 浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。
对比动画可以看到,不使用 requestAnimationFrame 的动画可能存在颠簸效果,使用 requestAnimationFrame 的动画更加平滑。