一直对动画有兴趣,可惜自己想要的效果现在没有办法实现。只能多学习相关的库了。这也是读react-motion的初衷。
概念 spring(弹簧)
在motion中,会给定一个初始值和一个终止值。motion会按桢计算出两个值之间的过滤。并且每个桢都会调用setState来触发render。而决定初始值到终止值之前是怎么过滤的就取决于spring
1 | import presets from './presets'; |
一个spring
最终是由val
(终止值)、stiffness
(刚度)、damping
(阻尼)、和 precision
(精度)来表示。
想像一根弹簧val
就是弹簧的原始长度。stiffness
的在高中物理中叫k
(劲度系数),有一个公式F = k * deltaS
,deltaS
是弹簧形变的长度。damping
阻尼被定义为速度与力的一个系数。在motion中被定义中F = b * V
其中V为速度,b为阻尼系数,F为力。
motion中有4组预定义的系数
1 | export default { |
分别是不摇晃的,温和的,摇晃的和僵硬的。其实的数值应该是根据经验试出来的。(在motion中所有的公式都是定性不定量的,即没有米的概念也没有牛的概念)
驱动react渲染的motion.js
1 | /* @flow */ |
motion是一个react组件。其中一个核心的函数就是startAnimationIfNecessary
,其各种生命周期函数都是在围绕着这个函数的调用。
startAnimationIfNecessary每次都只是会计算一桢的值供render使用。其中使用了raf(一个requestAnimationFrame
兼容库),注意到1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25const framesToCatchUp = Math.floor(this.accumulatedTime / msPerFrame);
if (this.accumulatedTime > msPerFrame * 10) {
this.accumulatedTime = 0;
}
if (this.accumulatedTime === 0) {
// no need to cancel animationID here; shouldn't have any in flight
this.animationID = null;
this.startAnimationIfNecessary();
return;
}
...
for (let i = 0; i < framesToCatchUp; i++) {
[newLastIdealStyleValue, newLastIdealVelocityValue] = stepper(
msPerFrame / 1000,
newLastIdealStyleValue,
newLastIdealVelocityValue,
styleValue.val,
styleValue.stiffness,
styleValue.damping,
styleValue.precision,
);
}
...
this.accumulatedTime -= framesToCatchUp * msPerFrame
startAnimationIfNecessary每次都会消费掉新产生的时间片,除非超过10桢没响应。
motion中的state存着4个值。currentStyle当前样式,currentVelocity当前的速率。lastIdealStyle上一个样式,lastIdealVelocity上一个速度。
在startAnimationIfNecessary主要是获取时间来迭代计算state中的值。其中用到一个变量函数stepper。
stepper.js
1 | /* @flow */ |
Fspring由劲度系数产生的力,Fdamper由阻尼产生的力。a为加速度,此处直接定义为两个力的和(假定质量为1了呗)。接着用积分的方法(假定了时间无穷小,其实一个时间片为1/60秒),算出新的速度和新的位置。并且如果速度和位置差在精度内就将速度置为0来结束运动。最后返回带有新位置和速度的tuple。
react-motion中还有其它的方法不在本次阅读范围内