JavaScript in-between原理与应用
简介
本文将介绍in-between的概念,以及in-between类库Tween.js的实现。接着,我将介绍一些常见的in-between的好玩的用法。最后,我还将介绍jQuery Effects对in-between的应用。
目录
什么是tween
tween是in-between的另一种写法。一个tween指的是让对象的属性值平滑变化的一个过程。
那么,什么是平滑变化?假设在9点10分的时候,对象foo.a
的值为0。在9点20分的时候,我希望它的值变成1。如果foo.a
是非平滑变化的,在9点10分到9点20分(除9点20分外)之间它依然是0,直到9点20分那一刻来临才变成1。如果它是平滑变化的,那么它应该在9点10分到9点20分之间的每一个时间点上的值都不同,并且是根据一定函数规律变化的。
tween就是这个平滑变化的过程。
这就好比一个人溜冰一样。你要从点a滑到点b,你是不可能一开始一直呆在a点,直到最后通过超时空转换直接把自己变到b点的。要从a点滑到b点,你必须经过一个路径,从而平滑地从a点滑到b点。
Tween.js
Tween.js是用来在JavaScript当中实现tween的一个工具库。我们接下来讲解它的实现。在实际应用中,我们一般自己编写自己的Tween类,或者复制并修改开源工具库中的Tween类,因为自己编写的总是最符合自己业务需求的。大部分Tween工具库包含了很多你用不到的东西,在后面我会提到。
为了使用Tween.js,你需要先有一个待变化的对象。在下面的例子里,我们将对象foo
初始化为{a: 1}
(初始状态),并要求它在3000毫秒后变成{a: 4}
(目标状态)。变化过程采用线性变化,即每个时间点的变化速率相等。
1 var foo = {a: 1}, /*初始状态*/
2 tween = new TWEEN.Tween(foo)
3 .to({a: 4} /*目标状态*/, 3000 /*变化时间*/)
4 .start();
5
6 (function animate() {
7 if ( foo.a < 4 ) {
8 requestAnimationFrame(animate);
9 }
10 TWEEN.update();
11 console.log(foo);
12 })();
如果你查看Chrome Inspector(或者Firefox下的Firebug插件),你将会看到控制台中输出了下面的数据
Object {a: 1.0001740000443533}
Object {a: 1.0924470000900328}
Object {a: 1.1527340000029653}
Object {a: 1.1701550000580028}
Object {a: 1.185736000072211}
... ...
喘口气
回过头来,我们来稍微解释一下上面的代码段。首先我们创建一个foo
对象的tween
1 var foo = {a: 1}, /*初始状态*/
2 tween = new TWEEN.Tween(foo);
接下来,我们需要将确认foo对象的目标状态,在这里是{a: 4}
,并且要求它正好在3000毫秒后到达目标状态。
1 tween.to({a: 4} /*目标状态*/, 3000 /*变化时间*/);
最后,我们需要激活这个tween,代表开始变化。调用tween.start()
的时间就是开始变化的时间时间戳,除非你调用了tween.delay()
方法。你还可以给tween.start(time)
传入一个额外参数time
,直接指定开始变化的时间戳。我们可以通过源码验证这点
1 _startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
2 _startTime += _delayTime;
值得注意的是,在没有delay和指定time参数的情况下,Tween.js将优先使用window.performance.now()
获取当前的时间戳,这样的时间戳是高精度时间戳(精度为10μs)。这是HTML5当中的新增的DOMHighResTimeStamp API。
询问进度
我们通过animate
函数来轮询foo
对象目前的状态。采用requestAnimationFrame
进行异步调用,效率会更高。你也可以选择用setTimeout
,jQuery就是这样做的。
在询问的时候,你首先需要调用TWEEN.update()
更新所有的tween。
1 (function animate() {
2 if ( foo.a < 4 ) {
3 requestAnimationFrame(animate);
4 }
5 TWEEN.update();
6 console.log(foo);
7 })();
精髓
使用in-between的精髓就在于,它将属性的变化和询问分离。如果你熟悉MVC,属性的变化就好像是MVC里面的Model,而询问就好像是Controller,最后我们输出到控制台(Console)就好像是View。
“历史总是惊人地相似”
分离带来的好处是什么呢?它使得我们可以统一管理所有页面上的Tween,而不用关心它们究竟用于什么途径。接下来,我们通过实践来证明这一点。
有趣的应用
首先你需要先将Tween.js的GitHub代码仓库复制到本地
git clone git@github.com:sole/tween.js.git
cd tween.js
在examples
目录里面有许多有趣的应用,我们只看其中第二个例子01_bars.html
。在这个例子中,有1000个彩条在屏幕上水平移动。每个彩条都对应两个tween,一个是从出发位置到目标位置的,一个是返回出发位置的。
1 var tween = new TWEEN.Tween(elem)
2 .to({ x: endValue }, 4000)
3 .delay(Math.random() * 1000)
4 .onUpdate(updateCallback)
5 .easing(TWEEN.Easing.Back.Out)
6 .start();
7
8 var tweenBack = new TWEEN.Tween(elem, false)
9 .to({ x: startValue}, 4000)
10 .delay(Math.random() * 1000)
11 .onUpdate(updateCallback)
12 .easing(TWEEN.Easing.Elastic.InOut);
13
14 tween.chain(tweenBack);
15 tweenBack.chain(tween);
Tween.js支持事件onUpdate
,每当TWEEN.update()
被调用的时候,会触发所有tween的update
事件。另外,你还能为每个tween设置easing function。如果你不清楚什么是easing function,可以看我昨天写的文章《JavaScript动画实现初探》。
由于Tween.js和其他支持in-between的类库都含有大量预置的easing function,其中有很多我们用不到的。所以,就像本文前面提到的一样,我们经常需要定制自己的Tween类库。
这里还用到了chaining来循环动画,tween
结束后将启动tweenBack
,tweenBack
启动后会再次启动tween
。
jQuery中的Tween
jQuery中也采用了Tween来管理动画的效果进度。在jQuery 1.8之后,引入了Tween来管理动画效果进度,原先的jQuery.fx
和Tween.prototype.init
是相同的。之所以保留jQuery.fx
,是为了兼容以前的插件。
1 jQuery.fx = Tween.prototype.init;
对于动画中需要变化的每一个属性,jQuery都为其创建一个Tween。
1 jQuery.map( props, createTween, animation );
2
3 function createTween( value, prop, animation ) {
4 var tween,
5 collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
6 index = 0,
7 length = collection.length;
8 for ( ; index < length; index++ ) {
9 if ( (tween = collection[ index ].call( animation, prop, value )) ) {
10 // we're done with this property
11 return tween;
12 }
13 }
14 }
每隔一段时间,jQuery要求每隔DOM节点的tween根据当前进度更新style。
1 for ( ; index < length ; index++ ) {
2 animation.tweens[ index ].run( percent /*当前动画的时间进度*/);
3 }
jQuery当中并没有用requestAnimationFrame
一直去询问,而是采用setTimeout
每隔13ms去询问,然后更新界面。13ms是一个平衡点,不会太长,也不会太短。
1 jQuery.fx.start = function() {
2 if ( !timerId ) {
3 timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
4 }
5 };
总结
本文介绍了in-between,并介绍了它的原理以及一些应用。in-between主要用在页面效果动画,数据可视化当中。你可以让它和一些著名的数据可视化库(如d3.js)协同工作。你可以查看Tween.js的examples,学到更多相关的应用。