[转载]javascript in-between原理与应用_前端开发.
简介
本文将介绍in-between的概念,以及in-between类库Tween.js的实现。接着,我将介绍一些常见的in-between的好玩的用法。最后,我还将介绍JQuery Effects对in-between的应用。
目录
- 什么是tween
- Tween.js
- 有趣的应用
- JQuery中的Tween
什么是tween
tween是in-between的另一种写法。一个tween指的是让对象的属性值平滑变化的一个过程。
那么,什么是平滑变化?假设在9点10分的时候,对象foo.a
的值为0。在9点20分的时候,我希望它的值变成1。如果foo.a
是非平滑变化的,在9点10分到9点20分(除9点20分外)之间它依然是0。如果它是平滑变化的,那么它应该在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}
(目标状态)。变化过程采用线性变化,即每个时间点的变化速率相等。
var foo = {a: 1}, /*初始状态*/ tween = new TWEEN.Tween(foo) .to({a: 4} /*目标状态*/, 3000 /*变化时间*/) .start(); (function animate() { if ( foo.a < 4 ) { requestAnimationFrame(animate); } TWEEN.update(); console.log(foo); })();
如果你查看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
var foo = {a: 1}, /*初始状态*/ tween = new TWEEN.Tween(foo);
接下来,我们需要将确认foo对象的目标状态,在这里是{a: 4},并且要求它正好在3000毫秒后到达目标状态。
tween.to({a: 4} /*目标状态*/, 3000 /*变化时间*/);
最后,我们需要激活这个tween,代表开始变化。调用tween.start()的时间就是开始变化的时间时间戳,除非你调用了tween.delay()方法。你还可以给tween.start(time)传入一个额外参数time,直接指定开始变化的时间戳。我们可以通过源码验证这点
_startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() ); _startTime += _delayTime;
值得注意的是,在没有delay和指定time参数的情况下,Tween.js将优先使用window.performance.now()获取当前的时间戳,这样的时间戳是高精度时间戳(精度为10μs)。这是HTML5当中的新增的DOMHighResTimeStamp API。
询问进度
我们通过animate函数来轮询foo对象目前的状态。采用requestAnimationFrame进行异步调用,效率会更高。你也可以选择用setTimeout,jQuery就是这样做的。
在询问的时候,你首先需要调用TWEEN.update()更新所有的tween。
(function animate() { if ( foo.a < 4 ) { requestAnimationFrame(animate); } TWEEN.update(); console.log(foo); })();
精髓
使用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,一个是从出发位置到目标位置的,一个是返回出发位置的。
var tween = new TWEEN.Tween(elem) .to({ x: endValue }, 4000) .delay(Math.random() * 1000) .onUpdate(updateCallback) .easing(TWEEN.Easing.Back.Out) .start(); var tweenBack = new TWEEN.Tween(elem, false) .to({ x: startValue}, 4000) .delay(Math.random() * 1000) .onUpdate(updateCallback) .easing(TWEEN.Easing.Elastic.InOut); tween.chain(tweenBack); 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,是为了兼容以前的插件。
jQuery.fx = Tween.prototype.init;
对于动画中需要变化的每一个属性,jQuery都为其创建一个Tween。
jQuery.map( props, createTween, animation );
function createTween( value, prop, animation ) { var tween, collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), index = 0, length = collection.length; for ( ; index < length; index++ ) { if ( (tween = collection[ index ].call( animation, prop, value )) ) { // we're done with this property return tween; } } }
每隔一段时间,jQuery要求每隔DOM节点的tween根据当前进度更新style。
for ( ; index < length ; index++ ) { animation.tweens[ index ].run( percent /*当前动画的时间进度*/); }
jQuery当中并没有用requestAnimationFrame一直去询问,而是采用setTimeout每隔13ms去询问,然后更新界面。13ms是一个平衡点,不会太长,也不会太短。
jQuery.fx.start = function() { if ( !timerId ) { timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); } };
总结
本文介绍了in-between,并介绍了它的原理以及一些应用。in-between主要用在页面效果动画,数据可视化当中。你可以让它和一些著名的数据可视化库(如d3.js)协同工作。你可以查看Tween.js的examples,学到更多相关的应用。
讨论
欢迎到我们的团队博客进行讨论,我在团队博客里面的讨论时间较多。