React Spring 学习笔记
炎灸纹舞 2020年09月15日 15:04 · 阅读 5261简介 ( 官方文档 )
react-spring
是 React
上的一个动画库。它基于弹簧物理原理实现,尽可能的使元素的动画轨迹与真实世界更接近,更易于我们理解与使用
React-spring:8.0.27
安装
npm install react-spring
yarn add react-spring
复制代码
API
react-spring
库的动画相关的 API
支持 render props
和 react hook
等两种不同的使用方式,我们接下来介绍的是 react hook
的部分动画相关的 API
:
react spring hook
创建的spring
动画的基础配置都可以通过useState
、useRef
等在外部控制,修改以改变动画。动画过渡属性(from、to
)也可以设置state
控制,但无法取得过渡效果,变换很生硬
- 动画定义
API
( 负责创建动画 )useSpring
:创建一个单独的简单动画Spring
useSprings
:创建一组同时执行的Spring
useTrail
:创建一组依次执行的Spring
(创建的Spring
属性一致)useTransition
:添加组件mounted/unmounted
等生命周期变化时的动画useChain
:用于自定义Spring
执行顺序
- 动画绘制
API
( 负责动画的执行 )animated
:是react-spring
动画效果实现的基本,所有通过上述API
生成的Spring
同必须通过animated
标签才能绘制动画 ( 动画的执行者 )interpolate
:将Spring
中执行过程的过渡属性值与animated.xxx
绑定 ( 添加到style\classname
等属性上 ),可对属性值进行数据映射或修改等操作 ( 绑定动画与执行者 )
- 默认配置
useSpring
( 示例 )
useSpring
用于创建一个单独的动画,它是其余的 api
的基础,它接收一个包含动画属性的对象或者返回值为对象的箭头函数作为参数
-
参数为
Object
- 接收类型为
UseSpringProps
的对象 - 返回一个类型为
AnimatedValue
的对象 - 动画属性的变更可以通过外部
useState
控制,修改state
通过Ref
调整reset
等属性,会执行相应动画
- 接收类型为
-
参数为箭头函数返回
Object
- 箭头函数返回类型为
UseSpringProps
的对象 - 返回一个
[AnimationValue, set, stop]
数组 - 动画属性的变化只能通过
set
函数重新传入,可以通过stop
方法提前结束动画
- 箭头函数返回类型为
动画创建过程解析
API
动画属性与返回值 (创建动画
)
在查看动画属性时,最好调整 demob
的属性查看效果,以便更好的理解
UseSpringProps
动画属性定义 (定义动画
)
动画属性定义,其他钩子函数的动画属性与其基本一致,只能增加了一些特有的过渡属性
// 只有在useSprings这个允许使用多个动画属性配置的hook中,key才会作为回调函数的参数
interface useSpringsProps {
// 动画的起始值
from?: object
// 动画的终点值
to?: object | key => object | Array<object>
// 动画开始的延迟时间
delay?: number | key => number
// 如果为真,则停止动画(直接跳转到结束状态)
immediate?: boolean | key => boolean
// 如果为true,将跳过渲染组件,直接写入dom
native?: boolean default false
// 弹簧的基本属性(影响动画的速度、轨迹等)
config?: SpringConfig | key => SpringConfig
// 是否重头开始重新执行动画,只有设置reset才能达到想要的过渡动画效果
reset?: boolean default false
// 如果为真,from和to设置的值将会对换,该属性只能和reset一起使用才能达到预期效果
reverse?: boolean default false
// 动画开始时执行
onStart?: (ds: DS) => void
// 所有动画都停止时执行
onRest?: (ds: DS) => void
// 动画执行过程中,每一帧间隔执行
onFrame?: (ds: DS) => void
}
复制代码
- 特殊的 to 属性
// to 属性可以是异步函数(过渡效果从上到下依次实现)
{
to: async (next: any, cancel: any) => {
await next({ opacity: 1, color: '#ffaaee', fontSize: 30 });
await next({ opacity: 0.5, color: 'rgb(14,26,19)', fontSize: 20 });
},
}
// to 属性可以是数组(过渡效果从左到右依次实现)
{ to: [{opacity: 1, color: '#ffaaee'}, {opacity: 0, color: 'rgb(14,26,19)'}], }
// to属性的值可以与其他属性平级(props的其他属性)
{
from: { o: 0.3, xyz: [0, 0, 0], color: 'red' },
o: 1,
xyz: [10, 20, 5],
color: 'green',
reverse: visible,
}
复制代码
SpringConfig
动画的基本配置之弹簧
默认配置为直接从
react-spring
导出的config.default
对象
interface SpringConfig {
// 弹簧质量,即:与动画的加速度有关,mass的值越大,动画执行的速度也会随着执行的时间越变越大
mass?: number default 1
// 弹簧张力,影响整体速度,即:动画在有一个点向下一个点运动时,递增的步长(受张力影响)如果过大,就会一下子超过范围,在下一点回来时又会距离终点较远,导致在同一点周围来回运动
// 即使是线性运动的过渡效果,设置tension之后可以实现曲线运动
tension?: number default 170
// 摩擦力(阻力),即动画执行时的反向加速度,可以与mass、tension的效果相互抵消
friction?: number default 26
// 动画执行速度
velocity?: number default 0
// 当动画弹簧越过边界时,是否立即结束动画
clamp?: boolean default false
// 准确率
precision?: number default 0.01
// 动画开始的延迟
delay?: number
// 当duration大于0时,动画将在规定的时间范围内执行,一旦超过就结束动画;否则将一直执行到动画结束,没有时间限制
duration?: number default undefined | 0 ms
// 缓动函数,默认是线性的,可以换成其他的如d3-ease(动画运行轨迹函数)
easing?: (t: number) => number default false
}
复制代码
内置的弹簧默认配置 config
config.default { mass: 1, tension: 170, friction: 26 }
config.gentle { mass: 1, tension: 120, friction: 14 }
config.wobbly { mass: 1, tension: 180, friction: 12 }
config.stiff { mass: 1, tension: 210, friction: 20 }
config.slow { mass: 1, tension: 280, friction: 60 }
config.molasses { mass: 1, tension: 280, friction: 120 }
复制代码
API Hook
返回值 ( 获取通过Hook生成的动画
)
Hook
的返回值值的第一个参数为 AnimatedValue
对象,如果是创建多个 Spring
的 Hook
,返回的是数组 AnimatedValues
,如果是箭头函数传参(手动控制动画),则为[AnimatedValues, set, stop]
stop
方法在当前8.0.27
版本存在,但在当前react-spring
的TS
类型定义中不存在,只能强制获取
// 返回值为对象时 useSpring
interface AnimatedValue {
// xxx 为from和to属性中返回的属性
[key in (form & to)]: AnimatedInterpolation;
}
// 返回值为数组 useSprings useTrail
type AnimatedValues = Array<AnimatedValue>;
复制代码
- 主要使用方法与属性
一个
animatedValue
对象的值在外部无法实时获取,只能通过interpolate
方法实时获取animatedValue
的value
等值的数据
interface AnimatedInterpolation {
interpolate // interpolate 方法
getValue()
// 以及其他从Animated对象上继承来的属性
...
value // 当前spring的值
}
复制代码
animated
组件 ( 动画的执行者
)
react spring hook
的 animated
使用比较简单可以直接使用 animated.xxx
从 animated
中导出需要使用的 html
标签,然后将 Spring
直接赋值给 style
等属性 即可
const spring = useSpring({...})
// 无需附加任何属性,直接使用传入from和to的值作为style的属性
<animated.div style={spring}></animated.div>
// 通过interpolate获取传入的参数,返回新的style属性
<animated.div style={{transfrom: spring.xxx.interpolate(x=>`translate2d(${x}px, 0)`)}}></animated.div>
const springs = useSpring(5, [{...}])
// springs当生成spring动画数组时,通过map解构获取单个spring,然后将值添加给animated
springs.map((spring, index) => (<animated.div key={index} style={{...spring}} />))}
springs.map((spring, index) => (<animated.div key={index} style={{transfrom: spring.xxx.interpolate(x=>`translate2d(${x}px, 0)`)}} />))}
复制代码
interpolate
方法 ( 将生成的动画与执行者绑定
)
interpolate
方法有两种,一种是在 AnimatedValue
属性上使用,一种是直接在 react-spring
库中导出使用
该方法只有在
animated
标签中使用才能执行回调函数,返回在箭头函数中设置的值,否则只会返回一个interpolate
函数
AnimatedValue
属性上使用
在 useSpring
等创建动画 spring
的属性的上调用(如:spring
中的 width
属性)
- 类型定义
// 类型定义,官方表示还有其他参数类型定义,但v8中存在问题无法使用,只能在v9中使用
{
range: InterpolationConfig | ((...args: any[]) => any),
}
// v9 类型定义 除extrapolate外 v8 可以在JS中使用,但Ts会报警
{
// 第一个参数,可能为range(number[]), 或者config(InterpolationConfig对象),或者 和我们上一个示例类似的取值的箭头函数
range: number[] | InterpolationConfig | ((...args: any[]) => any),
// 输出映射output,根据点的变化输出相应的output的值
output?: (number | string)[],
// 点的范围界限
extrapolate?: ExtrapolateType
// extend:
}
type InterpolationConfig<T, U = T> = {
range: T[]
output: U[]
}
// range、output示例(角度转化)
range: [0, 360],
output: ['0deg', '360deg']
// ExtrapolateType 枚举类型
Enum ExtrapolateType {
identity // 点与输入的值一致,一旦到底边界,会跳过一切的后续处理(output等映射失效),直接将输入input返回
clamp // 动画轨迹上的点无法超出边界
extend // 默认值,动画轨迹上的点可以超出一定的边界
}
复制代码
- 使用方式
// Ts只支持下列用法
// 动画运行时接收width的值,返回新的transform需要的值
<animated.div
style={{
transfrom: spring.width.interpolate((x) => `translate2d(${x}px, 0)`),
}}
></animated.div>;
// 在animated外不会运行箭头函数,返回一个新的interpolate
spring.width.interpolate({range, output}).((x) => `translate2d(${x}px, 0)`);
复制代码
直接在 react-spring
导出 interpolate
方法
- 类型定义
与上一种用法类似,
v8
的Ts
中range
参数只能使用箭头函数类型,不能使用其他类型,它也可以返回一个新的interpolate
对象,之后的参数定义与第一种用法一致
// 类型定义
{
// 需传入的 AnimatedValue 对象,对象的值将作为后续的箭头函数的参数一一传入
parent: number[],
// 第一个参数,可能为range(number[]), 或者config(InterpolationConfig对象),或者 和我们上一个示例类似的取值的箭头函数
range: number[] | InterpolationConfig | ((...args: any[]) => any),
// 输出映射output,根据点的变化输出相应的output的值
output?: (number | string)[],
}
复制代码
- 使用方法
// Ts只支持的用法
interpolate([x, y, ...], (xValue, yValue, ...)=>{})
// interpolate([x], xV=>xV).interpolate()
// 链式调用的interpolate用法与第一种方法一致
复制代码
useSprings
( 示例 )
useSprings
能够根据配置文件创建不同但却同时开始执行的 Spring
动画,相当于 useSpring
的复数版本,参数也非常类似,它接收两个参数
- 第一个参数
Number
- 需创建的
Spring
的个数
- 需创建的
- 第二个参数有两种情况,与
useSpring
的参数、返回值都非常类似- 类型为
Array
- 接收
Array<UseSpringProps>
的对象数组,长度为第一个参数Number
- 返回
AnimationValues
对象的数组springs
,需通过map
解构传入animated
使用
- 接收
- 类型为箭头函数
- 接收参数
num
,表示当前是第几个Spring
- 箭头函数返回 类型
UseSpringProps
的对象 - 返回一个
[AnimationValues, set, stop]
- 动画属性的变化同样只能通过
set
函数重新传入,可以通过stop
方法提前结束动画
- 接收参数
- 类型为
useTrail
( 示例 )
是在 useSprings
的基础上实现的,都是创建多个 Spring
动画,但 useSprings
创建的动画是同时执行,而 usetrail
创建的动画是按照顺序依次执行且动画的属性都是一个对象,它也接收两个参数
- 第一个参数
Number
- 需创建的
Spring
的个数,与useSprings
一致
- 需创建的
- 第二个参数
- 它只能接收一个对象作为所有
Spring
的动画属性,因此它创建的动画的属性都是一致的,细微不同可以在animated
中使用时自己调整 - 与
useSpring
的第一个参数一致
- 它只能接收一个对象作为所有
- 返回值与
useSprings
一致
useChain
( 示例 )
useChain
方法是用来控制不同的 spring 的执行顺序的,它没有返回值,接收一个[springRef ... ]
的数组,然后将数组的动画从左到右依次执行
- 参数
[springRef ...]
useTransition
( 示例 )
useTransition
方法
用于实现组件 mounted/unmounted
等生命周期时的过渡效果动画,其他的 API
都不能对组件生命周期进行控制,它支持三个参数
- 第一个参数
Items
- 可以是
Array
,表示useTransition
控制一个或多个组件的生命周期动画效果,返回的 transition 的长度是
Array.length` - 可以是
Boolean
,表示useTransition
只控制了一个组件的生命周期动画效果,但返回的transition
是一个长度为 1 的Array
- 可以是
- 第二个参数
key
- 可以为一个箭头函数,接收第一个参数的
array
的单个项作为参数,返回值作为组件的key
值,如item => item.key
- 可以为 null,将采用默认(
i => i
); 如果第一个参数为boolean
或长度为 1,第二个参数可以为null
- 可以为一个箭头函数,接收第一个参数的
- 第三个参数
value
- 是一个类型为
UseTransitionProps
的对象,包含useTransition
动画的基本属性
- 是一个类型为
- 返回值
- 只能返回
Array<UseTransitionResult>
数组
- 只能返回
UseTransitionProps
生命周期动画属性与返回值
from
与initial
功能一致,都是enter
的起始值,但inital
只有在组件第一次加载时有效,from
则是每次mounted
都有效,inital
的优先级要高
interface UseTransitionProps {
// 动画弹簧基本属性
config?: SpringConfig | ((item: TItem) => SpringConfig)
// 如果为true,key值相同的Spring将被重用
unique?: boolean default false
// 动画开始前的延迟(毫秒),每次进入/更新和离开时加起来
trail?: number
// 动画开始的起始属性(每次mounted是enter的起始值)
from?: InferFrom<DS> | ((item: TItem) => InferFrom<DS>)
// 组件mounted的动画属性
enter?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>)
// 组件unmounted的动画属性
leave?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>)
// 组件的属性更新时的动画属性(Item的值变化时,在enter之后启动,可以通过hook控制state变化)
update?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>)
// 动画初始值,第一次加载的值(只有第一次mounted有效)
initial?: InferFrom<DS> | ((item: TItem) => InferFrom<DS>) | null
// 在组件销毁时调用
onDestroyed?: (isDestroyed: boolean) => void
}
interface UseTransitionResult {
// items的单个项,如果items是boolean则为boolean的值
item: TItem
// key值,如果不设置为null,则默认key=>key自动生成
key: string
// 当前值状态 inital、enter、leave、update
state: State
// 动画属性
props: AnimatedValue
}
复制代码
Spring
状态变更
上述的实例中,所有的组件都是在 API
外部使用 useState
控制动画变更,一旦 state
有修改,动画的轨迹就好发生相应变化,但有时候我们需要自己去控制 Spring
动画的变更、停止,所以 react-spring
也为useSpring
、useSprings
和useTrail
这三个 API 提供了类似的功能,只需要我们修改传参的方式即可使用。
启用 手动控制
需要启用手动控制只需要我们将包含动画基本属性的对象通过箭头函数传入,然后 API 的返回值也会变化,修改之后将会返回一个数组,
-
返回的新的数组(长度为 3)
- 第一个值为原返回值(直接使用对象时)
- 第二个参数为
set
函数- 此时只能使用该函数修改动画属性,
spring
状态才会变化,无法在外部使用state
变更
- 此时只能使用该函数修改动画属性,
- 第三个参数为
可选
的finished
函数- 该函数可以停止动画效果,比如直接从开始状态跳转到结束状态
- 该参数的类型定义在
v8
中不存在,官方在v9
中已修改 - 接收一个
boolean
参数,为true
表示停止动画
-
注意:
useSprings
的动画属性参数为对象数组- 变更的箭头函数的有两个参数
- 第一个参数 为
num
,表示当前第几个spring
,然后返回当前spring
的动画属性对象,可以对spring
进行定制,以绘制属性不同的动画 - 箭头函数的第二个参数为
controller
,是useSprings
的控制器,可以拿到所有的配置文件信息
返回值变更示例
const props = useSpring({ ... })
const [props, set, stop] = useSpring(() => ({ ... }))
const props = useSprings(number, [{ ... }, { ... }, ...])
const [props, set, stop] = useSprings(number, (i, controller) => ({ ... }))
const trails = useTrail(number, { ... })
const [trails, set, stop] = useTrail(number, () => ({ ... }))
复制代码
函数类型定义
type returns = [AnimationValue, SpringUpdateFn, SpringStopFn];
// 第一个参数为AnimatedValue
// 第二个参数set函数接收 UseSpringProps 对象
export interface SpringUpdateFn {
props: UseSpringProps;
}
// 第三个参数stop函数
export interface SpringStopFn {
/** Stop all animations and delays */
(finished?: boolean): void;
// V8中只能使用第一种finished参数,后面两者是V9的
/** Stop the animations and delays of the given keys */
(...keys: string[]): void;
/** Stop the animations and delays of the given keys */
(finished: boolean, ...keys: string[]): void;
}
复制代码
React Spring V9
版本变更(部分) ( 官方说明文档 )
react-spring
的 v9
版本与 v8
版本相差不大,主要是新增了部分动画属性,但 Transitions
函数的写法有部分变更,且新增了 Controller 方法来控制动画
React-spring:9.0.0-rc.3
# 安装下一个待发布版本
npm install react-spring@next
yarn add react-spring@next
# 选择安装版本
npm install react-spring@cancary
yarn add react-spring@cancary
复制代码
动画属性及方法变化
interpolate
更名为to
函数- 动画属性
reset
默认值变为true
- 动画属性
to
允许使用useCallback
缓存动画变更 - 新增动画属性
loop
:循环执行动画true
: 一直循环执行function | Object
:满足条件,返回true
后,循环执行,不满足则停止
- 新增动画单个属性控制方法(貌似有些不稳定,有些是在当前源码里找到的) ( 示例 )
get idle(): boolean;
get goal(): T | (T extends FluidValue<infer U, any> ? U : T);
get velocity(): VelocityProp<T>;
/** Advance the current animation by a number of milliseconds */
advance(dt: number): boolean;
/** Check the current phase */
is(phase: SpringPhase): boolean;
/** Set the current value, while stopping the current animation */
set(value: T | FluidValue<T>): this;
/**
* Freeze the active animation in time.
* This does nothing when not animating.
*/
pause(): void;
/** Resume the animation if paused. */
resume(): void;
/**
* Skip to the end of the current animation.
*
* All `onRest` callbacks are passed `{finished: true}`
*/
finish(to?: T | FluidValue<T>): this;
/** Push props into the pending queue. */
update(props: SpringUpdate<T>): this;
/**
* Update this value's animation using the queue of pending props,
* and unpause the current animation (if one is frozen).
*
* When arguments are passed, a new animation is created, and the
* queued animations are left alone.
*/
start(): AsyncResult<T>;
start(props: SpringUpdate<T>): AsyncResult<T>;
start(to: Animatable<T>, props?: SpringUpdate<T>): AsyncResult<T>;
/**
* Stop the current animation, and cancel any delayed updates.
*
* Pass `true` to call `onRest` with `cancelled: true`.
*/
stop(cancel?: boolean): this;
/** Restart the animation. */
reset(): void;
/** Prevent future animations, and stop the current animation */
dispose(): void;
/** @internal */
onParentChange(event: FrameValue.Event): void;
复制代码
- 动画属性返回值
animationValue
新增属性finished
:是否完成cancelled
:是否已取消
Transitions
函数使用变更 ( 示例 )
新的 Transitions
函数去除了原本的第二个参数,只接收原来的第一、第三个参数(现只接收两个参数),第二个参数迁移到第三个参数 UseTransitionProps
内,返回值变为一个 TransitionFn
函数,该函数接收一个回调函数 TransitionRenderFn
作为参数,回调函数有四个参数(SpringValue, item, t, i)
,返回 Transitions
动画
const transition = useTransition(items, props);
const fragment = transition((style, item, t, i) => {
return <a.div style={style}>{item}</a.div>;
});
复制代码
返回函数类型定义
interface TransitionRenderFn {
(
// 当前动画属性
values: SpringValues<State>,
// 当前items的值
item: Item,
// 当前transitions对象
transition: TransitionState<Item, State>,
// 当前第几个动画
index: number
): ReactNode;
}
复制代码
UseTransitionProps 的变化
interface UseTransitionProps {
from?: TransitionFrom<Item>;
initial?: TransitionFrom<Item>;
enter?: TransitionTo<Item>;
update?: TransitionTo<Item>;
leave?: TransitionTo<Item>;
// transitions的key值(原v8的第二个参数)
key?: ItemKeys<Item>;
// 相当于对items数组设置的sort函数
sort?: (a: Item, b: Item) => number;
trail?: number;
/**
* When `true` or `<= 0`
* 立即执行 setTimeout(forceUpdate,0) (ctr.idle为false和处于leave状态才会生效)
*
* When `false` 永不卸载
*
* When `> 0`
* expires ms后执行forceUpdate(ctr.idle为false和处于leave状态才会生效)
*/
expires?: boolean | number | ((item: Item) => boolean | number);
config?:
| SpringConfig
| ((item: Item, index: number) => AnimationProps['config']);
// onFrame 更名后的函数
onChange?: (values: State) => void;
}
复制代码
unique、lazy、onDestroyed、order
属性去除,onFrame
更名为onChange