);
class Page extends React.Component {
handleChangeX = () => {
this.setState({
x: storeX.getX()
})
}
componentDidMount = () => {
storeX.subscribe(this.handleChangeX)
}
render = () =>
}
const startAnimation = (beginPos = 0, endPos = 300, duration = 5000, frameTime = 17) => {
const now = performance.now();
const loop = () => {
const passedTime = performance.now() - now;
const distance = endPos - beginPos;
const currX = distance/duration*passedTime + beginPos;
storeX.changeX(currX);
}
setTimeout(loop, frameTime);
};
reactDOM.render(
, document.body)
有没有觉得很棒!但仍然有优化的空间。动画是源自现实世界的,人类早已习惯了一个变速运动的物理环境,这样的一个匀速动画会让人相对感觉不适。为了优化用户体验,React Motion 使用了一种常见的变速运动 —— 弹簧运动。
React Motion 缓动原理剖析
React Motion 使动画看起来像一个弹簧那样(一个有空气阻力的弹簧,如果没有空气阻力,弹簧就会不停地做简谐运动了)。大家可以尝试使用 React Motion 的spring-parameters-chooser,配置一个合适的劲度系数和空气阻力。弹簧动画可以使网站增添一些俏皮的元素,让用户体验起来更加舒畅!
下面就让我们进入主题,开始解读 React Motion 的缓动过程。
先模拟弹簧的物理规律,实现弹簧动画。
假设有一个弹簧,弹簧上绑了一个砝码,回到初中物理,根据胡克定律,砝码的受到弹簧的拉力为:
$$ F_{spring} = kvarDelta{x} (k为弹簧的劲度系数)$$
我们假设该砝码受到的空气阻力 kdamping 与砝码当前的速度 vt 呈正相关,其中阻尼系数为 kdamping 。
对砝码进行受力分析得:
$$ F = F_{spring} - F_{damping} = k_{spring}varDelta{x} - k_{damping} imes v_{t} $$
设 at 为砝码当前加速度,得:
$$ F = ma_t $$
设 v" 和 x" 分别为经过 $$ dt $$ 时间后,砝码新的速度和位移,得:
$$ a_t = lim_{dt o 0} frac{dv}{dt} = lim_{dt o 0} frac{v^{"} - v_t}{dt} $$
$$ v_t = lim_{dt o 0} frac{dx}{dt} = lim_{dt o 0} frac{x^{"} - x_t}{dt} $$
即:
$$ v^{"} = lim_{dt o 0} a_t*d_t + v_t $$
$$ x^{"} = lim_{dt o 0} v_t*d_t + x_t $$
我们拿到了计算新状态的公式,但是 dt 是无限趋近于 0,怎么去模拟这个无限趋近于 0 呢?
现在只知道,当 dt 越趋近于 0 时,等式两边的值越接近(极限的单调有界准则可证)。可以把 dt 设为一个非常小的常量,虽然会造成一定的误差,但是不足为虑,只要骗过人类的眼睛就可以了。
这样我们就可以计算得出 v" 和 x" 。对以上过程不断重复,就能计算出任意时刻的位移和速度。
这是个通用的模拟物理规律的缓动过程,是否让你茅塞顿开?看一个同样的模拟物理规律的动画,有没有手痒?
但是,原谅我又说了 “但是”,如果我们要用 raf 实现这个缓动的话,raf 不能设置 callback 的延迟时间,而我们的 dt 是一个固定的非常小的常量。这种情况下,怎么计算新的状态呢?
我们设 raf callback 的延迟时间为 Δt ,第二部分已经说过,这个 Δt 是浏览器自己决定的。
不管 Δt 是多少,可以用几个缓动过程连续叠加(一个缓动过程的时间是 dt )来拼凑出 Δt 。
不过 Δt 往往不是 dt 的整数倍,对于最后多出来的一小块时间,我们可以取一个比例值。
const dt = 1000 / 60;
let preTime = 0
, initialState = {
currX: -250,
currV: 0,
}
const update = () => {
const currTime = performance.now();
const deltaTime = currTime - preTime;
const steps = deltaTime / dt;
const multiObj = (obj, k) => {
return Object.keys(obj).reduce((res, key) => {
return { ...res, [key]: obj[key] * k }
}, {})
};
const getCurrState = (prevState, steps) => {
if (steps < 1) {
return multiObj(cal(prevState), steps)
}
return getCurrState(cal(prevState), steps - 1)
};
render(getCurrState(initialState, steps))
raf(update);
}
update()
动画漫谈
CSS 动画与 JS 动画的区别是,使用 CSS 动画,不需要写缓动过程。比如在 transition 中,可以使用现成的 cubic bizier 的缓动(其中 ease, ease-in, ease-out 等都是特定参数值的 cubic bizier)。
(值得一提的是,transition的实现也使用了 raf 的机制,当标签页被切换时, transition 动画也会暂停,大家不妨试一试)
CSS 的 animation 使用设置关键帧的方式实现动画,适合完成多步、往返或者不断重复的动画。
那么我们什么时候需要 JS 动画呢——当你对 CSS 提供的缓动函数不满意的时候。打个比方,如果想实现像淘宝网在加购成功后,让商品 logo 沿着弧线运动的动画。
React Motion 所做的事,只不过自己实现了一套缓动函数。如果你不关心缓动过程,用 CSS 动画可以直接替换。
至于 React 当中的 ReactCSSTransitionGroup,是React提供的支持列表动画的 API 。试想一下,当渲染函数发现新的列表状态中,消失了某一项。那么要绘制这一项消失的动画,必须先让这一项暂存在 DOM 中,直到动画结束,再从 DOM 消失。这个实现起来比较麻烦,所以 React 提供了这个 API 帮助我们实现动画。值得注意的是,ReactCSSTransitionGroup 只是对列表的增与删提供动画支持。如果只是对列表项进行修改,不要生硬的套用 ReactCSSTransitionGroup,自己在 state 中管理列表实现起来更加方便。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/78388.html