元素动画
为单个DOM元素应用动画
SSGOI
from0
to1
spring
过渡效果示例
如何使用各种过渡效果:
import { transition } from '@ssgoi/react';
import { fade, scale, blur, slide, fly, rotate, bounce } from '@ssgoi/react/transitions';
/**
* [React] Auto Key Plugin
*
* 使用插件时可以省略 key 属性。
* 基于 文件:行:列 自动生成唯一的 key。
*
* Next.js 配置:
* ```ts
* // next.config.ts
* import SsgoiAutoKey from "@ssgoi/react/unplugin/webpack";
*
* const nextConfig = {
* webpack: (config) => {
* config.plugins.push(SsgoiAutoKey());
* return config;
* },
* };
* ```
*
* ⚠️ 注意: .map() 列表中只需要 JSX key - 插件会自动处理
*
* // .map() 列表中的正确用法:
* {items.map((item) => (
* <div
* key={item.id} // JSX key 就足够了
* ref={transition(fade())}
* >
* {item.name}
* </div>
* ))}
*
* // 不使用插件时,key 是必需的
*/
// Fade过渡
<div ref={transition(fade({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Fade效果
</div>
// Scale过渡
<div ref={transition(scale({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Scale效果
</div>
// Blur过渡
<div ref={transition(blur({ amount: 10, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Blur效果
</div>
// Slide过渡(方向指定)
<div ref={transition(slide({ direction: 'left', physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Slide效果
</div>
// Fly过渡(自定义位置)
<div ref={transition(fly({ x: 200, y: -50, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Fly效果
</div>
// Rotate过渡
<div ref={transition(rotate({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Rotate效果
</div>
// Bounce过渡
<div ref={transition(bounce({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Bounce效果
</div>
// 明确指定 key(不使用插件时必须)
<div ref={transition({
key: "custom-element",
...fade({ physics: { spring: { stiffness: 300, damping: 30 } } })
})}>
自定义 Key
</div>
基本结构
TransitionConfig接口
interface TransitionConfig {
physics?: {
// Spring: 弹簧物理 (ease-out 效果, 默认)
spring?: {
stiffness: number; // 弹簧刚度 (默认: 300)
damping: number; // 阻尼系数 (默认: 30)
doubleSpring?: boolean | number; // 双弹簧效果 (默认: false)
};
// Inertia: 惯性物理 (ease-in 效果)
inertia?: {
acceleration: number; // 加速度 (默认: 500)
resistance: number; // 阻力系数 (默认: 10)
};
// Custom Integrator: 自定义物理引擎
integrator?: () => Integrator;
};
// 选择两种动画模式之一(不能同时使用)
tick?: (progress: number) => void; // 基于RAF的动画
css?: (progress: number) => StyleObject; // 基于Web Animation API(推荐)
prepare?: (element: HTMLElement) => void; // 动画开始前的初始设置
onStart?: () => void;
onEnd?: () => void;
}
物理引擎选项
SSGOI 支持多种物理引擎来实现自然动画:
Spring (弹簧物理)
ease-out 效果的默认物理引擎。动画快速开始,到达目标点时自然减速。
{
in: (element) => ({
physics: {
spring: {
stiffness: 300, // 弹簧刚度 (越高越快)
damping: 30, // 阻尼系数 (越高弹性越小)
doubleSpring: false // 双弹簧效果
}
},
css: (progress) => ({
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}),
})
}
Inertia (惯性物理)
提供 ease-in 效果的物理引擎。动画缓慢开始然后逐渐加速。
{
in: (element) => ({
physics: {
inertia: {
acceleration: 500, // 加速度 (越高越快加速)
resistance: 10 // 阻力系数 (越高越减速)
}
},
css: (progress) => ({
opacity: progress,
transform: `scale(${0.8 + progress * 0.2})`,
}),
})
}
Custom Integrator (自定义物理引擎)
您可以实现并使用自己的物理引擎。
{
in: (element) => ({
physics: {
integrator: () => ({
next: (dt, state) => {
// 自定义物理计算逻辑
return newState;
}
})
},
css: (progress) => ({
opacity: progress,
}),
})
}
动画模式: css vs tick
SSGOI支持两种动画模式:
css模式(推荐)
{
in: (element) => ({
physics: { spring: { stiffness: 300, damping: 30 } },
css: (progress) => ({
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}),
})
}
- 使用Web Animation API最小化主线程负载
- 预计算弹簧物理并转换为关键帧
- GPU加速实现流畅动画
- 性能更好,大多数情况下推荐使用
tick模式
{
in: (element) => ({
physics: { spring: { stiffness: 300, damping: 30 } },
tick: (progress) => {
element.style.opacity = String(progress);
element.style.transform = `translateY(${(1 - progress) * 20}px)`;
},
})
}
- 基于requestAnimationFrame,每帧执行回调
- 适用于需要直接DOM操作的复杂动画
- 当动画期间需要动态更改值时很有用
css和tick不能同时使用。请选择其中一个。
过渡定义
interface Transition {
in?: (element: HTMLElement) => TransitionConfig;
out?: (element: HTMLElement) => TransitionConfig;
}
工作原理
- 挂载时: 当元素添加到DOM时执行
in函数 - 卸载时: 在元素被移除前执行
out函数 - 动画: 弹簧物理引擎生成进度
- in: 0 → 1
- out: 1 → 0
- tick回调: 每帧调用以更新样式
过渡预设
import { fade, scale /** etc */ } from "@ssgoi/react/transitions";
框架特定用法
import { transition } from "@ssgoi/react";
<div
ref={transition({
key: "unique-key",
in: (element) => ({
tick: (progress) => {
element.style.opacity = progress;
element.style.transform = `translateY(${20 * (1 - progress)}px)`;
},
}),
out: (element) => ({
tick: (progress) => {
element.style.opacity = 1 - progress;
},
}),
})}
>
内容
</div>
进度行为
key
key必须在页面内唯一(这样即使DOM被创建后删除或删除后创建,动画状态也可以被跟踪)- React中可以使用Auto Key Plugin自动生成key(详见上方React标签页中的说明)
- 如果
key未设置且未使用插件,则根据调用位置生成默认的自动键
in动画
- progress: 0 → 1
- 当元素出现时执行
- 不透明度从0到1,从小尺寸到原始尺寸
out动画
- progress: 1 → 0
- 当元素消失时执行
- 不透明度从1到0,从原始尺寸到小尺寸
// 示例:in和out的区别
{
in: (element) => ({
tick: (progress) => {
// progress: 0 → 1
element.style.opacity = progress; // 0 → 1
}
}),
out: (element) => ({
tick: (progress) => {
// progress: 1 → 0
element.style.opacity = progress; // 1 → 0
}
})
}
prepare回调
动画开始前准备DOM元素的阶段:
{
in: {
prepare: (element) => {
// 在tick执行前设置初始状态
element.style.willChange = 'opacity, transform';
},
tick: (progress) => ({
opacity: progress,
transform: `translateY(${20 * (1 - progress)}px)`
})
}
}
TransitionScope
TransitionScope是一个控制子元素动画行为的容器。当父元素和子元素同时挂载/卸载时,可以跳过不必要的动画。
使用场景
- 页面过渡: 防止页面过渡时各个元素单独动画
- 模态框/对话框: 防止与容器一起出现的内容重复动画
- 列表: 当整个列表挂载/卸载时跳过单个项目的动画
scope选项
interface TransitionOptions {
// ...
scope?: 'global' | 'local';
}
global(默认): 始终执行动画local: 与TransitionScope同时挂载/卸载时跳过动画
使用示例
import { TransitionScope, transition } from '@ssgoi/react';
function MyComponent() {
const [show, setShow] = useState(true);
return (
<>
<button onClick={() => setShow(!show)}>Toggle</button>
{show && (
<TransitionScope>
<div style={{ padding: '2rem', border: '1px dashed #ccc' }}>
{/* local: 与Scope同时挂载/卸载时跳过动画 */}
<div
ref={transition({
key: 'local-child',
scope: 'local',
in: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
out: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
})}
>
Local Scope(与Scope一起显示/隐藏)
</div>
{/* global: 始终执行动画 */}
<div
ref={transition({
key: 'global-child',
// scope: 'global'是默认值
in: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
out: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
})}
>
Global Scope(始终动画)
</div>
</div>
</TransitionScope>
)}
</>
);
}
行为
| 情况 | scope: 'local' | scope: 'global' |
|---|---|---|
| 与Scope同时挂载 | 跳过动画 | 执行动画 |
| 与Scope同时卸载 | 跳过动画 | 执行动画 |
| 在Scope内单独切换 | 执行动画 | 执行动画 |