By

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结束后将启动tweenBacktweenBack启动后会再次启动tween

jQuery中的Tween

jQuery中也采用了Tween来管理动画的效果进度。在jQuery 1.8之后,引入了Tween来管理动画效果进度,原先的jQuery.fxTween.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,学到更多相关的应用。

Written by
前端开发工程师