Skip to content

ReactJS - 动画

动画可以通过使 UI 过渡更平滑、更直观来显著提升用户体验。React 本身没有内置的动画系统,但它提供了 Hook 并能很好地与第三方动画库集成。react-transition-group 是一个管理 CSS 过渡和动画的流行选择。

本章将演示如何使用 react-transition-group 创建基本动画。

react-transition-group 是一个库,它暴露了一些组件用于管理过渡阶段,从而更容易在组件进入或离开 DOM 时实现动画。

使用 npm 或 yarn 安装它:

your-project-directory> npm install react-transition-group
your-project-directory> yarn add react-transition-group

使用 react-transition-group 实现动画依赖于 CSS 过渡或动画。创建一个 CSS 文件(例如,在 src 文件夹中的 animations.css),并将其导入到你的组件或主应用程序文件中。

示例:src/animations.css(我们稍后会填充内容)

并在 App.js 或 main.js 中导入它:

import './animations.css'; // Adjust path as necessary

步骤 3 - 出现动画(CSSTransition)

Section titled “步骤 3 - 出现动画(CSSTransition)”

react-transition-group 中的 CSSTransition 组件有助于在单个组件首次挂载或其 in prop 改变时为其添加动画。

让我们创建一个在出现时淡入的组件。

import React, { useState, useEffect } from 'react';
import { CSSTransition } from 'react-transition-group';
import './animations.css'; // Make sure to create and import this CSS file
function App() {
const [showTitle, setShowTitle] = useState(false);
// Trigger the animation after the component mounts
useEffect(() => {
setShowTitle(true);
}, []);
return (
<div>
<CSSTransition
in={showTitle} // Controls the transition (true to enter, false to exit)
timeout={500} // Duration of the transition in milliseconds
classNames="fade" // Prefix for CSS classes (e.g., fade-enter, fade-enter-active)
appear // Enable appear transition on initial mount
unmountOnExit // Optional: remove the component from DOM when exited
>
<h1>My Animated Element</h1>
</CSSTransition>
</div>
);
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
/* Fade Appear Animation */
.fade-appear {
opacity: 0;
}
.fade-appear-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
/* If you also want enter/exit animations for dynamic show/hide later */
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 500ms ease-in;
}

解释:

  • 我们使用 useState 和 useEffect 在组件挂载后将 showTitle 设置为 true,从而触发 CSSTransition 的 in prop。

  • CSSTransition 的 prop:

    • in:布尔值。当为 true 时,应用进入/出现类。当为 false 时,应用退出类。
    • timeout:动画持续时间(毫秒)。应与你的 CSS 过渡持续时间匹配。
    • classNames:一个字符串前缀。react-transition-group 会附加后缀,如 -appear、-appear-active、-enter、-enter-active、-exit、-exit-active。
    • appear:在初始挂载时启用出现过渡,使用 *-appear 和 *-appear-active 类。
    • unmountOnExit:如果为 true,组件在完成退出后将从 DOM 中移除。

当应用程序加载时,<h1> 元素将淡入。

步骤 4 - 进入和离开动画(TransitionGroup)

Section titled “步骤 4 - 进入和离开动画(TransitionGroup)”

要对列表项(例如,添加或移除项时)进行动画处理,请结合使用 TransitionGroup 和 CSSTransition 组件。

import React, { useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './animations.css'; // Ensure this CSS file includes .item-* classes
import { v4 as uuidv4 } from 'uuid'; // For unique keys: npm install uuid
function App() {
const [items, setItems] = useState([
{ id: uuidv4(), text: 'Item 1...' },
{ id: uuidv4(), text: 'Item 2...' },
]);
const handleAddItem = () => {
const newItemText = prompt('Enter new item text:');
if (newItemText) {
setItems(prevItems => [
...prevItems,
{ id: uuidv4(), text: newItemText },
]);
}
};
const handleRemoveItem = (idToRemove) => {
setItems(prevItems => prevItems.filter(item => item.id !== idToRemove));
};
return (
<div>
<button onClick={handleAddItem}>Add Item</button>
<TransitionGroup component="ul"> {/* component prop defines the wrapping element */}
{items.map(({ id, text }) => (
<CSSTransition
key={id} // Crucial: unique key for each item
timeout={500}
classNames="item"
>
<li onClick={() => handleRemoveItem(id)}>
{text}
</li>
</CSSTransition>
))}
</TransitionGroup>
</div>
);
}
export default App;

src/animations.css (列表动画的补充)

Section titled “src/animations.css (列表动画的补充)”
/* ... (keep .fade-* classes from above if needed) ... */
/* Item Enter/Exit Animation */
.item-enter {
opacity: 0;
transform: translateX(-100px);
}
.item-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 500ms ease-in, transform 500ms ease-in;
}
.item-exit {
opacity: 1;
transform: translateX(0);
}
.item-exit-active {
opacity: 0;
transform: translateX(100px);
transition: opacity 500ms ease-in, transform 500ms ease-in;
}

列表动画解释:

  • 我们使用 uuid 为列表项生成唯一的 ID,这对于 TransitionGroup 正确跟踪项至关重要。通过 npm install uuid 安装它。

  • TransitionGroup:包装多个 CSSTransition 组件。它监控其子项。当添加或移除子项时,TransitionGroup 管理其动画状态。

  • CSSTransition(在 map 内部):每个列表项都包裹在自己的 CSSTransition 中。

    • key prop 必须是唯一的且稳定的,对于每个项都是如此。
    • classNames 设置为 "item",因此将应用像 item-enter、item-enter-active 等 CSS 类。
  • handleAdd 和 handleRemove:这些函数更新 items 状态。React 重新渲染,TransitionGroup 检测到其子项的变化,触发动画。

当你添加一个项时,它将动画进入(从左侧滑入并淡入)。当你点击一个项将其移除时,它将动画退出(向右侧滑出并淡出)。

关于 prompt() 的注意:在演示中使用 prompt() 很简单,但由于其阻塞特性和浏览器样式不一致,通常不建议在生产 UI 中使用。在实际应用中,你会在 React 组件内部使用适当的输入字段。

进一步探索:

react-transition-group 还提供了 Transition 和 SwitchTransition 组件,用于更精细的控制。对于更复杂的基于物理的动画或交互,像 Framer Motion (https://www.framer.com/motion/) 或 React Spring (https://www.react-spring.dev/) 这样的库是极好的选择。请务必参考这些库的官方文档以获取最新信息和高级功能。