- 前端
- UI组件
【免费下载链接】rsuite
🧱 A suite of React components .
rsuite 的Animation是一组开箱即用的动画组件集合,用于为元素的显示与隐藏赋予平滑的过渡效果。本文以 Animation 官方文档 为主线,结合 组件实现源码 与 单元测试,系统讲解Animation.Fade、Animation.Collapse、Animation.Bounce、Animation.Slide以及可完全自定义的Animation.Transition的用法、Props 与底层状态机原理,帮助你快速在项目里实现高质量的交互动画。
一、Animation 组件总览与导入
Animation组件是一组动画组件的集合,你不需要安装任何额外的动画库,通过配置相关属性即可获得动画效果。它由 5 个子组件组成,全部导出自 src/Animation/index.tsx:
| 子组件 | 说明 |
|---|---|
Animation.Fade | 淡入淡出动画 |
Animation.Collapse | 折叠(高度/宽度收放)动画 |
Animation.Bounce | 弹跳动画 |
Animation.Slide | 滑动动画 |
Animation.Transition | 自定义动画,可接入任意 CSS animation |
从源码可以看出,Animation是一个聚合对象,五个子组件均通过React.forwardRef实现并共享同一个Transition内核:
export const Animation: AnimationAPI = { Transition, Collapse, Fade, Bounce, Slide };使用方式也非常灵活,既可以整体引入,也可以单独按需引入:
import { Animation } from 'rsuite'; // 或按需引入单个组件 import Fade from 'rsuite/Animation/Fade';所有动画子组件的共同特征是:children 既可以是一个普通 React 元素,也可以是一个渲染函数(props, ref) => ReactNode,后者会将className与ref透传给目标节点,便于精确控制动画作用在哪个 DOM 元素上。
二、核心机制:Transition 状态机
在深入各个组件之前,先理解所有动画的底层引擎Transition。它定义了一个五态状态机(见 src/Animation/Transition.tsx):
export enum STATUS { UNMOUNTED = 0, // 组件未挂载(配合 unmountOnExit 使用) EXITED = 1, // 退出完成,已隐藏 ENTERING = 2, // 正在进入 ENTERED = 3, // 进入完成,已显示 EXITING = 4 // 正在退出 }当in属性变化时,组件会在这些状态之间迁移,并在每个阶段切换对应的 CSS class:
| 状态 | 生效的 className | 触发的回调 |
|---|---|---|
exited(退出完成) | exitedClassName | onExited |
exiting(退出中) | exitingClassName | onExit/onExiting |
entering(进入中) | enteringClassName | onEnter/onEntering |
entered(进入完成) | enteredClassName | onEntered |
源码中的performEnter/performExit是状态迁移的核心(Transition.tsx):它们先回调onEnter/onExit,将状态置为ENTERING/EXITING后回调onEntering/onExiting,再通过onTransitionEnd监听节点的transitionend/animationend事件(或超时兜底)完成状态收尾。值得注意的是:
- 监听事件由
animation属性决定:Transition默认监听transitionend,而Bounce、Slide这类基于@keyframes的动画会显式传入animation,通过 utils.ts 中的getAnimationEnd()自动适配webkitAnimationEnd与标准animationend; - 默认
timeout兜底机制保证即使 CSS 动画事件异常,状态机也能按超时时间完成迁移(Transition默认 1000ms); unmountOnExit为 true 时,动画结束后组件会从 DOM 中彻底移除(STATUS.UNMOUNTED时render返回null),这在列表、弹层等场景可减少无谓的 DOM 节点。
下面按组件逐一展开,先看官方示例代码(完整示例位于 docs/pages/components/animation/fragments),再对照 Props 表格与源码原理。
三、Animation.Fade:淡入淡出
Animation.Fade是最常用的基础动画,适用于弹窗遮罩、提示层、内容区切换等任何需要"淡入淡出"的场景。
3.1 官方示例
import { Animation, Button, Card } from 'rsuite'; const AnimatedPanel = React.forwardRef((props, ref) => ( <Card {...props} ref={ref} shaded bordered={false} w={240} h={120} c="white" bg="linear-gradient(45deg, #4CAF50, #2196F3)" > <div> <Card.Header>Fade Animation</Card.Header> <Card.Body>This panel demonstrates a smooth fade transition effect.</Card.Body> </div> </Card> )); const App = () => { const [show, setShow] = React.useState(true); const onChange = () => setShow(!show); return ( <div> <Button appearance="primary" onClick={onChange}> Toggle Fade </Button> <hr /> <div> <Animation.Fade in={show}> {(props, ref) => <AnimatedPanel {...props} ref={ref} />} </Animation.Fade> </div> </div> ); }; ReactDOM.render(<App />, document.getElementById('root'));要点:通过in布尔值控制动画开关,children 使用渲染函数把props和ref转发给内部节点。完整示例见 fade.md。
3.2<Animation.Fade>Props
| Property | Type(Default) | Description |
|---|---|---|
| enteredClassName | string | 组件进入动画完成之后添加的 className |
| enteringClassName | string | 组件开始进入动画时添加的 className |
| exitedClassName | string | 组件退出动画完成之后添加的 className |
| exitingClassName | string | 组件开始退出动画时添加的 className |
| in | boolean | 为 true 时显示组件并播放进入动画 |
| onEnter | (node?: null, Element, Text) => void | 进入动画开始前触发的回调 |
| onEntered | (node?: null, Element, Text) => void | 进入动画完成后触发的回调 |
| onEntering | (node?: null, Element, Text) => void | 进入动画开始时触发的回调 |
| onExit | (node?: null, Element, Text) => void | 退出动画即将开始时触发的回调 |
| onExited | (node?: null, Element, Text) => void | 退出动画完成后触发的回调 |
| onExiting | (node?: null, Element, Text) => void | 退出动画开始时触发的回调 |
| timeout | number(300) | 动画过渡时长 |
| transitionAppear | boolean | 首次渲染即显示时,是否也播放进入动画 |
| unmountOnExit | boolean | 退出后是否将组件从 DOM 中卸载 |
3.3 源码原理
查看 Fade.tsx 可以看到,Fade 本质上是Transition的预配置封装:
<Transition {...propsWithDefaults} ref={ref} timeout={timeout} // 默认 300 className={merge(className, prefix('fade'))} // rs-anim-fade enteredClassName={prefix('in')} // rs-anim-in enteringClassName={prefix('in')} // rs-anim-in />对应的样式在 styles/_fade.scss:
.rs-anim-fade { opacity: 0; transition: opacity var(--rs-anim-fade-duration) linear; pointer-events: none; &.rs-anim-in { opacity: 1; pointer-events: auto; } }从源码结构可以推断:隐藏状态下不仅opacity: 0,还设置了pointer-events: none,避免不可见的层拦截点击;进入时恢复pointer-events: auto。动画时长由 CSS 变量--rs-anim-fade-duration(默认0.15s,见 styles/_variables.scss)控制,而timeout属性则用于 JS 侧的状态机兜底计时。
rsuite 内部组件也大量复用了 Fade:例如 Modal 遮罩 使用<Fade transitionAppear in={open} timeout={backdropTransitionTimeout}>实现弹窗背景层的淡入淡出。
四、Animation.Collapse:折叠展开
Animation.Collapse适用于手风琴面板、抽屉菜单、表单分组等需要"按高度/宽度收起展开"的场景,动画基于元素的实际尺寸测量实现。
4.1 官方示例
示例(见 collapse.md)同时演示了纵向折叠(默认height)与横向折叠(dimension="width")两种模式:
import { Animation, Button, Card } from 'rsuite'; const AnimatedPanel = React.forwardRef((props, ref) => ( <Card {...props} ref={ref} shaded bordered={false} w={240} h={120} c="white" bg="linear-gradient(45deg, #4CAF50, #2196F3)"> <div> <Card.Header>Collapse Animation</Card.Header> <Card.Body w={240}>Demonstrates vertical and horizontal collapse transitions.</Card.Body> </div> </Card> )); const App = () => { const [show, setShow] = React.useState(true); const onChange = () => setShow(!show); return ( <div> <Button appearance="primary" onClick={onChange}> Toggle Collapse </Button> <hr /> <HStack spacing={16} alignItems="flex-start"> <VStack w={240}> <Text>Vertical Collapse</Text> <Animation.Collapse in={show}> {(props, ref) => <AnimatedPanel {...props} ref={ref} />} </Animation.Collapse> </VStack> <VStack w={240}> <Text>Horizontal Collapse</Text> <Animation.Collapse in={show} dimension="width"> {(props, ref) => <AnimatedPanel {...props} ref={ref} />} </Animation.Collapse> </VStack> </HStack> </div> ); };4.2<Animation.Collapse>Props
| Property | Type(Default) | Description |
|---|---|---|
| dimension | 'height' | 'width' | () => ('height' | 'width') | 设置折叠尺寸类型 |
| enteredClassName | string('collapse in') | 组件进入动画完成之后添加的 className |
| enteringClassName | string('collapsing') | 组件开始进入动画时添加的 className |
| exitedClassName | string('collapse') | 组件退出动画完成之后添加的 className |
| exitingClassName | string('collapsing') | 组件开始退出动画时添加的 className |
| getDimensionValue | () => number | 自定义折叠的尺寸数值 |
| in | boolean | 为 true 时显示组件并播放进入动画 |
| onEnter | (node?: null, Element, Text) => void | 进入动画开始前触发的回调 |
| onEntered | (node?: null, Element, Text) => void | 进入动画完成后触发的回调 |
| onEntering | (node?: null, Element, Text) => void | 进入动画开始时触发的回调 |
| onExit | (node?: null, Element, Text) => void | 退出动画即将开始时触发的回调 |
| onExited | (node?: null, Element, Text) => void | 退出动画完成后触发的回调 |
| onExiting | (node?: null, Element, Text) => void | 退出动画开始时触发的回调 |
| role | string | HTML role 属性 |
| timeout | number(300) | 动画过渡时长 |
| transitionAppear | boolean | 首次渲染即显示时,是否也播放进入动画 |
| unmountOnExit | boolean | 退出后是否将组件从 DOM 中卸载 |
4.3 源码原理:基于真实尺寸的收放
Collapse的关键实现(Collapse.tsx)是在 Transition 的各个阶段回调中,动态读写节点的内联尺寸样式:
- 进入阶段:
handleEnter先把height/width设为0;handleEntering读取节点的scrollHeight/scrollWidth作为目标值;handleEntered恢复为auto; - 退出阶段:
handleExit先通过getDimensionValue(默认实现为offsetHeight/offsetWidth + margin之和)取得当前尺寸写入内联样式,再通过triggerBrowserReflow强制浏览器回流,最后handleExiting将尺寸改为0触发过渡; getDimensionValue支持自定义返回数值,测试用例 Collapse.spec.tsx 中就用getDimensionValue={() => 50}验证了退出时高度被设置为50px、进入折叠后变为0px的行为。
dimension既支持字符串也支持返回字符串的函数(测试用例同时覆盖了dimension={() => DIMENSION.WIDTH}的形式),当为width时 className 会追加rs-anim-collapse-horizontal。对应样式见 styles/_collapse.scss,其中.rs-anim-collapsing设置了height: 0; overflow: hidden以及基于--rs-anim-collapse-duration(默认0.35s)的过渡,还兼容tr、tbody等表格行的折叠。
五、Animation.Bounce:弹跳动画
Animation.Bounce提供带弹性曲线(overshoot)的弹跳效果,适合消息提醒、徽标强调、卡片入场等需要"吸引注意力"的场景。
5.1 官方示例
import { Animation, Button, Card } from 'rsuite'; const AnimatedPanel = React.forwardRef((props, ref) => ( <Card {...props} ref={ref} shaded bordered={false} w={240} h={120} c="white" bg="linear-gradient(45deg, #4CAF50, #2196F3)"> <div> <Card.Header>Bounce Animation</Card.Header> <Card.Body>This is a beautiful animated panel that demonstrates the bounce effect.</Card.Body> </div> </Card> )); const App = () => { const [show, setShow] = React.useState(true); const onChange = () => setShow(!show); return ( <div> <Button appearance="primary" onClick={onChange}> Toggle Bounce </Button> <hr /> <div> <Animation.Bounce in={show}> {(props, ref) => <AnimatedPanel {...props} ref={ref} />} </Animation.Bounce> </div> </div> ); };完整示例见 bounce.md。
5.2<Animation.Bounce>Props
| Property | Type(Default) | Description |
|---|---|---|
| enteredClassName | string | 组件进入动画完成之后添加的 className |
| enteringClassName | string | 组件开始进入动画时添加的 className |
| exitedClassName | string | 组件退出动画完成之后添加的 className |
| exitingClassName | string | 组件开始退出动画时添加的 className |
| in | boolean | 为 true 时显示组件并播放进入动画 |
| onEnter | (node?: null, Element, Text) => void | 进入动画开始前触发的回调 |
| onEntered | (node?: null, Element, Text) => void | 进入动画完成后触发的回调 |
| onEntering | (node?: null, Element, Text) => void | 进入动画开始时触发的回调 |
| onExit | (node?: null, Element, Text) => void | 退出动画即将开始时触发的回调 |
| onExited | (node?: null, Element, Text) => void | 退出动画完成后触发的回调 |
| onExiting | (node?: null, Element, Text) => void | 退出动画开始时触发的回调 |
| timeout | number(300) | 动画过渡时长 |
| transitionAppear | boolean | 首次渲染即显示时,是否也播放进入动画 |
| unmountOnExit | boolean | 退出后是否将组件从 DOM 中卸载 |
5.3 源码原理
Bounce.tsx 为Transition传入animation并绑定四组 class:
<Transition {...propsWithDefaults} ref={ref} animation timeout={timeout} enteringClassName={prefix('bounce-in')} // rs-anim-bounce-in enteredClassName={prefix('bounce-in')} exitingClassName={prefix('bounce-out')} // rs-anim-bounce-out exitedClassName={prefix('bounce-out')} />样式定义在 styles/_bounce.scss:bounceIn从opacity: 0; transform: scale(0.8)弹入至scale(1),bounceOut反向弹出,弹性缓动曲线为cubic-bezier(0.68, -0.55, 0.27, 1.55),动画时长0.3s且fill-mode: forwards(见 styles/_mixin.scss 中的animation-commonmixin)。
六、Animation.Slide:滑动动画
Animation.Slide支持从四个方向滑入滑出,默认从右侧滑入,常用于抽屉、侧边栏、通知浮层等场景。
6.1 官方示例
示例(见 slide.md)用一组按钮动态切换placement:
import { Animation, Button, ButtonToolbar, Card } from 'rsuite'; const AnimatedPanel = React.forwardRef((props, ref) => ( <Card {...props} ref={ref} shaded bordered={false} w={240} h={120} c="white" bg="linear-gradient(45deg, #4CAF50, #2196F3)"> <div> <Card.Header>Slide Animation</Card.Header> <Card.Body>This panel demonstrates sliding transitions from different directions.</Card.Body> </div> </Card> )); const App = () => { const [show, setShow] = React.useState(true); const [placement, setPlacement] = React.useState('right'); const onChange = placement => { setShow(!show); setPlacement(placement); }; return ( <div> <ButtonToolbar> <Button appearance="primary" onClick={() => onChange('left')}>Slide Left</Button> <Button appearance="primary" onClick={() => onChange('right')}>Slide Right</Button> <Button appearance="primary" onClick={() => onChange('top')}>Slide Top</Button> <Button appearance="primary" onClick={() => onChange('bottom')}>Slide Bottom</Button> </ButtonToolbar> <hr /> <div> <Animation.Slide in={show} placement={placement}> {(props, ref) => <AnimatedPanel {...props} ref={ref} />} </Animation.Slide> </div> </div> ); };6.2<Animation.Slide>Props
| Property | Type(Default) | Description |
|---|---|---|
| enteredClassName | string | 组件进入动画完成之后添加的 className |
| enteringClassName | string | 组件开始进入动画时添加的 className |
| exitedClassName | string | 组件退出动画完成之后添加的 className |
| exitingClassName | string | 组件开始退出动画时添加的 className |
| in | boolean | 为 true 时显示组件并播放进入动画 |
| onEnter | (node?: null, Element, Text) => void | 进入动画开始前触发的回调 |
| onEntered | (node?: null, Element, Text) => void | 进入动画完成后触发的回调 |
| onEntering | (node?: null, Element, Text) => void | 进入动画开始时触发的回调 |
| onExit | (node?: null, Element, Text) => void | 退出动画即将开始时触发的回调 |
| onExited | (node?: null, Element, Text) => void | 退出动画完成后触发的回调 |
| onExiting | (node?: null, Element, Text) => void | 退出动画开始时触发的回调 |
| timeout | number(300) | 动画过渡时长 |
| transitionAppear | boolean | 首次渲染即显示时,是否也播放进入动画 |
| unmountOnExit | boolean | 退出后是否将组件从 DOM 中卸载 |
| placement | Placement('right') | 组件的放置方向 |
其中placement的类型定义(与 docs/pages/_common/types/placement4.md 一致):
type Placement = 'top' | 'bottom' | 'right' | 'left';6.3 源码原理
Slide.tsx 根据placement动态拼接 class:
const enterClassName = prefix('slide-in', placement); // rs-anim-slide-in-right const exitClassName = prefix('slide-out', placement); // rs-anim-slide-out-right样式见 styles/_slide.scss:.rs-anim-slide-out使用 "Fast out" 缓动cubic-bezier(0.4, 0, 1, 1),.rs-anim-slide-in使用ease-in-out;right/left/top/bottom四个方向分别映射slideInRight/slideOutRight、slideInLeft/slideOutLeft、slideInTop/slideOutTop、slideInBottom/slideOutBottom四组 keyframes。特别值得一提的是源码对 RTL 做了适配:当文档方向为[dir='rtl']时,right与left的滑入滑出动画自动互换,保证在从右到左的语言环境下滑动方向依然符合直觉。
七、Animation.Transition:完全自定义动画
当内置的四种动画无法满足需求时,Animation.Transition允许你通过指定四组 className 接入任意自定义 CSS animation,实现 Zoom、Flip、Rotate 等任意效果。
官方做法是在Transition上配置以下 className:
exitedClassName="custom-exited" exitingClassName="custom-exiting" enteredClassName="custom-entered" enteringClassName="custom-entering"7.1 官方示例:多效果切换
transition.md 中的示例定义了 Zoom、Flip、Rotate、Bounce 四套@keyframes,并通过effect状态拼接出每一套专属的 class:
import { Animation, Button, ButtonToolbar, Card } from 'rsuite'; const AnimatedPanel = React.forwardRef(({ ...props }, ref) => ( <Card {...props} ref={ref} shaded bordered={false} w={240} h={120} c="white" bg="linear-gradient(45deg, #4CAF50, #2196F3)"> <div> <Card.Header>Custom Transition</Card.Header> <Card.Body>Click different buttons to see various animation effects!</Card.Body> </div> </Card> )); const App = () => { const [show, setShow] = React.useState(true); const [effect, setEffect] = React.useState('zoom'); const onChange = newEffect => { setShow(!show); setEffect(newEffect); }; return ( <div> <Styles /> <ButtonToolbar> <Button appearance="primary" onClick={() => onChange('zoom')}>Zoom</Button> <Button appearance="primary" onClick={() => onChange('flip')}>Flip</Button> <Button appearance="primary" onClick={() => onChange('rotate')}>Rotate</Button> <Button appearance="primary" onClick={() => onChange('bounce')}>Bounce</Button> </ButtonToolbar> <hr /> <div> <Animation.Transition exitedClassName={`custom-exited custom-${effect}-exited`} exitingClassName={`custom-exiting custom-${effect}-exiting`} enteredClassName={`custom-entered custom-${effect}-entered`} enteringClassName={`custom-entering custom-${effect}-entering`} in={show} > {(props, ref) => <AnimatedPanel {...props} ref={ref} />} </Animation.Transition> </div> </div> ); };配套的样式模板定义了动画基础节奏与四套效果(节选):
.custom-exiting, .custom-entering { animation: 0.5s cubic-bezier(0.4, 0, 0.2, 1); animation-fill-mode: forwards; } /* Zoom 效果 */ .custom-zoom-exiting { animation-name: zoomOut; } .custom-zoom-entering { animation-name: zoomIn; } @keyframes zoomIn { from { opacity: 0; transform: scale3d(0.3, 0.3, 0.3); } 50% { opacity: 1; } } @keyframes zoomOut { from { opacity: 1; } 50% { opacity: 0; transform: scale3d(0.3, 0.3, 0.3); } to { opacity: 0; } } /* Flip 效果 */ .custom-flip-exiting { animation-name: flipOut; } .custom-flip-entering { animation-name: flipIn; } @keyframes flipIn { from { opacity: 0; transform: perspective(400px) rotate3d(1, 0, 0, 90deg); } 40% { transform: perspective(400px) rotate3d(1, 0, 0, -20deg); } 60% { opacity: 1; transform: perspective(400px) rotate3d(1, 0, 0, 10deg); } 80% { transform: perspective(400px) rotate3d(1, 0, 0, -5deg); } to { transform: perspective(400px); } } @keyframes flipOut { from { transform: perspective(400px); } 30% { opacity: 1; transform: perspective(400px) rotate3d(1, 0, 0, -20deg); } to { opacity: 0; transform: perspective(400px) rotate3d(1, 0, 0, 90deg); } } /* Rotate 效果 */ .custom-rotate-exiting { animation-name: rotateOut; } .custom-rotate-entering { animation-name: rotateIn; } @keyframes rotateIn { from { opacity: 0; transform: rotate3d(0, 0, 1, -180deg) scale(0.3); } 50% { opacity: 1; } } @keyframes rotateOut { from { opacity: 1; } to { opacity: 0; transform: rotate3d(0, 0, 1, 180deg) scale(0.3); } } /* Bounce 效果 */ .custom-bounce-exiting { animation-name: bounceOut; } .custom-bounce-entering { animation-name: bounceIn; } @keyframes bounceIn { from { opacity: 0; transform: scale3d(0.3, 0.3, 0.3); } 20% { transform: scale3d(1.1, 1.1, 1.1); } 40% { transform: scale3d(0.9, 0.9, 0.9); } 60% { opacity: 1; transform: scale3d(1.03, 1.03, 1.03); } 80% { transform: scale3d(0.97, 0.97, 0.97); } to { opacity: 1; transform: scale3d(1, 1, 1); } } @keyframes bounceOut { 20% { transform: scale3d(0.9, 0.9, 0.9); } 50%, 55% { opacity: 1; transform: scale3d(1.1, 1.1, 1.1); } to { opacity: 0; transform: scale3d(0.3, 0.3, 0.3); } } .custom-entered { opacity: 1; } .custom-exited { opacity: 0; }7.2<Animation.Transition>Props
| Property | Type(Default) | Description |
|---|---|---|
| enteredClassName | string | 组件进入动画完成之后添加的 className |
| enteringClassName | string | 组件开始进入动画时添加的 className |
| exitedClassName | string | 组件退出动画完成之后添加的 className |
| exitingClassName | string | 组件开始退出动画时添加的 className |
| in | boolean | 为 true 时显示组件并播放进入动画 |
| onEnter | (node?: null, Element, Text) => void | 进入动画开始前触发的回调 |
| onEntered | (node?: null, Element, Text) => void | 进入动画完成后触发的回调 |
| onEntering | (node?: null, Element, Text) => void | 进入动画开始时触发的回调 |
| onExit | (node?: null, Element, Text) => void | 退出动画即将开始时触发的回调 |
| onExited | (node?: null, Element, Text) => void | 退出动画完成后触发的回调 |
| onExiting | (node?: null, Element, Text) => void | 退出动画开始时触发的回调 |
| timeout | number(1000) | 动画过渡时长 |
| transitionAppear | boolean | 首次渲染即显示时,是否也播放进入动画 |
| unmountOnExit | boolean | 退出后是否将组件从 DOM 中卸载 |
需要注意:Transition的默认timeout为1000ms(见 Transition.tsx 的defaultProps),与其余四个子组件默认 300ms 不同,自定义动画通常需要更充裕的兜底时间。
八、通用行为与最佳实践
8.1 回调执行顺序(测试验证)
Transition的回调有严格的时序:进入时依次为onEnter → onEntering → onEntered,退出时依次为onExit → onExiting → onExited。这一行为由 Transition.spec.tsx 中的单元测试验证,测试分别断言了三个进入回调与三个退出回调都会按序被调用。
8.2 transitionAppear 的取舍
transitionAppear控制"首次渲染且in为 true 时"是否也要播放进入动画:
- 不开启(默认):组件初始即显示,不播放动画,避免页面加载时出现多余的闪烁;
- 开启:挂载时执行一次完整的进入动画,适合弹窗、通知等"出现即强调"的场景。
源码层面,构造函数中props.in && props.transitionAppear时初始状态为STATUS.EXITED,随后在componentDidMount中调用performEnter播放进入动画;测试用例 Transition.spec.tsx 验证了transitionAppear下onEntered会在动画结束后才触发。rsuite 的 Modal 遮罩层(Modal.tsx)正是通过Fade transitionAppear实现弹窗打开时的淡入效果。
8.3 unmountOnExit 与性能
开启unmountOnExit后,退出动画完成时组件会进入UNMOUNTED状态并返回null(不再渲染)。这在列表渲染大量隐藏节点时能显著减少 DOM 数量。但其代价是每次显示都需要重新挂载子树,因此对"频繁开合、内部有重状态"的内容需权衡使用。
8.4 与内置组件的配合
Animation系列组件已深度集成进 rsuite 自身:除上述 Modal 遮罩 使用Fade外,Ripple 水波纹 也使用<Transition in={rippling} enteringClassName={prefix('rippling')} onEntered={handleRippled}>驱动点击波纹扩散动画。你可以参考这些内部用法,在自己的业务组件中做同样的集成。
九、小结
- 选型速查:淡入淡出用
Fade,收放面板用Collapse(dimension切换横/纵),强调提示用Bounce,方向性滑动用Slide(placement选方向),任何其他效果用Transition+ 自定义@keyframes。 - 统一心智模型:五个组件共享同一套"五态状态机 + 四阶段 className + 六事件回调 + timeout 兜底"的架构,学会一个就等于学会了全部。
- 实战建议:需要精确控制"出现"时机请开启
transitionAppear;隐藏内容占位可开启unmountOnExit;自定义动画务必保证 CSSanimation-fill-mode与Transition的timeout兜底时间匹配,避免状态机卡在中间态。
更多源码级细节可继续阅读 src/Animation/Transition.tsx、src/Animation/Collapse.tsx、src/Animation/styles/index.scss 以及测试目录 src/Animation/test。
- 前端
- UI组件
【免费下载链接】rsuite
🧱 A suite of React components .
相关推荐
Element UI 内置过渡动画实战:fade、zoom 与 el-collapse-transition 详解
Element UI 内置过渡动画实战:fade、zoom 与 el collapse transition 详解 Element UI(Element)除了提
前端UI组件设计系统CANN数学算子:BesselI0e函数
BesselI0e 产品支持情况 | 产品 | 是否支持 | |: | : : | | <term Ascend 950PR/Ascend 950DT</ter
算子库人工智能CANNElement Plus 内置过渡动画完全指南:Fade、Zoom 与 Collapse Transition 的原理与实战
Element Plus 内置过渡动画完全指南:Fade、Zoom 与 Collapse Transition 的原理与实战 Element Plus 在官方文
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考