在现代 Web 开发中,动画和过渡效果不仅仅是视觉上的点缀,它们在提升用户体验、引导用户注意力以及增强交互性方面扮演着重要角色。作为一款广受欢迎的前端框架,React 提供了多种实现动画的方式,从简单的 CSS 类名切换到功能强大的动画库如 React Transition Group 和 Framer Motion。本文将深入探讨这些技术,帮助开发者为 React 应用添加流畅、自然且吸引人的动画效果。
本文的目标读者是希望提升 UI 交互体验的开发者,内容包括以下几个方面:
CSSTransition
和 TransitionGroup
实现过渡和列表动画。通过本文的学习,你将全面掌握 React 中动画和过渡效果的实现方法,并能在实际项目中灵活运用这些技术。
CSS 动画是通过 CSS 样式规则控制 HTML 元素随时间变化的一种技术。它包括两种主要形式:过渡(Transition) 和 关键帧动画(Keyframes)。在 React 中,我们可以通过动态改变元素的类名或样式属性,触发这些动画效果。
React 本身并没有内置的动画系统,但它通过状态管理(State)和条件渲染提供了与 CSS 动画结合的天然支持。我们可以通过 useState
或其他状态管理工具控制组件的状态,然后根据状态变化动态切换 CSS 类名,从而触发动画。
让我们从一个简单的显示/隐藏动画开始:
import { useState } from 'react';
import './styles.css';
function ToggleAnimation() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? '隐藏' : '显示'}
</button>
<div className={`box ${isVisible ? 'visible' : 'hidden'}`}></div>
</div>
);
}
export default ToggleAnimation;
/* styles.css */
.box {
width: 100px;
height: 100px;
background-color: #3498db;
transition: opacity 0.5s ease-in-out;
}
.visible {
opacity: 1;
}
.hidden {
opacity: 0;
}
isVisible
是一个布尔状态,用于控制元素的显示状态。isVisible
的值,box
元素的类名会动态切换为 visible
或 hidden
。transition
属性定义了透明度(opacity
)变化的动画效果,持续时间为 0.5 秒,使用 ease-in-out
缓动函数。如果需要更复杂的动画,可以使用 @keyframes
:
import { useState } from 'react';
import './styles.css';
function BounceAnimation() {
const [isBouncing, setIsBouncing] = useState(false);
return (
<div>
<button onClick={() => setIsBouncing(!isBouncing)}>
{isBouncing ? '停止' : '弹跳'}
</button>
<div className={`ball ${isBouncing ? 'bounce' : ''}`}></div>
</div>
);
}
export default BounceAnimation;
/* styles.css */
.ball {
width: 50px;
height: 50px;
background-color: #e74c3c;
border-radius: 50%;
}
.bounce {
animation: bounce 1s infinite;
}
@keyframes bounce {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-100px);
}
100% {
transform: translateY(0);
}
}
@keyframes
定义了动画的多个阶段,animation
属性控制其执行。当 CSS 动画无法满足复杂需求时,React Transition Group 是一个强大的选择。它是一个轻量级库,提供了 CSSTransition
和 TransitionGroup
两个核心组件,帮助开发者更轻松地实现过渡和列表动画。
首先安装 React Transition Group:
npm install react-transition-group
CSSTransition
组件通过为元素添加特定的类名,控制其进入(enter)和离开(exit)动画。
import { CSSTransition } from 'react-transition-group';
import { useState } from 'react';
import './styles.css';
function FadeTransition() {
const [isShown, setIsShown] = useState(false);
return (
<div>
<button onClick={() => setIsShown(!isShown)}>
{isShown ? '隐藏' : '显示'}
</button>
<CSSTransition in={isShown} timeout={500} classNames="fade">
<div className="box">淡入淡出元素</div>
</CSSTransition>
</div>
);
}
export default FadeTransition;
/* styles.css */
.box {
width: 150px;
height: 150px;
background-color: #2ecc71;
}
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 500ms ease-in-out;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 500ms ease-in-out;
}
in
:布尔值,控制组件的进入或离开状态。timeout
:动画持续时间(毫秒)。classNames
:类名前缀,自动生成如 fade-enter
和 fade-exit-active
等类名。in
为 true
时,依次应用 fade-enter
和 fade-enter-active
类名。in
为 false
时,依次应用 fade-exit
和 fade-exit-active
类名。{isShown && ...
}),需要确保 CSSTransition
的父容器始终存在,否则离开动画不会触发。TransitionGroup
组件用于管理一组动态元素(如列表)的进入和离开动画,通常与 CSSTransition
配合使用。
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { useState } from 'react';
import './styles.css';
function ListTransition() {
const [items, setItems] = useState([1, 2, 3]);
const addItem = () => setItems([...items, items.length + 1]);
const removeItem = (id) => setItems(items.filter((item) => item !== id));
return (
<div>
<button onClick={addItem}>添加项目</button>
<TransitionGroup component="ul" className="list">
{items.map((item) => (
<CSSTransition key={item} timeout={500} classNames="item">
<li>
项目 {item}
<button onClick={() => removeItem(item)}>删除</button>
</li>
</CSSTransition>
))}
</TransitionGroup>
</div>
);
}
export default ListTransition;
/* styles.css */
.list {
list-style: none;
padding: 0;
}
.item-enter {
opacity: 0;
transform: translateX(-100%);
}
.item-enter-active {
opacity: 1;
transform: translateX(0);
transition: all 500ms ease-in-out;
}
.item-exit {
opacity: 1;
transform: translateX(0);
}
.item-exit-active {
opacity: 0;
transform: translateX(100%);
transition: all 500ms ease-in-out;
}
key
属性确保每个列表项的唯一性,便于动画追踪。TransitionGroup
管理所有子元素的动画状态。Framer Motion 是一个专为 React 设计的动画库,以其声明式 API 和强大的功能而著称。它不仅简化了动画的实现,还支持手势、拖拽和复杂动画序列,是 React 动画的首选工具。
安装 Framer Motion:
npm install framer-motion
Framer Motion 提供了一系列 motion
组件(如 motion.div
、motion.li
),通过属性控制动画。
import { motion } from 'framer-motion';
function ScaleAnimation() {
return (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.5, ease: 'easeInOut' }}
style={{ width: 100, height: 100, backgroundColor: '#9b59b6' }}
>
缩放元素
</motion.div>
);
}
export default ScaleAnimation;
initial
:初始状态。animate
:目标状态。transition
:动画配置(如持续时间、缓动函数)。在 React 中,当组件被条件渲染移除时,默认不会有离开动画。Framer Motion 的 AnimatePresence
组件解决了这个问题。
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
import './styles.css';
function Modal() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>打开模态框</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
transition={{ duration: 0.3 }}
className="modal"
>
<h2>模态框标题</h2>
<p>这是一个简单的模态框。</p>
<button onClick={() => setIsOpen(false)}>关闭</button>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default Modal;
/* styles.css */
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
exit
属性定义了离开动画。AnimatePresence
确保组件卸载前执行离开动画。Framer Motion 也非常适合实现列表动画。
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
import './styles.css';
function AnimatedList() {
const [items, setItems] = useState([1, 2, 3]);
const addItem = () => setItems([...items, items.length + 1]);
const removeItem = (id) => setItems(items.filter((item) => item !== id));
return (
<div>
<button onClick={addItem}>添加项目</button>
<ul className="list">
<AnimatePresence>
{items.map((item) => (
<motion.li
key={item}
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 100 }}
transition={{ duration: 0.5 }}
className="list-item"
>
项目 {item}
<button onClick={() => removeItem(item)}>删除</button>
</motion.li>
))}
</AnimatePresence>
</ul>
</div>
);
}
export default AnimatedList;
/* styles.css */
.list {
list-style: none;
padding: 0;
}
.list-item {
padding: 10px;
margin: 5px 0;
background: #ecf0f1;
border-radius: 4px;
}
Framer Motion 支持手势和拖拽动画。例如:
import { motion } from 'framer-motion';
function DraggableBox() {
return (
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
style={{ width: 100, height: 100, backgroundColor: '#e67e22' }}
/>
);
}
export default DraggableBox;
drag
:启用拖拽。dragConstraints
:限制拖拽范围。whileHover
和 whileTap
:定义交互状态的动画。动画的流畅性直接影响用户体验。以下是一些性能优化的关键策略:
transform
(如 translate
、scale
)和 opacity
,这些属性由 GPU 处理,性能更高。left
、top
、width
等触发布局重计算的属性。/* 未优化:触发重排 */
.box {
transition: left 0.5s;
}
.box-active {
left: 100px;
}
/* 优化:使用 GPU 加速 */
.box {
transition: transform 0.5s;
}
.box-active {
transform: translateX(100px);
}
react-window
或 react-virtualized
减少渲染开销。Framer Motion 内置了性能优化:
layout
属性,优化动态布局动画。import { motion } from 'framer-motion';
function LayoutAnimation() {
return (
<motion.div
layout
animate={{ width: '200px' }}
transition={{ duration: 0.5 }}
style={{ height: 100, backgroundColor: '#3498db' }}
/>
);
}
layout
:自动优化布局变化的动画。以下是一个完整的实践案例,展示如何使用 Framer Motion 实现模态框动画和列表过渡效果。
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
import './styles.css';
function ModalExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>打开模态框</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.4, ease: 'easeInOut' }}
className="modal"
>
<h2>欢迎</h2>
<p>这是一个动画模态框示例。</p>
<button onClick={() => setIsOpen(false)}>关闭</button>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default ModalExample;
/* styles.css */
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
import './styles.css';
function ListExample() {
const [items, setItems] = useState(['苹果', '香蕉', '橙子']);
const addItem = () => setItems([...items, `水果 ${items.length + 1}`]);
const removeItem = (item) => setItems(items.filter((i) => i !== item));
return (
<div>
<button onClick={addItem}>添加水果</button>
<ul className="list">
<AnimatePresence>
{items.map((item) => (
<motion.li
key={item}
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="list-item"
>
{item}
<button onClick={() => removeItem(item)}>删除</button>
</motion.li>
))}
</AnimatePresence>
</ul>
</div>
);
}
export default ListExample;
/* styles.css */
.list {
list-style: none;
padding: 0;
}
.list-item {
padding: 10px;
margin: 5px 0;
background: #f1c40f;
border-radius: 4px;
overflow: hidden;
}
现在,让我们完成一个练习任务:实现一个购物车添加动画。目标是点击“添加到购物车”按钮时,商品图片从当前位置飞入购物车图标。
import { motion, useAnimation } from 'framer-motion';
import { useState } from 'react';
import './styles.css';
function ShoppingCartAnimation() {
const [cartItems, setCartItems] = useState(0);
const controls = useAnimation();
const addToCart = async () => {
await controls.start({
x: 200,
y: -100,
scale: 0.5,
opacity: 0,
transition: { duration: 0.6, ease: 'easeInOut' },
});
setCartItems(cartItems + 1);
controls.set({ x: 0, y: 0, scale: 1, opacity: 1 });
};
return (
<div className="cart-container">
<motion.div
className="product"
animate={controls}
style={{ width: 50, height: 50, backgroundColor: '#e74c3c' }}
/>
<button onClick={addToCart}>添加到购物车</button>
<div className="cart-icon">购物车: {cartItems}</div>
</div>
);
}
export default ShoppingCartAnimation;
/* styles.css */
.cart-container {
position: relative;
padding: 20px;
}
.product {
border-radius: 4px;
}
.cart-icon {
position: absolute;
top: 20px;
right: 20px;
font-weight: bold;
}
useAnimation
Hook 提供动画控制。controls.start
执行动画,controls.set
重置状态。在众多 React 动画解决方案中,Framer Motion 因其简单性和强大功能脱颖而出:
相比 React Transition Group,Framer Motion 在实现复杂动画时更简洁高效,是现代 React 开发的理想选择。
本文系统介绍了 React 中的动画和过渡效果实现方法:
希望本文能为你提供全面的指导,让你在 React 项目中自信地实现各种动画效果!
以下是一个完整的 React 动画示例,包含模态框和列表动画:
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React 动画示例title>
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.development.js">script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.development.js">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/framer-motion.umd.js">script>
<script src="https://cdn.jsdelivr.net/npm/@babel/[email protected]/babel.min.js">script>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
.list {
list-style: none;
padding: 0;
}
.list-item {
padding: 10px;
margin: 5px 0;
background: #ecf0f1;
border-radius: 4"EOT;
display: flex;
justify-content: space-between;
align-items: center;
}
button {
padding: 8px 16px;
margin: 5px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #2980b9;
}
style>
head>
<body>
<div id="root">div>
<script type="text/babel">
const { useState } = React;
const { motion, AnimatePresence } = window.FramerMotion;
function Modal() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>打开模态框</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
transition={{ duration: 0.3 }}
className="modal"
>
<h2>模态框</h2>
<p>这是一个简单的模态框动画。</p>
<button onClick={() => setIsOpen(false)}>关闭</button>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
function AnimatedList() {
const [items, setItems] = useState([1, 2, 3]);
const addItem = () => setItems([...items, items.length + 1]);
const removeItem = (id) => setItems(items.filter((item) => item !== id));
return (
<div>
<button onClick={addItem}>添加项目</button>
<ul className="list">
<AnimatePresence>
{items.map((item) => (
<motion.li déput="motion.li"
key={item}
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 100 }}
transition={{ duration: 0.5 }}
className="list-item"
>
项目 {item}
<button onClick={() => removeItem(item)}>删除</button>
</motion.li>
))}
</AnimatePresence>
</ul>
</div>
);
}
function App() {
return (
<div>
<h1>React 动画示例</h1>
<Modal />
<AnimatedList />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
script>
body>
html>
这篇文章和示例代码提供了一个全面的 React 动画指南,从基础到高级技术一应俱全。希望它能帮助你在项目中打造出色的交互体验!