由于最近做了一些页面的动画效果,之前经验不多,这次做的过程中碰到些问题,加之很早前就阅读过一篇很好介绍动画的博客《关于动画,你需要知道的》,来自十年踪迹,所以就思考了一些关于动画的基本原理的问题,比如本文这个。这个问题要简单也可以非常简单,比如前面提到那篇博客里就有一个比较好的解释,本文提供的是另外一种更详细地方式,希望对有需要的人有所价值。
在客观的物体运动中,以匀速直线运动为例,我们可以同时用速度与时间曲线或位移与时间曲线来描述物体的运动:
不管是用速度与时间的关系还是位移与时间的关系来描述客观物体的直线运动,物体的状态都是一致的,这是因为客观物体的运动总是沿着人无法改变的客观时间轴进行变化,在时间轴上的任意一点,总有特定的速度以及位移与之对应。
而在网页动画中,虽然它也呈现为运动,但是我们不能用客观物体的运动规律去描述它。我认为原因主要是动画的本质不是运动,仅仅是基于定时器对元素状态进行的瞬间改变。以一个简单的元素进行水平匀速偏移的动画效果为例,要实现这个动画,只要用一个定时器在一个固定的时间间隔,重新设置元素的x轴偏移量即可,大概用图可以描述如下:
图中t1~t6代表定时器回调函数执行的时刻。在这个效果中,元素的偏移位置将在定时器每次执行的时刻发生变化,而在相邻的两个执行时刻之间,元素的偏移位置是不变的。我们看到的动画,仅仅是因为定时器间隔时间太短,从视觉上感知不到这段时间的过程,如果将定时器间隔加到足够长,我们就能看到元素在间隔时间内的状态了。
正因动画不是运动,所以我们在尝试理解一些动画过程的时候,不能用运动规律去思考。比如我们该如何去理解动画停止那一刻的状态?还以前面提到的这个动画效果为例,当把定时器清掉的时候,动画瞬间停止,对于元素而言,它的动画速度将骤变为0,如果我们类比到客观的物体运动,总是会想当然地以为元素的动画也应该先有个减速的过程才能停止下来,要是这样想,就没办法理解元素动画停止时骤停的原理了。但是当我们从动画的本质去思考这个问题的时候,就很好理解了,因为定时器是元素在动画过程中发生状态改变的唯一要素,当定时器不起作用的时候,就没有外在的力量去改变元素的状态了,它还怎么能动呢?
尽管动画不是运动,我们还是希望找到一个方式,能够很好的控制动画的快慢,以便打造更加流畅,更加逼近客观世界的动画效果。当提到快慢,就很容易想到速度,因为在客观物体运动中,速度就是用来描述运动快慢的要素。而且用速度的规律来控制动画的快慢,看起来也很好理解和实现。将前面的的例子再具体一点,假如我们想实现一个元素在1秒内往右匀速偏移120px的动画效果,那么只要用定时器控制元素每次往右偏移固定的量即可,这里面定时器每次执行给元素添加的偏移量,就是我们用来控制动画的速度。如果我们以16ms作为定时器的间隔,那么这个动画的速度可以通过: 120px / (1000ms / 16ms) 求得(约等于 2px),也就是说只要定时器每次执行的时候将元素往右偏移2px就能实现我们要的效果。简单代码实现如下:
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 |
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="box" style="width: 100px;height: 100px;background-color: goldenrod"> </div> <br> <button type="button" onclick="start()">开始</button> </body> <script> var box = document.getElementById('box'); function start() { var duration = 1000;//动画时长 var s = 120;//总的偏移量 var cur_s = 0;//当前偏移总量 var p = 16;//定时器间隔 var speed = s / (duration / p);//速度 var count = 0; var start_time = new Date().getTime(); var timer = setInterval(function(){ if(cur_s >= s) { clearInterval(timer); console.log('动画运行时间(ms): ' + (new Date().getTime() - start_time)); return; } count++; cur_s = speed * count; box.style.transform = 'translateX(' + cur_s + 'px)'; },p); } </script> </html> |
在浏览器中运行以上代码,动画效果肯定是跟预期一致的,而且动画的实际执行时间也与规定的时长相差很小:
至于为什么不完全等于1000ms,那是因为多的那20多毫秒都耗费在了代码执行上。
通过这个例子,看起来,我们用速度去控制动画的思路还比较可行。事实上,这种思路是很有局限性的,我不是说它不行,只是说局限性,就是只能用于小部分的场合,而不能适用更广泛的动画效果中。为什么呢,原因有多个方面。
先从定时器说起。
定时器给了我们一种通过代码的方式来管理时间轴,但是这个时间轴与客观时间轴是有差别的。假如我们把一个动画的定时器间隔放大,放大到1000ms,让这个定时器执行10次,定时器执行的真实时间间隔会等于1000ms吗?
以上代码模拟了一个动画,并且放大了动画的时间间隔,如果把它拿到浏览器中执行,我们会得到下面类似的结果:
从这个结果可以看出,虽然定时器的间隔设置为了1000ms,但是实际的执行间隔却只能说在1000左右浮动。这是很正常的,假如我们把操作系统的时间看成是客观的时间轴,那么浏览器里面定时器构建的时间轴只能是一个尽可能的接近客观时间轴的模拟时间轴。操作系统的状态,浏览器的状态,定时器内外代码的执行时间都会影响这根时间轴与客观时间轴的差距,只考虑浏览器内部,定时器内外的代码执行时间越长,这其中的差距越大。因为上面的代码是在一个很简单的网页中测试出来的,所以定时器的实际间隔与客观时间的偏差很小,要是一个页面内容比较多的时候,这个偏差一定会比现在的大。
时间轴的不稳定性,会直接导致速度的不稳定性,也就是说匀速运动都无法达到理想状态,更别说其它复杂的变速运动了。
单从这点来说,不管用什么方式控制运动,都会存在这个问题,所以它还并不能完全说明速度控制动画的根本问题所在。这个根本问题在于无法确保动画能够按照规定的时长完成。在上面的例子的基础上,我们想办法把定时器的时间轴与客观时间差的偏差放大,这个不难办到,只要在定时器执行过程中,加入一些耗时任务即可,代码如下: