前端动画原理与实现
参考月影大大同名分享内容总结了此份博文。经过一番归纳整理后熟悉了很多,整理于此,以供参考。
动画的本质(动画是关于时间的函数)
- 定时器 改变元素的属性
- 浏览器/GPU 的渲染过程
动画的种类
- JavaScript 动画
- DOM 动画
- Canvas 动画
- CSS3 动画
- transition
- animation
- SVG 动画
动画的控制
例 1-1 简单动画
1 2 3 4 5 6 7 | var deg = 0; block.addEventListener('click', function(){ var self = this; setInterval(function(){ self.style.transform = 'rotate(' + (deg++) +'deg)'; }); }); |
弊端:动画应该用当前时间和开始时间的差值来计算当前动画元素的位置而不是像上例那样用属性增量来实现动画,因为每隔多少毫秒增加一点属性的话,浏览器timer并不能保证在那个准确的时间点执行,而且那样做也很难精确控制动画的各个物理量。(造成丢帧)
建议使用 requestAnimationFrame
定义绘制每一帧前的工作。 requestAnimationFrame(callback)
方法可以自动调节频率。callback 工作太多时无法在一帧内完成,会自动降低为 30 FPS, 虽然频率会降低但比丢帧好。
渲染一帧目标( 1 / 60 FPS = 16 ms-)
具体表格体现如下:
时间 | 增量 | |
---|---|---|
幅度控制 | ✓ | ✓ |
时间控制 | ✓ | × |
速度控制 | ✓ | ✓ |
不会延迟 | ✓ | × |
不会掉帧 | × | ✓ |
例 1-2 改进简单动画
1 2 3 4 5 6 7 8 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(); setInterval(function(){ var T = 1000; var p = (Date.now() - startTime) / T; self.style.transform = 'rotate(' + (360 * p) +'deg)'; }); }); |
直线动画
例 2-1 匀速运动
问:让滑块在 2 秒内向右匀速移动 200px
1 2 3 4 5 6 7 8 9 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), distance = 200, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); self.style.transform = 'translateX(' + (distance * p) +'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
基本公式:
时间:$t = T⋅p$
位移:$S_t = S⋅p = v⋅t$
速度:$v = \frac{S⋅p}{t} = \frac{S}{T}$
加速度:$a = 0$
例 2-2 匀加速运动
问:让滑块在 2 秒内向右匀加速移动 200px,速度从 0 开始
1 2 3 4 5 6 7 8 9 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), distance = 200, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); self.style.transform = 'translateX(' + (distance * p * p) +'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
时间:$t = T⋅p$
位移:$S_t = S⋅p^2 = t^2 ⋅\frac{S}{T^2}$
速度:$v = \frac{2S}{T^2}⋅t = \frac{2Sp}{T}$
加速度:$a = \frac{2S}{T^2}$
例 2-3 匀减速运动
让滑块在 2 秒内向右匀减速移动 200px,速度从最大减为 0
1 2 3 4 5 6 7 8 9 10 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), distance = 200, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); self.style.transform = 'translateX(' + (distance * p * (2-p)) +'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
时间:$t = T⋅p$
位移:$S_t = \frac{2S}{T}⋅t - t^2 ⋅\frac{S}{T^2} = Sp(2-p)$
速度:$v = \frac{2S(1-p)}{T} = \frac{2S}{T} - \frac{2S}{T^2}⋅t$
加速度:$a = - \frac{2S}{T^2}$
平面上的运动
例3-1 斜线运动
让滑块沿斜线运动 2s,x、y 移动距离都是 200px
1 2 3 4 5 6 7 8 9 10 11 12 13 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), distance = 200, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); var tx = distance * p; var ty = tx; self.style.transform = 'translate(' + tx + 'px' + ',' + ty +'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
demo (就是 x 轴 y 轴各自方向的匀速运动)
例3-2 抛物线运动
让滑块做抛物线运动(平抛)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), disX = 200, disY = 200, T = 1000 * Math.sqrt(2 * disY / 98); //假设10px是1米,disY = 20米 requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); var tx = disX * p; var ty = disY * p * p; self.style.transform = 'translate(' + tx + 'px' + ',' + ty +'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
demo ($y = ax^2 + bx + c$)
例3-3 简谐振动
让滑块做简谐摆运动(简谐振动)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), distance = 100, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); var tx = distance * Math.sin(2 * Math.PI * p); self.style.transform = 'translateX(' + tx + 'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
demo (正弦曲线)
例3-4 正弦曲线
让滑块沿正弦曲线运动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), distance = 100, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); var ty = distance * Math.sin(2 * Math.PI * p); var tx = 2 * distance * p; self.style.transform = 'translate(' + tx + 'px,' + ty + 'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
例3-5 圆周运动
让滑块做圆周运动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), r = 100, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); var rotation = 360 * p; self.style.transformOrigin = '0 ' + r + 'px'; self.style.transform = 'rotate(' + rotation + 'deg)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
例3-6 圆周运动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | block.addEventListener('click', function(){ var self = this, startTime = Date.now(), r = 100, T = 2000; requestAnimationFrame(function step(){ var p = Math.min(1.0, (Date.now() - startTime) / T); var tx = -r * Math.sin(2 * Math.PI * p), ty = -r * Math.cos(2 * Math.PI * p); self.style.transform = 'translate(' + tx + 'px,' + ty + 'px)'; if(p < 1.0) requestAnimationFrame(step); }); }); |
圆的轨迹方程
- 代数方程: $x^2 + y^2 = r^2$
- 参数方程(更优):$x = R⋅cos(ωt)$ $y = R⋅sin(ωt)$
- 极坐标方程(更优):$ρ = R$
动画的要素分析
动画时长:$T$
动画进程:$p = \frac{t}{T}(p ∈ [0,1])$
easing:$e = f(p)$
动画方程:$[x,y] = G(e)$
动画开始、进行中、结束:onStart、onProgress、onFinished
动画的简易封装
对动画进行简易封装
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 35 36 37 38 39 40 41 42 43 | /** * [Animator description] * @param {[type]} duration [时间] * @param {[type]} progress [运动公式] * @param {[type]} easing [缓动函数] */ function Animator(duration, progress, easing){ this.duration = duration; this.progress = progress; this.easing = easing || function(p){return p}; } Animator.prototype = { start: function(finished){ var startTime = Date.now(); var duration = this.duration, self = this; requestAnimationFrame(function step(){ var p = (Date.now() - startTime) / duration;//当前时间 var next = true; if(p < 1.0){ self.progress(self.easing(p), p);//传p执行动画 }else{ if(typeof finished === 'function'){ //结束判断 next = finished() === false; }else{ next = finished === false; } if(!next){ self.progress(self.easing(1.0), 1.0);//动画中断 }else{ startTime += duration;//循环时间 self.progress(self.easing(p), p); } } if(next) requestAnimationFrame(step);//反复执行 }); } }; |
例4-1 持续圆周运动
1 2 3 4 5 6 7 8 9 10 11 | var animator = new Animator(2000, function(p){ var tx = -100 * Math.sin(2 * Math.PI * p), ty = -100 * Math.cos(2 * Math.PI * p); block.style.transform = 'translate(' + tx + 'px,' + ty + 'px)'; }); block.addEventListener('click', function(){ animator.start(false); }); |
例4-2 折线运动
让滑块先向右然后再向下运动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var a1 = new Animator(1000, function(p){ var tx = 100 * p; block.style.transform = 'translateX(' + tx + 'px)'; }); var a2 = new Animator(1000, function(p){ var ty = 100 * p; block.style.transform = 'translate(100px,' + ty + 'px)'; }); block.addEventListener('click', function(){ a1.start(function(){ a2.start(); }); }); |
demo这里有两个连续的动画,所以我们最好对动画做一个队列的封装。
动画队列封装
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 | function AnimationQueue(animators){ this.animators = animators || []; } AnimationQueue.prototype = { status: 'ready', append: function(){ var args = [].slice.call(arguments); this.animators.push.apply(this.animators, args); }, flush: function(){ if(this.animators.length){ var self = this; function play(){ var animator = self.animators.shift(); animator.start(function(){ if(self.animators.length){ play(); } }); } play(); } } }; |
例4-3 矩形线
让滑块沿一个矩形边界运动
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 | var a1 = new Animator(1000, function(p){ var tx = 100 * p; block.style.transform = 'translateX(' + tx + 'px)'; }); var a2 = new Animator(1000, function(p){ var ty = 100 * p; block.style.transform = 'translate(100px,' + ty + 'px)'; }); var a3 = new Animator(1000, function(p){ var tx = 100 * (1-p); block.style.transform = 'translate(' + tx + 'px, 100px)'; }); var a4 = new Animator(1000, function(p){ var ty = 100 * (1-p); block.style.transform = 'translateY(' + ty + 'px)'; }); block.addEventListener('click', function(){ var animators = new AnimationQueue(); animators.append(a1, a2, a3, a4); animators.flush(); }); |
改良动画队列
添加了 animator 是否为 Animator 的实例判断
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 | function AnimationQueue(animators){ this.animators = animators || []; } AnimationQueue.prototype = { status: 'ready', append: function(){ var args = [].slice.call(arguments); this.animators.push.apply(this.animators, args); }, flush: function(){ if(this.animators.length){ var self = this; function play(){ var animator = self.animators.shift(); if(animator instanceof Animator){ animator.start(function(){ if(self.animators.length){ play(); } }); }else{ animator.apply(self); if(self.animators.length){ play(); } } } play(); } } }; |
例4-4 循环折线动画
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 | var a1 = new Animator(1000, function(p){ var tx = 100 * p; block.style.transform = 'translateX(' + tx + 'px)'; }); var a2 = new Animator(1000, function(p){ var ty = 100 * p; block.style.transform = 'translate(100px,' + ty + 'px)'; }); var a3 = new Animator(1000, function(p){ var tx = 100 * (1-p); block.style.transform = 'translate(' + tx + 'px, 100px)'; }); var a4 = new Animator(1000, function(p){ var ty = 100 * (1-p); block.style.transform = 'translateY(' + ty + 'px)'; }); block.addEventListener('click', function(){ var animators = new AnimationQueue(); animators.append(a1, a2, a3, a4, function(){ this.append(a1, a2, a3, a4, arguments.callee); }); animators.flush(); }); |
经典小球运动
例5-1 弹跳的小球(自由落体、弹起、循环)
小球方程 设 20px 为 1 米,下落距离为 200px(10 米)
$g \approx 10米/秒$
$S = \frac{1}{2}gT^2 = 10米$
$T = \sqrt\frac{2S}{g} = \sqrt2\approx1.414秒 = 1414毫秒$
下落阶段:$S_t = Sp^2$
上升阶段:$S_t = S - S_p(2 - p)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var a1 = new Animator(1414, function(p){ var ty = 200 * p * p; block.style.transform = 'translateY(' + ty + 'px)'; }); var a2 = new Animator(1414, function(p){ var ty = 200 - 200 * p * (2-p); block.style.transform = 'translateY(' + ty + 'px)'; }); block.addEventListener('click', function(){ var animators = new AnimationQueue(); animators.append(a1,a2, function(){ this.append(a1, a2, arguments.callee); }); animators.flush(); }); |
例5-2 弹跳的小球(能量损耗)
设每一个周期损耗为0.7:$T = 0.7T$ ` 上升距离:$S = 0.49S$
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 | block.addEventListener('click', function(){ var T = 1414; var a1 = new Animator(T, function(p){ var s = this.duration * 200 / T; var ty = s * (p * p - 1); block.style.transform = 'translateY(' + ty + 'px)'; }); var a2 = new Animator(T, function(p){ var s = this.duration * 200 / T; var ty = - s * p * (2-p); block.style.transform = 'translateY(' + ty + 'px)'; }); var animators = new AnimationQueue(); function foo(){ a2.duration *= 0.7; if(a2.duration <= 0.0001){ animators.animators.length = 0; } } animators.append(a1 ,foo, a2, function b(){ a1.duration *= 0.7; this.append(a1, foo, a2, b); }); animators.flush(); }); |
例5-3 滚动的小球
小球直径:$d = 50px$
圆周长:$l = πd$
周期:$T = 2秒$
滚动时间:$t_{max} = 4秒$
滚动距离等于:$S = πd·\frac{t_{max}}{T} = 314px$
1 2 3 4 5 6 7 8 9 10 11 | var a1 = new Animator(4000, function(p){ var rotation = 'rotate(' + 720 * p + 'deg)'; var x = 50 + 314 * p + 'px'; block.style.transform = rotation; block.style.left = x; }); block.addEventListener('click', function(){ a1.start(); }); |
例5-4 甩出小球(惯性)
1)小球做半径 100px 的匀速圆周运动,周期 2s 2)在 2.8s 后小球从手中甩出
甩出小球公式:
$x = − rsin(πt)$
$v_x = − πrcos(πt)$
$y = r − rcos(πt)$
$v_y = πrsin(πt)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | var a1 = new Animator(2800, function(p){ var x = -100 * Math.sin(2.8 * Math.PI * p); var y = 100 - 100 * Math.cos(2.8 * Math.PI * p); block.style.transform = 'translate(' + x + 'px,' + y + 'px)'; }); var a2 = new Animator(5000, function(p){ var x = -100 * Math.sin(2.8 * Math.PI) -100 * Math.cos(2.8 * Math.PI) * Math.PI * 5 * p; var y = 100 - 100 * Math.cos(2.8 * Math.PI) + 100 * Math.sin(2.8 * Math.PI) * Math.PI * 5 * p; block.style.transform = 'translate(' + x + 'px,' + y + 'px)'; }); block.addEventListener('click', function(){ a1.start(function(){ a2.start(); }); }); |
例5-5 匀加速到匀速到减速运动
滑块 1s 匀加速运动 100px,匀速运动 100px, 然后再经过 50px 速度减为 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | var a1 = new Animator(1000, function(p){ var x = 100 * p * p; block.style.transform = 'translateX(' + x + 'px)'; }); var a2 = new Animator(500, function(p){ var x = 100 + 100 * p; block.style.transform = 'translateX(' + x + 'px)'; }); var a3 = new Animator(500, function(p){ var x = 200 + 50 * p * (2 - p); block.style.transform = 'translateX(' + x + 'px)'; }); block.addEventListener('click', function(){ var animators = new AnimationQueue(); animators.append(a1, a2, a3); animators.flush(); }); |
此类型运动运动过程均可以用熟悉的速度-时间图表示
贝塞尔曲线
我们可以使用二阶贝塞尔曲线来构造平滑动画,这里如果对贝塞尔曲线还不太了解,可以看看下面这篇详解 贝塞尔曲线扫盲
这里有一些贝塞尔曲线的实现资源库和文档:
借助资源库我们可以方便实现简单的贝塞尔曲线动画
例6-1 easeInOutQuint动画
1 2 3 4 5 6 7 8 9 10 11 12 13 | var easing = BezierEasing(0.86, 0, 0.07, 1); //easeInOutQuint var a1 = new Animator(2000, function(ep,p){ var x = 200 * ep; block.style.transform = 'translateX(' + x + 'px)'; }, easing); block.addEventListener('click', function(){ a1.start(); }); |
例6-2 easeInOutBack动画
1 2 3 4 5 6 7 8 9 10 11 12 13 | var easing = BezierEasing(0.68, -0.55, 0.265, 1.55); //easeInOutQuint var a1 = new Animator(2000, function(ep,p){ var x = 200 * ep; block.style.transform = 'translateX(' + x + 'px)'; }, easing); block.addEventListener('click', function(){ a1.start(); }); |
例6-3 easeInOutBackXY动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var easing = BezierEasing(0.68, -0.55, 0.265, 1.55); //easeInOutQuint var a1 = new Animator(2000, function(ep,p){ var x = 200 * ep; var y = -200 * p; block.style.transform = 'translate(' + x + 'px,' + y + 'px)'; }, easing); block.addEventListener('click', function(){ a1.start(); }); |
例6-4 3DeaseInOutBackXY动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var easing = BezierEasing(0.68, -0.55, 0.265, 1.55); //easeInOutQuint var a1 = new Animator(2000, function(ep,p){ var y = -200 * ep; var x = 200 * p; var r = 360 * ep; block.style.transform = 'translate(' + x + 'px,' + y + 'px) rotateY(' + r + 'deg)'; }, easing); block.addEventListener('click', function(){ a1.start(); }); |
逐帧动画
例7-1 FlappyBird
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <style type="text/css"> .sprite {display:inline-block; overflow:hidden; background-repeat: no-repeat;background-image:url(http://res.h5jun.com/matrix/8PQEganHkhynPxk-CUyDcJEk.png);} .bird0 {width:86px; height:60px; background-position: -178px -2px} .bird1 {width:86px; height:60px; background-position: -90px -2px} .bird2 {width:86px; height:60px; background-position: -2px -2px} #bird{ position: absolute; left: 100px; top: 100px; zoom: 0.5; } </style> <div id="bird" class="sprite bird1"></div> <script type="text/javascript"> var i = 0; setInterval(function(){ bird.className = "sprite " + 'bird' + ((i++) % 3); }, 1000/10); </script> |
CSS3动画
-
transitions属性:
支持浏览器:IE10+,GC,FF
timing functions属性:
linear
(规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))ease
(规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))ease-in
(规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))ease-out
(规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))ease-in-out
(规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))cubic-bezier(n,n,n,n)
例8-1 圆周运动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <style> #block{ position:absolute; left: 200px; top: 100px; width: 20px; height: 20px; background: #0c8; text-align: center; border-radius: 50%; transform-origin: 0 100px; transform: rotate(0deg); } #block.play { transform: rotate(360deg); transition: transform 2.0s linear; } </style> <div id="block"></div> <script> block.addEventListener('click', function(){ block.className = 'play'; }); </script> |
例8-2 贝塞尔曲线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <style> #block{ position:absolute; left: 50px; top: 200px; width: 20px; height: 20px; background: #0c8; text-align: center; border-radius: 50%; } #block.play { transform: translateX(200px); transition: transform 2.0s cubic-bezier(0.68, -0.55, 0.265, 1.55); } </style> <div id="block"></div> <script> block.addEventListener('click', function(){ block.className = 'play'; }); </script> |
例8-3 transition覆盖
1 2 3 4 5 6 7 8 9 10 11 12 | #block.play { border-radius: 0; transform: scale(2.0); background: #c80; transition: all 2.0s cubic-bezier(0.68, -0.55, 0.265, 1.55) 3s; } #block.play2 { /* transition 覆盖*/ background: #c8f; transition: all 2.0s linear 0.5s; transform: scale(2.0) rotate(360deg); } |
-
animations:
支持浏览器:IE10+,GC,FF
主要属性:
keyframes'name
(规定需要绑定到选择器的 keyframe 名称)duration
(规定完成动画所花费的时间,以秒或毫秒计)timing functions
(规定动画的速度曲线)delay
(规定在动画开始之前的延迟)iteration count
(规定动画应该播放的次数)direction
(规定是否应该轮流反向播放动画)
例8-4 往复圆周运动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #block{ position:absolute; left: 200px; top: 100px; width: 20px; height: 20px; background: #0c8; text-align: center; border-radius: 50%; animation: roll 2.0s linear 0s infinite alternate; transform-origin: 0 100px; } @keyframes roll{ 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} } |
例8-5 往复圆周运动(贝塞尔曲线)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #block{ position:absolute; left: 200px; top: 100px; width: 20px; height: 20px; background: #0c8; text-align: center; border-radius: 50%; animation: roll 4.0s linear 0s infinite; transform-origin: 0 100px; } @keyframes roll{ 0%{transform:rotate(0deg)} 50%{transform:rotate(360deg)} 100%{transform:rotate(0deg)} } |
-
动画组合:
主要内容:
animation-fill-mode
webkitAnimationEnd
例8-6 组合动画
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <style> #block{ position:absolute; left: 150px; top: 200px; width: 20px; height: 20px; background: #0c8; text-align: center; border-radius: 50%; animation: anim 2.0s linear 0s forwards; } @keyframes anim{ 0%{border-radius: 50%} 50%{border-radius: 0; background: #c80;} 100%{border-radius: 20%; transform:scale(2.0); background: #08c;} } </style> <div id="block"></div> <script> function Animator(duration, progress, easing){ this.duration = duration; this.progress = progress; this.easing = easing || function(p){return p}; } Animator.prototype = { start: function(finished){ var startTime = Date.now(); var duration = this.duration, self = this; requestAnimationFrame(function step(){ var p = (Date.now() - startTime) / duration; var next = true; if(p < 1.0){ self.progress(self.easing(p), p); }else{ if(typeof finished === 'function'){ next = finished() === false; }else{ next = finished === false; } if(!next){ self.progress(self.easing(1.0), 1.0); }else{ startTime += duration; self.progress(self.easing(p), p); } } if(next) requestAnimationFrame(step); }); } }; var easing = BezierEasing(0.68, -0.55, 0.265, 1.55); var a1 = new Animator(2000, function(ep,p){ var x = 150 + 200 * ep; block.style.left = x + 'px'; }, easing); block.addEventListener('webkitAnimationEnd', function(){ a1.start(); }); </script> |
SVG 动画
动画性能
具体参考即为如下几点: 由于渲染三阶段分为:Layout--->Paint--->Composite,针对此三者进行优化。 优化目标:15FPS 不流畅 ,30FPS+ 感觉流畅,60FPS 舒适完美
触发 Layout 的方式有:
- 改变 width, height, margin 等和大小、位置相关的属性
- 读取 size, position 相关得属性 要点:
- 使用 transform 代替 top, left 的动画
- 分离读写,减少 Layout
- 面对解耦代码,使用 rAF 推迟的方法分离读写。
触发 Paint 的方式: 当修改 border-radius, box-shadow,color 等展示相关属性时,会触发 paint 要点:
- 简化绘制的复杂度
- 避免不必要的绘制
- 减少绘制区域
Composite 小结:
- GPU 是有限度的,不要滥用 GPU 资源生成不必要的 Layer
- 留意意外生成的 Layer
总结
本文版权归 yangzj1992 所有。来源青春样博客(qcyoung.com),商业转载请联系本人获得授权,非商业转载请注明出处。
本博客采用 Disqus 作为评论解决方案,目前 Disqus 经常被 GFW 封锁,若想参与评论请翻墙访问本站或将 disqus.com 添加至翻墙白名单。你也可以通过导航栏上的社交网站与我联系