JavaScript in-between原理与应用
发布在子回的前端专栏2014年5月12日view:3401
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

简介

本文将介绍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点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结束后将启动tweenBacktweenBack启动后会再次启动tween

jQuery中的Tween

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

讨论

欢迎到我们的团队博客进行讨论,我在团队博客里面的讨论时间较多。

评论
发表评论
暂无评论
WRITTEN BY
子回
主攻互联网开发。
TA的新浪微博
PUBLISHED IN
子回的前端专栏

就是一个专栏而已

我的收藏