React Spring是一个基于弹簧的动画,弹簧的弹性轨迹符合人脑对真实物理的建模。因此,相比于css基于函数插值的动画,基于弹簧轨迹的动画更有助于人们在直觉上感觉“更平滑”。这就是为什么iOS的动画饱受好评的原因,因为在iOS中,大量使用了弹簧动画。
目前,React Spring已经成为了我“御用”的动画框架,其声明式语法降低了心智负担,在正确的封装后,可以大大提高开发动画效率,增强UI界面的活泼性。
项目地址:pmndrs/react-spring: ✌️ A spring physics based React animation library (github.com)
基本结构分析
React Spring使用lerna框架实现Monorepo架构,所有依赖的包都放在/packages目录中。
在React Spring中,/packages
有这些包:animated core parallax rafz react-spring shared types
其中,core
包是弹簧动画核心代码所在位置;rafz
用于requestAnimationFrame调度;react-spring
是外部调用的入口。
React Spring是平台无关(platform-agnostic)的,一套语法可以同时用于多种不同的平台。在/targets
中,React Spring对不同平台进行了适配,包括web、three fiber、react native等。也就是说,在dom、3D场景和原生app中,都可以使用React Spring实现弹簧动画。
动画核心
SpringValue中的advance方法是计算每一帧动画的核心算法,以下是主要逻辑(省略部分代码),弹簧部分其实就是高中物理:
/** 每一帧requestAnimationFrame时调用的函数,参数为距离上一帧的间隔时间 */
advance(dt: number) {
let idle = true
let changed = false
//省略部分代码
anim.values.forEach((node, i) => {
//省略初始化代码
if (!finished) {
//省略
if (!is.und(config.duration)) {
//省略,如果config中有duration值,则使用普通的函数插值计算动画(类似 css animation)
//is.und是工具函数,判断对应值是否为undefined
}
else if (config.decay) {
//省略,如果config中有decay值,则使用decay动画
}
else {
//使用基于弹簧的动画,关键部分
velocity = node.lastVelocity == null ? v0 : node.lastVelocity
/** The smallest distance from a value before being treated like said value. */
const precision =
config.precision ||
(from == to ? 0.005 : Math.min(1, Math.abs(to - from) * 0.001))
/** The velocity at which movement is essentially none */
const restVelocity = config.restVelocity || precision / 10
// Bouncing is opt-in (not to be confused with overshooting)
const bounceFactor = config.clamp ? 0 : config.bounce!
const canBounce = !is.und(bounceFactor)
/** When `true`, the value is increasing over time */
const isGrowing = from == to ? node.v0 > 0 : from < to
/** When `true`, the velocity is considered moving */
let isMoving!: boolean
/** When `true`, the velocity is being deflected or clamped */
let isBouncing = false
//根据距离上一帧的时间进行循环,以1毫秒为单位
const step = 1 // 1ms
const numSteps = Math.ceil(dt / step)
for (let n = 0; n < numSteps; ++n) {
//判断速度是否小于最小速度,如果小于,且已经到达目标位置(小于precision精度),则标记为finished
isMoving = Math.abs(velocity) > restVelocity
if (!isMoving) {
finished = Math.abs(to - position) <= precision
if (finished) {
break
}
}
//是否反弹(达到目标值后,按照惯性继续来回移动,就像弹簧来回跳动一样)
if (canBounce) {
isBouncing = position == to || position > to == isGrowing
// Invert the velocity with a magnitude, or clamp it.
if (isBouncing) {
velocity = -velocity * bounceFactor
position = to
}
}
//计算弹簧的弹力
const springForce = -config.tension * 0.000001 * (position - to)
//计算用于衰减速度的阻力
const dampingForce = -config.friction * 0.001 * velocity
//计算加速度,a=F/m
const acceleration = (springForce + dampingForce) / config.mass // pt/ms^2
//计算速度
velocity = velocity + acceleration * step // pt/ms
//根据速度计算位移
position = position + velocity * step
}
}
node.lastVelocity = velocity
if (Number.isNaN(position)) {
console.warn(`Got NaN while animating:`, this)
finished = true
}
}
})
//省略额外后续操作
}
在GitHub中源码对应的位置:https://github.com/pmndrs/react-spring/blob/c4c7de9f75eb59e9d3e24dc7f8e188abccf93c71/packages/core/src/SpringValue.ts#L304
欢迎来到Yari的网站:yar2001 » React Spring 源码解析 基于弹簧的声明式动画库