动画支持 #
一、CSS 过渡 #
1.1 基本过渡 #
jsx
import styled from 'styled-components';
const Button = styled.button`
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #5a6fd6;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
`;
1.2 多属性过渡 #
jsx
const Card = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition:
transform 0.3s ease,
box-shadow 0.3s ease,
background 0.2s ease;
&:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
background: #fafafa;
}
`;
1.3 过渡曲线 #
jsx
const Box = styled.div`
width: 100px;
height: 100px;
background: #667eea;
border-radius: 8px;
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
&:hover {
transform: scale(1.2) rotate(10deg);
}
`;
const SmoothBox = styled.div`
width: 100px;
height: 100px;
background: #52c41a;
border-radius: 8px;
transition: all 0.4s ease-in-out;
&:hover {
transform: translateX(50px);
}
`;
二、关键帧动画 #
2.1 基本语法 #
jsx
import styled, { keyframes } from 'styled-components';
const fadeIn = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const FadeInBox = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
animation: ${fadeIn} 0.5s ease forwards;
`;
2.2 多步关键帧 #
jsx
const bounce = keyframes`
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
`;
const BouncingBall = styled.div`
width: 50px;
height: 50px;
background: #667eea;
border-radius: 50%;
animation: ${bounce} 1s infinite;
`;
2.3 复杂动画序列 #
jsx
const pulse = keyframes`
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.7);
}
70% {
transform: scale(1.1);
box-shadow: 0 0 0 20px rgba(102, 126, 234, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
}
`;
const PulseButton = styled.button`
padding: 16px 32px;
background: #667eea;
color: white;
border: none;
border-radius: 50px;
font-size: 18px;
cursor: pointer;
animation: ${pulse} 2s infinite;
`;
三、动画复用 #
3.1 定义动画库 #
jsx
import styled, { keyframes, css } from 'styled-components';
const animations = {
fadeIn: keyframes`
from { opacity: 0; }
to { opacity: 1; }
`,
fadeOut: keyframes`
from { opacity: 1; }
to { opacity: 0; }
`,
slideInUp: keyframes`
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
`,
slideInDown: keyframes`
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
`,
slideInLeft: keyframes`
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
`,
slideInRight: keyframes`
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
`,
zoomIn: keyframes`
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
`,
spin: keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`,
shake: keyframes`
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
`,
};
3.2 动画混入函数 #
jsx
const animated = (animation, duration = '0.5s', timing = 'ease') => css`
animation: ${animation} ${duration} ${timing};
`;
const FadeInDiv = styled.div`
${animated(animations.fadeIn)}
padding: 24px;
background: white;
`;
const SlideInCard = styled.div`
${animated(animations.slideInUp, '0.6s', 'cubic-bezier(0.4, 0, 0.2, 1)')}
padding: 24px;
background: white;
border-radius: 12px;
`;
const SpinIcon = styled.span`
${animated(animations.spin, '1s', 'linear')}
display: inline-block;
`;
3.3 条件动画 #
jsx
const AnimatedCard = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
${props => props.animate && css`
animation: ${animations.slideInUp} 0.5s ease forwards;
`}
${props => props.exit && css`
animation: ${animations.fadeOut} 0.3s ease forwards;
`}
`;
function App() {
const [visible, setVisible] = useState(true);
return (
<>
{visible && <AnimatedCard animate exit>Content</AnimatedCard>}
<button onClick={() => setVisible(!visible)}>Toggle</button>
</>
);
}
四、动态动画 #
4.1 基于 Props 的动画 #
jsx
const AnimatedBox = styled.div`
width: 100px;
height: 100px;
background: #667eea;
border-radius: 8px;
animation: ${props => props.animation || animations.fadeIn}
${props => props.duration || '0.5s'}
${props => props.timing || 'ease'}
${props => props.delay ? props.delay : ''};
`;
function App() {
return (
<>
<AnimatedBox animation={animations.slideInUp} duration="0.8s" />
<AnimatedBox animation={animations.zoomIn} duration="0.3s" delay="0.2s" />
</>
);
}
4.2 动态关键帧 #
jsx
const createSlideAnimation = (distance) => keyframes`
from {
opacity: 0;
transform: translateX(${distance}px);
}
to {
opacity: 1;
transform: translateX(0);
}
`;
const SlideInBox = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
animation: ${props => createSlideAnimation(props.distance || 50)} 0.5s ease;
`;
function App() {
return (
<>
<SlideInBox distance={100}>Sliding from right</SlideInBox>
<SlideInBox distance={-100}>Sliding from left</SlideInBox>
</>
);
}
4.3 颜色动画 #
jsx
const colorShift = keyframes`
0% { background: #667eea; }
25% { background: #764ba2; }
50% { background: #f093fb; }
75% { background: #f5576c; }
100% { background: #667eea; }
`;
const GradientBox = styled.div`
width: 200px;
height: 200px;
border-radius: 16px;
animation: ${colorShift} 4s infinite;
`;
五、加载动画 #
5.1 旋转加载器 #
jsx
const spin = keyframes`
to { transform: rotate(360deg); }
`;
const Spinner = styled.div`
width: ${props => props.size || '40px'};
height: ${props => props.size || '40px'};
border: 3px solid #e0e0e0;
border-top-color: #667eea;
border-radius: 50%;
animation: ${spin} 0.8s linear infinite;
`;
const SpinnerContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
`;
function Loading() {
return (
<SpinnerContainer>
<Spinner />
</SpinnerContainer>
);
}
5.2 脉冲加载器 #
jsx
const pulse = keyframes`
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(0.8);
}
`;
const PulseLoader = styled.div`
display: flex;
gap: 8px;
`;
const PulseDot = styled.div`
width: 12px;
height: 12px;
background: #667eea;
border-radius: 50%;
animation: ${pulse} 1s ease-in-out infinite;
animation-delay: ${props => props.delay || '0s'};
`;
function Loading() {
return (
<PulseLoader>
<PulseDot delay="0s" />
<PulseDot delay="0.2s" />
<PulseDot delay="0.4s" />
</PulseLoader>
);
}
5.3 骨架屏 #
jsx
const shimmer = keyframes`
0% {
background-position: -200px 0;
}
100% {
background-position: 200px 0;
}
`;
const Skeleton = styled.div`
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200px 100%;
animation: ${shimmer} 1.5s infinite;
border-radius: ${props => props.radius || '8px'};
width: ${props => props.width || '100%'};
height: ${props => props.height || '20px'};
`;
const SkeletonCard = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 16px;
`;
function LoadingCard() {
return (
<SkeletonCard>
<Skeleton width="60%" height="24px" />
<Skeleton width="100%" height="16px" />
<Skeleton width="80%" height="16px" />
<Skeleton width="40%" height="40px" radius="20px" />
</SkeletonCard>
);
}
六、交互动画 #
6.1 按钮点击效果 #
jsx
const ripple = keyframes`
to {
transform: scale(4);
opacity: 0;
}
`;
const RippleButton = styled.button`
position: relative;
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%) scale(0);
}
&:active::after {
animation: ${ripple} 0.6s ease-out;
}
`;
6.2 悬停效果 #
jsx
const grow = keyframes`
from {
transform: scale(1);
}
to {
transform: scale(1.05);
}
`;
const InteractiveCard = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
&:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
animation: ${grow} 0.3s ease forwards;
}
`;
6.3 焦点动画 #
jsx
const focusRing = keyframes`
0% {
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
}
100% {
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
`;
const FocusInput = styled.input`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.2s;
&:focus {
border-color: #667eea;
outline: none;
animation: ${focusRing} 0.3s ease forwards;
}
`;
七、动画控制 #
7.1 播放状态 #
jsx
const AnimatedIcon = styled.div`
width: 40px;
height: 40px;
background: #667eea;
border-radius: 50%;
animation: ${animations.spin} 1s linear infinite;
animation-play-state: ${props => props.paused ? 'paused' : 'running'};
`;
function App() {
const [paused, setPaused] = useState(false);
return (
<>
<AnimatedIcon paused={paused} />
<button onClick={() => setPaused(!paused)}>
{paused ? 'Play' : 'Pause'}
</button>
</>
);
}
7.2 动画迭代 #
jsx
const BounceBox = styled.div`
width: 100px;
height: 100px;
background: #667eea;
border-radius: 8px;
animation: ${animations.slideInUp} 0.5s ease;
animation-iteration-count: ${props => props.repeat || 1};
`;
const InfiniteBounce = styled.div`
width: 50px;
height: 50px;
background: #52c41a;
border-radius: 50%;
animation: ${animations.bounce} 1s ease infinite;
`;
7.3 动画方向 #
jsx
const AnimatedProgress = styled.div`
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
&::after {
content: '';
display: block;
width: 30%;
height: 100%;
background: #667eea;
animation: ${keyframes`
from { transform: translateX(-100%); }
to { transform: translateX(400%); }
`} 1.5s ease-in-out infinite;
}
`;
八、性能优化 #
8.1 使用 transform 和 opacity #
jsx
const OptimizedCard = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
transition: transform 0.3s, opacity 0.3s;
&:hover {
transform: translateY(-4px);
}
`;
const UnoptimizedCard = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
transition: top 0.3s, left 0.3s;
&:hover {
position: relative;
top: -4px;
}
`;
8.2 will-change 提示 #
jsx
const AnimatedElement = styled.div`
will-change: transform, opacity;
animation: ${animations.fadeIn} 0.5s ease;
`;
8.3 减少重绘 #
jsx
const Container = styled.div`
position: relative;
overflow: hidden;
`;
const AnimatedBackground = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #667eea, #764ba2);
animation: ${keyframes`
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
`} 2s infinite;
z-index: -1;
`;
const Content = styled.div`
position: relative;
padding: 24px;
`;
九、响应式动画 #
9.1 减少动画偏好 #
jsx
const AnimatedBox = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
animation: ${animations.slideInUp} 0.5s ease;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
`;
9.2 设备适配 #
jsx
const ResponsiveAnimation = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
animation: ${animations.slideInUp} 0.5s ease;
@media (max-width: 768px) {
animation-duration: 0.3s;
}
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: none;
}
`;
十、总结 #
动画技巧速查表:
| 技巧 | 示例 |
|---|---|
| 过渡 | transition: all 0.3s ease |
| 关键帧 | keyframes\from {…} to {…}`` |
| 动画应用 | animation: ${fadeIn} 0.5s ease |
| 动画复用 | const fadeIn = keyframes\…`` |
| 动态动画 | animation: ${props => props.anim} |
| 播放控制 | animation-play-state: paused |
| 性能优化 | will-change: transform |
| 无障碍 | @media (prefers-reduced-motion) |
下一步:学习 主题系统 掌握主题切换和设计系统构建。
最后更新:2026-03-28