React Spring 学习笔记

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动画的基础配置都可以通过 useStateuseRef 等在外部控制,修改以改变动画。动画过渡属性(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等属性上 ),可对属性值进行数据映射或修改等操作 ( 绑定动画与执行者 )
  • 默认配置
    • config弹簧属性 ( 影响动画的速度、加速度、延迟、误差等基本属性 ) 的默认值 ( 官方示例 )

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也为useSpringuseSpringsuseTrail这三个 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

posted on 2023-01-19 11:17  漫思  阅读(114)  评论(0编辑  收藏  举报

导航