Object是个dom,动起来就是改变定位。

CSS模拟

所以从top: 0到top: 100px就算运动了?当然不是,真实世界中的物体,任何移动都需要时间。CSS3加入了transition。所以最简单的模拟运动,是设置好transition-duration,比如设置为1s,那么0移动到100px,速度就是100px/s。结合transition-timing-function(在Chrome中可以直接看到对应时间曲线),可以模拟有加速度的object了。

这样做,起始和结束都是确定的,要做一些菜单等交互的缓动,绰绰有余。但是如果要结合控制,比如,控制一个球的运动,该怎么做呢?不断改变结束位置?但是transition-duration确定了,移动100px和移动200px都是1s?显然,需要js出场了。

js计算运动

让我们再回顾一下屏幕上模拟运动的方式:计算机是离散的,为了模拟连续世界,在两个离散量之间加入更多中间量。也就是说,套用微积分的概念,我们要对这个过程进行微分。

先来一个匀速运动:还是0-100px,1s。不失一般性,我们分成10份,[0, 0.1, 0.2, ..., 1.0]。显然这个运动就是第0s在0px,第0.1s在10px……第1.0秒在100px。扯到时间的概念,调用一下setTimeout、setInterval函数,很容易写。这个运动模型是:\(s=0+100px\cdot t\)

每一次移动的距离:

$$\Delta s=v\cdot \Delta t$$

加速运动呢?不好意思我把中学物理翻出来才想起来……

$$\Delta s=v\cdot \Delta t+\frac{1}{2}\cdot a\cdot \Delta t^2$$

怎样实现呢?这里还是要搞清,真实连续世界和离散世界的区别,我们进行的是模拟。就像刚才一样,分成1/x秒就可以了。所以我们的工作是计算这1/x秒内的变化。比如再次设置间隔为0.1s(100ms),调用setTimeout可以写一个递归的形式:

var calcMove = (a, v, s, dT) => {  
  ds = v * dT + a * dT * dT * 0.5;  
  v = v + a * dT;  
  s = s + ds;  
  // move to s  
  console.log(s);  
  setTimeout(calcMove, dT * 1000, a, v, s, dT);  
}  
// a=5/s, v0=0, s0=0  
calcMove(5, 0, 0, 0.1);

到这里已经差不多了。不过HTML5时代了,还用什么setTimeout啊,让我们用requestAnimationFrame吧!关于它的优点,MDN页面后有引用,不再赘述,总之不考虑太老旧的浏览器的话,这是个更好的选择。

var lastTime = null;  
var a = 5;  
var v = 0;  
var s = 0;  
var calcMove = (now) => {  
  if (!lastTime) lastTime = now;  
  dT = (now - lastTime) / 1000;  
  ds = v * dT + a * dT * dT * 0.5;  
  v = v + a * dT;  
  s = s + ds;  
  // move to s  
  lastTime = now;  
  requestAnimationFrame(calcMove);  
}  
requestAnimationFrame(calcMove);

需要注意的是requestAnimationFrame接受一个callback,callback只有一个参数,提供当前帧时间,由浏览器统一管理重绘。也就是说在setTimeout里需要的delay不需要了。浏览器大约每16ms重绘一次。

更真实一点

按照物理模型有什么好处呢,可以再进一步完善模型~比如,加入摩擦:

var lastTime = null;  
var ForceC = 0.03;  
var a = 2000;  
var v = 0;  
var s = 0;  
var calcMove = (now) => {  
  if (!lastTime) lastTime = now;  
  dT = (now - lastTime) / 1000;  
  f = Math.sign(-v) * v * v * ForceC;  
  ds = v * dT + (a + f) * dT * dT * 0.5;  
  v = v + (a + f) * dT;  
  s = s + ds;  
  // move to s  
  lastTime = now;  
  requestAnimationFrame(calcMove);  
}  
requestAnimationFrame(calcMove);

说明,空气阻力:

$$f=\frac{1}{2}C\rho SV^2$$

总之 \(C\rho S\) 都是常数,干脆都搞成一个量好了(ForceC)。至于怎么出这个量大小……试出来的→_→这样就只与\(v^2\)成正比了。

另外因为和运动方向相反,取个v的方向好了~即Math.sign(-v)。

这样呢,只要改变a的大小、方向,就能控制对象运动了~

总结

数学、物理的知识,可以帮助我们建立一个合理的模型。因为对现实世界的模拟更符合我们的认知。且直接使用经过验证的理论,是自洽的、可扩展的。其效果当然也是可靠的。

图就不上了自己玩吧~