动画支持 #

一、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