React Spring 动画处理
目录
- 概述
- 使用
- 进阶
概述
React Spring 是一个用于构建交互式、数据驱动和动画 UI 组件的库。
react-spring
有以下五个版本,分别对应不同的渲染方式
- web - react-dom
- native - react-native
- three - react-three-fiber
- konva - react-konva
- zdog - react-zdog
优点
使用命令式 API 方法来运行动画,无需更新状态。通过响应事件而避免 React 渲染开销,实现流畅、流动的动画效果。
react-spring
的基础在于SpringValues
和animated
组件,也叫做 Animated Elements
目标元素
animated
组件可以从我们的任何 web 元素目标导入
import { animated } from '@react-spring/web'
// ✅ This will work because `div` is a web element
<animated.div />
// ❌ This will not work because `mesh` is not a web element.
<animated.mesh />
import { animated } from '@react-spring/web'
// ✅ This will work because `div` is a web element
<animated.div />
// ❌ This will not work because `mesh` is not a web element.
<animated.mesh />
如果动画目标是一个组件,可以使用animated
作为 HOC 包装组件
import { animated, useSpring } from "@react-spring/web";
const ExternalComponent = (props) => {
return <div {...props} />;
};
const AnimatedCom = animated(ExternalComponent);
const App = () => {
const styles = useSpring({
from: {
x: 0,
},
to: {
x: 200,
},
});
return <AnimatedCom style={styles} />;
};
import { animated, useSpring } from "@react-spring/web";
const ExternalComponent = (props) => {
return <div {...props} />;
};
const AnimatedCom = animated(ExternalComponent);
const App = () => {
const styles = useSpring({
from: {
x: 0,
},
to: {
x: 200,
},
});
return <AnimatedCom style={styles} />;
};
使用
useSpring
ts 定义
function useSpring(configuration: ConfigObject): SpringValues;
function useSpring(
configurationFn: () => ConfigObject,
deps?: any[]
): [springs: SpringValues, api: SpringRef];
function useSpring(configuration: ConfigObject): SpringValues;
function useSpring(
configurationFn: () => ConfigObject,
deps?: any[]
): [springs: SpringValues, api: SpringRef];
- With a config object
import { useSpring, animated } from "@react-spring/web";
const MyComponent = () => {
const springs = useSpring({
from: { opacity: 0 },
to: { opacity: 1 },
});
return <animated.div style={springs}>Hello World</animated.div>;
};
import { useSpring, animated } from "@react-spring/web";
const MyComponent = () => {
const springs = useSpring({
from: { opacity: 0 },
to: { opacity: 1 },
});
return <animated.div style={springs}>Hello World</animated.div>;
};
- With a function & deps
import { useSpring, animated } from "@react-spring/web";
const MyComponent = () => {
const [springs, api] = useSpring(
() => ({
from: { opacity: 0 },
to: { opacity: 1 },
}),
[]
);
return <animated.div style={springs}>Hello World</animated.div>;
};
import { useSpring, animated } from "@react-spring/web";
const MyComponent = () => {
const [springs, api] = useSpring(
() => ({
from: { opacity: 0 },
to: { opacity: 1 },
}),
[]
);
return <animated.div style={springs}>Hello World</animated.div>;
};
useSprings
用一个统一的 API 来编排多个spring
import { useSprings, animated } from "@react-spring/web";
import cx from "./index.module.less";
function MyComponent() {
const [springs, api] = useSprings(
5,
(i: index) => {
return { from: { top: 0, position: "absolute" } };
},
[]
);
return (
<div className={cx("ball")}>
{springs.map((props, index) => (
<animated.div key={index} style={props} className={cx("ball")}>
{index}
</animated.div>
))}
</div>
);
}
import { useSprings, animated } from "@react-spring/web";
import cx from "./index.module.less";
function MyComponent() {
const [springs, api] = useSprings(
5,
(i: index) => {
return { from: { top: 0, position: "absolute" } };
},
[]
);
return (
<div className={cx("ball")}>
{springs.map((props, index) => (
<animated.div key={index} style={props} className={cx("ball")}>
{index}
</animated.div>
))}
</div>
);
}
useSpringValue
useSpringValue
是一个 SpringValue
的简单包装器,运行不多的spring
时可以使用,命令式 API
import { animated, useSpringValue } from "@react-spring/web";
function MyComponent() {
const opacity = useSpringValue(0.5);
return (
<animated.div
onClick={() => {
opacity.start(opacity.get() === 1 ? 0.5 : 1);
}}
style={{ opacity }}
>
Hello World
</animated.div>
);
}
import { animated, useSpringValue } from "@react-spring/web";
function MyComponent() {
const opacity = useSpringValue(0.5);
return (
<animated.div
onClick={() => {
opacity.start(opacity.get() === 1 ? 0.5 : 1);
}}
style={{ opacity }}
>
Hello World
</animated.div>
);
}
useTransition
这个钩子最适合对数据集进行动画处理。
trail
参数控制每个动画起始时间的间隔
ts 定义
function useTransition<Item extends any>(
data: Item[],
configuration: ConfigObject
): TransitionFn<Item>
function useTransition<Item extends any>(
data: Item[],
configurationFn: () => ConfigObject
deps?: any[]
): [transition: TransitionFn<Item>, api: SpringRef]
function useTransition<Item extends any>(
data: Item[],
configuration: ConfigObject
): TransitionFn<Item>
function useTransition<Item extends any>(
data: Item[],
configurationFn: () => ConfigObject
deps?: any[]
): [transition: TransitionFn<Item>, api: SpringRef]
import { useTransition, animated } from "@react-spring/web";
function MyComponent({ data = [1, 2, 3] }) {
const [transitions, api] = useTransition(data, () => ({
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 1 },
}));
return transitions((style, item) => (
<animated.div style={style}>{item}</animated.div>
));
}
import { useTransition, animated } from "@react-spring/web";
function MyComponent({ data = [1, 2, 3] }) {
const [transitions, api] = useTransition(data, () => ({
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 1 },
}));
return transitions((style, item) => (
<animated.div style={style}>{item}</animated.div>
));
}
useChain
useChain
用于按顺序编排动画,确保它们按照指定的顺序执行,适用于需要依次触发一组动画的场景。
import {
useTransition,
useSpring,
useChain,
useSpringRef,
} from "@react-spring/web";
const data = ["hello", "world!"];
function MyComponent() {
const springRef = useSpringRef();
const springs = useSpring({
ref: springRef,
from: { size: "20%" },
to: { size: "50%" },
});
const transRef = useSpringRef();
const transitions = useTransition(data, {
ref: transRef,
from: { scale: 0 },
enter: { scale: 1 },
leave: { scale: 0 },
});
useChain([springRef, transRef]);
}
import {
useTransition,
useSpring,
useChain,
useSpringRef,
} from "@react-spring/web";
const data = ["hello", "world!"];
function MyComponent() {
const springRef = useSpringRef();
const springs = useSpring({
ref: springRef,
from: { size: "20%" },
to: { size: "50%" },
});
const transRef = useSpringRef();
const transitions = useTransition(data, {
ref: transRef,
from: { scale: 0 },
enter: { scale: 1 },
leave: { scale: 0 },
});
useChain([springRef, transRef]);
}
useTrail
useTrail
会自动编排 spring 动画,错开执行每一个动画
import { useTrail, animated } from "@react-spring/web";
export default function MyComponent() {
const [trails, api] = useTrail(
2,
() => ({
from: { opacity: 0 },
to: { opacity: 1 },
}),
[]
);
return (
<div>
{trails.map((props) => (
<animated.div style={props}>Hello World</animated.div>
))}
</div>
);
}
import { useTrail, animated } from "@react-spring/web";
export default function MyComponent() {
const [trails, api] = useTrail(
2,
() => ({
from: { opacity: 0 },
to: { opacity: 1 },
}),
[]
);
return (
<div>
{trails.map((props) => (
<animated.div style={props}>Hello World</animated.div>
))}
</div>
);
}
- delay: 前置动画延迟,无法设置每个动画之间的时间间隔
- config.duration 设置每个动画的时长,但后一个动画还是会在第一个动画结束前就开始执行
进阶用法
config 配置
config
是一个具有许多属性的对象,可用于自定义和微调动画。它直接控制弹簧的行为方式,并且可以逐个spring
进行定制
config
最典型的用途是编辑质量、摩擦和张力属性
- mass: 质量,默认值 1
- friction: 摩擦阻力,默认值 170
- tension: 弹簧张力,默认值 26
import { useSpring, animated } from "@react-spring/web";
const MyComponent = () => {
const [springs, api] = useSpring(
() => ({
backgroundColor: "#00ff00",
y: 0,
config: (key) => {
if (key === "y") {
return {
mass: 1,
friction: 170,
tension: 26,
};
}
return {};
},
}),
[]
);
return <animated.div style={springs} />;
};
import { useSpring, animated } from "@react-spring/web";
const MyComponent = () => {
const [springs, api] = useSpring(
() => ({
backgroundColor: "#00ff00",
y: 0,
config: (key) => {
if (key === "y") {
return {
mass: 1,
friction: 170,
tension: 26,
};
}
return {};
},
}),
[]
);
return <animated.div style={springs} />;
};
- config 预设属性
import { config } from "@react-spring/web";
// default { tension: 170, friction: 26 }
// gentle { tension: 120, friction: 14 }
// wobbly{ tension: 180, friction: 12 }
// stiff { tension: 210, friction: 20 }
// slow{ tension: 280, friction: 60 }
// molasses { tension: 280, friction: 120 }
{
config: config.gentle;
}
import { config } from "@react-spring/web";
// default { tension: 170, friction: 26 }
// gentle { tension: 120, friction: 14 }
// wobbly{ tension: 180, friction: 12 }
// stiff { tension: 210, friction: 20 }
// slow{ tension: 280, friction: 60 }
// molasses { tension: 280, friction: 120 }
{
config: config.gentle;
}
duration 持续时间
easing 动画效果
easing
仅可与 duration
同时使用。
import { easings } from "@react-spring/web";
// easeInBack easeOutBack easeInOutBack...
const [springs1, api] = useSpring(() => ({
x: 0,
config: { duration: 500, easing: easings.easeInBack },
}));
import { easings } from "@react-spring/web";
// easeInBack easeOutBack easeInOutBack...
const [springs1, api] = useSpring(() => ({
x: 0,
config: { duration: 500, easing: easings.easeInBack },
}));
Events 事件
每个事件函数既可以是它自己的函数,也可以是它的键与
spring
相关的对象
useSpring(
() => ({
x: 0,
y: 0,
onStart: () => console.log("the spring has started"),
}),
[]
);
useSpring(
() => ({
x: 0,
y: 0,
onStart: {
x: () => console.log("x key has started"),
y: () => console.log("y key has started"),
},
}),
[]
);
useSpring(
() => ({
x: 0,
y: 0,
onStart: () => console.log("the spring has started"),
}),
[]
);
useSpring(
() => ({
x: 0,
y: 0,
onStart: {
x: () => console.log("x key has started"),
y: () => console.log("y key has started"),
},
}),
[]
);
- onStart 在动画开始时调用
- onChange 在每一帧上调用
- onRest 在动画停止时调用
- onPause 在动画暂停时调用
- onResume 在恢复动画时调用
- onResolve 在解析加载时调用
- onProps 在动画被新道具更新后调用,即使动画没有执行也是如此
// onStart onChange onRest onPause onResume onResolve 类型一致
type onStart = (
result: AnimationResult,
spring: Controller | SpringValue,
item?: Item
) => void;
// onStart onChange onRest onPause onResume onResolve 类型一致
type onStart = (
result: AnimationResult,
spring: Controller | SpringValue,
item?: Item
) => void;
type OnProps = (
props: {[key: string]: any}
spring: SpringValue,
) => void
type OnProps = (
props: {[key: string]: any}
spring: SpringValue,
) => void
- onDestroyed 在卸载过渡项后调用
type OnDestroyed = (
item: Item
key: string | number
) => void
type OnDestroyed = (
item: Item
key: string | number
) => void
仅当 useTransition 在钩子中使用相关事件时,Item 参数才存在。
Interpolation 插值
最常见的用途是将 SpringValue
的值转换为另一个值。通过使用to
方法完成
import { useSpring, animated, to } from "@react-spring/web";
function MyComponent() {
const props = useSpring({
from: { x: 0 },
to: { x: 360 },
});
return (
<animated.div
style={{ transform: props.x.to((value) => `rotateZ(${value}deg)`) }}
>
Hello World
</animated.div>
);
}
function MyComponent() {
const props = useSpring({
from: { x: 0 },
to: { x: 360 },
});
return (
<animated.div
style={{ transform: to(props.x, (value) => `rotateZ(${value}deg)`) }}
>
Hello World
</animated.div>
);
}
import { useSpring, animated, to } from "@react-spring/web";
function MyComponent() {
const props = useSpring({
from: { x: 0 },
to: { x: 360 },
});
return (
<animated.div
style={{ transform: props.x.to((value) => `rotateZ(${value}deg)`) }}
>
Hello World
</animated.div>
);
}
function MyComponent() {
const props = useSpring({
from: { x: 0 },
to: { x: 360 },
});
return (
<animated.div
style={{ transform: to(props.x, (value) => `rotateZ(${value}deg)`) }}
>
Hello World
</animated.div>
);
}
更高级用途是组合多个 SpringValue
, 需要使用 to
函数来创建
import { animated, to, useSpring } from "@react-spring/web";
export default function MyComponent() {
const props = useSpring({
from: { x: 0, y: 0, z: 0 },
to: { x: 1, y: 1, z: 1 },
});
return (
<animated.div
style={{
transform: to(
[props.x, props.y, props.z],
(x, y, z) => `rotate3d(${x}, ${y}, ${z}, 45deg)`
),
}}
œ
>
Hello World
</animated.div>
);
}
import { animated, to, useSpring } from "@react-spring/web";
export default function MyComponent() {
const props = useSpring({
from: { x: 0, y: 0, z: 0 },
to: { x: 1, y: 1, z: 1 },
});
return (
<animated.div
style={{
transform: to(
[props.x, props.y, props.z],
(x, y, z) => `rotate3d(${x}, ${y}, ${z}, 45deg)`
),
}}
œ
>
Hello World
</animated.div>
);
}
Async Animations 异步动画
- 数组语法是异步动画完全控制,不需要为每次更新修改键值,每个更新将在上一个更新完成后触发。
- 需要控制动画时,通过 to 函数编写。该函数接收两个参数,一个是接受更新对象的函数 next ,第二个停止动画 cancel
import { useSpring, animated } from "@react-spring/web";
export default function MyComponent() {
const springs = useSpring({
from: { background: "#ff6d6d", y: -40, x: 0 },
// to: [
// { x: 80, background: "#fff59a" },
// { y: 40, background: "#88DFAB" },
// { x: 0, background: "#569AFF" },
// { y: -40, background: "#ff6d6d" },
// ],
to: async (next, cancel) => {
await next({ x: 80, background: "#fff59a" });
await next({ y: 40, background: "#88DFAB" });
await next({ x: 0, background: "#569AFF" });
await next({ y: -40, background: "#ff6d6d" });
},
loop: true,
});
return (
<animated.div
style={{
width: 40,
height: 40,
borderRadius: 4,
...springs,
}}
/>
);
}
import { useSpring, animated } from "@react-spring/web";
export default function MyComponent() {
const springs = useSpring({
from: { background: "#ff6d6d", y: -40, x: 0 },
// to: [
// { x: 80, background: "#fff59a" },
// { y: 40, background: "#88DFAB" },
// { x: 0, background: "#569AFF" },
// { y: -40, background: "#ff6d6d" },
// ],
to: async (next, cancel) => {
await next({ x: 80, background: "#fff59a" });
await next({ y: 40, background: "#88DFAB" });
await next({ x: 0, background: "#569AFF" });
await next({ y: -40, background: "#ff6d6d" });
},
loop: true,
});
return (
<animated.div
style={{
width: 40,
height: 40,
borderRadius: 4,
...springs,
}}
/>
);
}