多段动画整合为一个动画的思路

785 阅读5分钟

需求

这是从一个项目中提取的需求。 大致的思路是: 通过获取后端的一系列人员的点位信息,在前端模拟人员的一段时间内的行动过程。

我们的开发人员首先想到的思路是,把获取的点位列表信息,每两个取取来,组成一条直线路径。然后基于每一条直线路径,创建一个动画Animate对象。(Animate是我们内部的一个管理/播放动画的类)。

其中创建每一个Animate动画的代码大致如下:

new Animate({
    from: p1,
    to : p2,
    dur : 30 * 1000,
    onUpdate(p){
       persion.setPosition(p)
    }
});

上述代码中,创建了一个动画对象,其参数表示如下:

  • from : p1, 动画开始的位置
  • to : p2,动画结束的位置
  • dur: 动画持续的时间
  • onUpdate函数,Animate对象内部更新处理每一帧数据的回调函数, Animate对象会根据from和
    to以及dur的值,结合当前这一块的时间戳。计算出每一帧的插值数据p,并把p传递给回调函数onUpdate, 我们可以在onUpdate接收每一帧的数据值,并进行我们的更新动作,比如在本实例中会根据接收到的定向信息更新人员的位置信息。

问题

这个思路初期时没有问题的,只是到了实测的时候,发现性能有挺大的问题。因为获取一天的点位信息,点位的数量大概在几天条,这就意味这需要持续创建几千个Animate对象。这必然会造成性能问题。

在了解了这个问题之后,我建议我们的开发人员值只创建一个Animate对象,把所有直线路径的动画都整合到该Animate对象中。

要把所有的路径动画包含到一个Animate对象中,第一步是要改造from和to的值,此时不能直接使用点位信息来作为from和to的值。 实际上,我们可以通过百分比的方式来设置from和to,即from等于0,to等于1(相当于100%)。

为了简化问题,我们首先看只有一条直线的情况下,from和to设置位百分比是如何实现的。代码如下:

var p1, p2;
...
new Animate({
    from: 0,
    to : 1,
    dur : 30 * 1000,
    onUpdate(value){ 
       var p = lerpVectors(p1,p2,value);
       persion.setPosition(p)
    }
});

当把from和to设置位百分比时,Animate计算出来的每一帧的数值value是一个0~1之间的百分比值。此时,我们在onUpdate函数中,自己通过插值计算来计算点位信息p(其中lerpVectors函数完成此功能,此处不详述)。

整合思路

然后,我们在看多条直线的情况。 这种情况下的难点在于,要把一个动画分割成多段动画。然后对于分割的每一段,启动上面实例中的0~1的过程动画。其中涉及到两个问题

  1. Animate给定的value值,我们如何确定要执行的是那一段动画。
  2. 确定了那一段之后,如何把value值转换位这一段动画(从0~1)的动画值。

确定索引值

对于第一个问题,由需求有关。本文中,所有要执行动画的片段,其时间是一样的,这样就意外这,所有段的分配值是平均的。 举个例子,由10条直线组成动画,对于0~1之间的value值,由:

  • 0~0.1 执行第一条直线的动画
  • 0.1~0.2 执行第二条直线的动画
  • 依次类推。。。

因此我们可以通过循环变量和值比较来确定要执行动画直线的索引值,比如如下代码:

var index = 0;
for(var  i = 0;i < len;i ++){
  if(value > i / len && value < (i + 1) / len){
     index = i;
    break;
  }
}

上述代码中,首先启动以一个循环,循环的长度位片段的数量长度len。在循环体内,比较value值是否在索引i分片所对应的区间之内,如果在区间,就让index = i,以此确定要执行第index条直线的动画。

当然,如果通过数学公式,可以更加方便确定索引值index。怎么处理呢?

首先把0~1的范围扩展到0~len,此时把value值也乘以len,一样的道理,假设如果value * len的结果:

  • 在0~1之间,则value值对应的是第一分片的动画
  • 在1~2之间,则value值对应的是第二分片的动画
  • ...
  • 在len - 1 ~ len之间,则value值对应的是第二分片的动画

因此只要看value*len在那个整数区间即可,只是确定一个数值的整数区间,不在需要通过遍历,而只需要一个数学的函数即可:Math.floor。因此可以通过下述一行代码既可快速确定索引值:

var index = Math.floor(value * len);

确定每一段动画的帧数值

上面确定了索引值。 如何确定每段动画的的帧数值呢? 起始很简单,我们知道value * len一定在index ~ index + 1 之间。 那么可以通过平移把区间index ~ index + 1 平移到区间0~1即可。 平移只需要减去index即可。代码如下:···var valueOfSegment = value * len - index;···

上代码

下面是比较完整的代码(伪代码):

var lines = [];// 要播放动画的一系列直线;
var len = lines.length;
new Animate({
    from: 0,
    to : 1,
    dur : 30 * 1000,
    onUpdate(value){ 
       var index = Math.floor(value * len);
       var valueOfSegment = value * len - index;
       var line = lines[index];
        var p1 = line.p1,p2 = line.p2;
       var p = lerpVectors(p1,p2,value);
       persion.setPosition(p)
    }
});

总结

  • 把多个动画整合位一个,是在动画很多的时候,提高性能的一个有效手段。
  • 善于利用数学公式,可以有效提高代码编写效率,本文中两个重要的公式如下, 具体作用见上面正文:
   var index = Math.floor(value * len);
   var valueOfSegment = value * len - index;

欢迎关注公众号“ITman彪叔”。彪叔,拥有10多年开发经验,现任公司系统架构师、技术总监、技术培训师、职业规划师。在计算机图形学、WebGL、前端可视化方面有深入研究。对程序员思维能力训练和培训、程序员职业规划有浓厚兴趣。ITman彪叔公众号