前端趣法:游乐园的小火车如何跑起来

分类栏目:用户体验 - 前端开发

3828

发布于 2 条评论

前端趣法:游乐园的小火车如何跑起来 - 0

淘宝玩法平台的版头动画,是由我们的视觉设计师 @鲍龙 设计,由我(叶斋)实现的。

版头动画中,跑着一列小火车。这销魂的路线,确实能给人「眼前一亮」的感觉,也有一些同学询问我实现的原理。其实,真的很简单,就是一点小技巧而已。

查看演示下载附件

采集数据

首先,我沿着轨道采集了一些点的坐标。平滑的地方可以稀疏一些,曲率较大的地方需要采集得密一些。采集好的点,画出来大概是这样的:

前端趣法:游乐园的小火车如何跑起来 - 1

确定位置

动画的基本原理,就是在每一帧的时候进行重绘。我们需要知道,某一帧 时,小火车的位置(坐标)是多少?

假设小火车是匀速的,速度为 speed,那么我们可以知道:在某一帧时,小火车已经行进的距离 L,是上一帧行进的距离 L’ 加上两帧的时间差 inter 乘以 speed。那么,我们只需为小火车保存当前的 L,在每一帧,就可以获得最新的 L,进而决定那一帧火车的位置。代码如下(这里是为了理解方便,实际情况要复杂一些,我们不会用 setInterval,时间间隔也不会那么武断地取常量)。

train.speed = 10;
train.L = 0;
setInterval(function(){
    var inter = 50;
    train.L += inter * trans.speed;
    // 根据 train.L 算出 train 的位置
    // 在那个位置上画出 train
}, 50)

有了小火车当前帧已经行进的距离 L,又有了轨道的踩点数据,如何确定小火车的状态呢?勾股定理帮助我们计算轨道中任意两个相邻踩点之间的距离,进而我们也可以得到整个轨道的长度。这样,就能够通过 L 求取火车位于轨道上的哪两个踩点之间,以及火车在它们之间的哪个位置。

假设小火车在第 i 个点 p[i] 和第 i+1 个点 p[i+1] 之间,而且在比例为 q 的位置上,如何确定小火车的位置呢。

前端趣法:游乐园的小火车如何跑起来 - 2

简单的线性内插,求得:

train.x = (1-q) * p[i].x + q * p[i+1].x
train.y = (1-q) * p[i].y + q * p[i+1].y

这样就能够算出小火车每一帧的位置坐标,我们只需要在 (train.x , train.y) 位置绘制出火车即可。

确定角度

现在,小火车已经能开动了。但遗憾的是,它永远只保持着一个方向,但显然的是,沿着轨道方向的小火车更加真实。我们可以通过轨道上任意相邻两点的坐标(比如下图的 A B 两点),求出这两点间线段的角度。然后在对小火车平移之后,先不急着绘制,再旋转这个角度绘制,小火车就能沿着轨道了。

前端趣法:游乐园的小火车如何跑起来 - 3

看上去不错,但是还不够,因为采样点不够密集,所以在每个采样点处,小火车的旋转角度都会「骤变」一下,动画看起来就会不流畅。所以,对于每一帧的旋转角度,我们再进行一次内插。

首先,为每个点确定一个「切线」法向量,如 A 点的 theta_A,B 点的 theta_B,B 点的 theta_C。确定的过程,可以取前后两点连线的法线,比如 theta_B 就是 AC 两点的法线。然后对于任意一帧,根据小火车的位置,内插出火车的角度。

train.theta = (1-q) * p[i].theta + q * p[i+1].theta

前端趣法:游乐园的小火车如何跑起来 - 4

这样,小火车跑动就不会再一卡一卡的了。

速度问题

之前,我们把小火车的速度设置为了常量,但是如果它的速度是合理变化的,那就会更加逼真了。对于过山车而言,简单的能量守恒定律告诉我们,速度的平方与小火车的高度存在某种线性关系。稍加推理,不难写出:

// 在每一帧
train.speed = Math.sqrt(C1 - C2 * train.y)

适当地调节两个常量 C 1C 2 的值,使动画看上去比较合理。这样,动画就完成了。

总结

动画的原理很简单,最关键的就是「内插」而已。每一帧中,不管是小火车的位置还是角度,都是通过内插得来的。这样,位置和角度的值就会是连续变化的。其实,这种情况,也许把曲线拟合成若干条贝塞尔曲线互相连接,会更加「完美」,但是经验告诉我,拟合贝塞尔曲线也许没那么轻松,而适当的采样点也蕴含了足够的信息,足以使我完成这个动画。

查看演示下载附件

全部评论 / 2

  1. jack

    效果看到了,非常好看,实现原理还是有点模糊

    jack 2015-12-30
    2
  2. 若水

    动画好好玩 :wink:

    若水 2015-12-30
    1