Day-17-CSS动画

Day 17: CSS 动画 (CSS Animation)

🎯 学习目标

  • 理解 CSS 动画的基本概念和原理
  • 掌握 @keyframes 规则的使用方法
  • 学会使用 animation 简写属性
  • 能够创建复杂的动画效果和过渡
  • 了解性能优化和最佳实践

💡 核心概念

什么是 CSS 动画?

CSS 动画 是一种使用 CSS 创建元素动态效果的技术,可以让元素从一个样式逐渐变化到另一个样式,创建流畅的视觉体验。

CSS 动画 vs CSS 过渡

特性 CSS 动画 (Animation) CSS 过渡 (Transition)
复杂度 可以创建复杂的多阶段动画 只能在两个状态之间过渡
控制力 可以暂停、反转、控制播放进度 较少的控制选项
关键帧 支持多个关键帧 只有起始和结束状态
JavaScript 交互 提供丰富的 API 接口 较少的 API 支持
性能 同样基于 GPU 加速 同样基于 GPU 加速
适用场景 复杂动画序列、循环动画 简单的状态变化

动画的基本组成部分

/* 1. 定义关键帧 */
@keyframes animationName {
  from { /* 起始状态 */ }
  to { /* 结束状态 */ }
}

/* 2. 应用动画 */
.element {
  animation: animationName 2s ease-in-out infinite;
}

📝 动画属性详解

1. @keyframes – 定义关键帧

语法:

@keyframes animationName {
  /* 百分比表示时间点 */
  0% { /* 起始状态 */ }
  50% { /* 中间状态 */ }
  100% { /* 结束状态 */ }
  
  /* 也可以使用 from 和 to */
  from { /* 等同于 0% */ }
  to { /* 等同于 100% */ }
}

示例:

/* 淡入效果 */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* 移动效果 */
@keyframes moveRight {
  0% {
    transform: translateX(0);
  }
  50% {
    transform: translateX(100px);
  }
  100% {
    transform: translateX(200px);
  }
}

/* 多属性变化 */
@keyframes complex {
  0% {
    transform: scale(1) rotate(0deg);
    opacity: 0;
  }
  50% {
    transform: scale(1.2) rotate(180deg);
    opacity: 1;
  }
  100% {
    transform: scale(1) rotate(360deg);
    opacity: 0;
  }
}

2. animation-name – 动画名称

指定要应用的动画名称(@keyframes 定义的名字)。

.element {
  animation-name: fadeIn;
}

3. animation-duration – 动画时长

设置动画完成一个周期所需的时间。

.element {
  animation-duration: 2s;      /* 2秒 */
  animation-duration: 1000ms;  /* 1000毫秒 */
}

4. animation-timing-function – 时间函数

控制动画的速度曲线。

常用值:

/* 线性速度 */
animation-timing-function: linear;

/* 缓动效果 */
animation-timing-function: ease;        /* 默认值 */
animation-timing-function: ease-in;     /* 慢速开始 */
animation-timing-function: ease-out;    /* 慢速结束 */
animation-timing-function: ease-in-out; /* 慢速开始和结束 */

/* 自定义贝塞尔曲线 */
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);

/* 阶梯函数(离散步骤) */
animation-timing-function: steps(4);       /* 4个步骤 */
animation-timing-function: steps(4, end);  /* 在每个步骤结束跳变 */
animation-timing-function: steps(4, start); /* 在每个步骤开始跳变 */

可视化时间曲线:

linear:     ─────────────
ease:       ╱────────╲
ease-in:    ╱──────────
ease-out:   ──────────╲
ease-in-out:╱───────╲

5. animation-delay – 动画延迟

设置动画开始前的延迟时间。

.element {
  animation-delay: 1s;     /* 延迟1秒后开始 */
  animation-delay: -0.5s;  /* 立即开始,从中间开始播放 */
}

6. animation-iteration-count – 迭代次数

设置动画播放的次数。

.element {
  animation-iteration-count: 3;        /* 播放3次 */
  animation-iteration-count: infinite; /* 无限循环 */
  animation-iteration-count: 2.5;      /* 播放2.5次 */
}

7. animation-direction – 动画方向

设置动画的播放方向。

.element {
  /* 正常方向(默认) */
  animation-direction: normal;
  
  /* 反向播放 */
  animation-direction: reverse;
  
  /* 交替方向:正-反-正-反... */
  animation-direction: alternate;
  
  /* 反向交替:反-正-反-正... */
  animation-direction: alternate-reverse;
}

8. animation-fill-mode – 填充模式

设置动画在播放前后的样式。

.element {
  /* 默认值:动画结束后回到初始状态 */
  animation-fill-mode: none;
  
  /* 动画开始前保持第一帧样式 */
  animation-fill-mode: backwards;
  
  /* 动画结束后保持最后一帧样式 */
  animation-fill-mode: forwards;
  
  /* 同时应用 backwards 和 forwards */
  animation-fill-mode: both;
}

填充模式对比:

none:       [初始] ──动画──> [回到初始]
backwards:  [第一帧] ──动画──> [回到初始]
forwards:   [初始] ──动画──> [最后一帧]
both:       [第一帧] ──动画──> [最后一帧]

9. animation-play-state – 播放状态

控制动画的播放和暂停。

.element {
  animation-play-state: running;  /* 播放(默认) */
  animation-play-state: paused;   /* 暂停 */
}

/* 悬停时暂停动画 */
.element:hover {
  animation-play-state: paused;
}

10. animation – 简写属性

一次性设置所有动画属性。

/* 完整语法 */
animation: name duration timing-function delay iteration-count direction fill-mode play-state;

/* 示例 */
animation: fadeIn 2s ease-in-out 1s infinite alternate forwards running;

/* 分开写更清晰 */
animation-name: fadeIn;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-delay: 1s;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-fill-mode: forwards;
animation-play-state: running;

注意: 简写时,duration 必须在 delay 之前!


🎮 实战示例

示例 1: 基础淡入动画

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>基础淡入动画</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      margin: 0;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      font-family: 'Arial', sans-serif;
    }

    .container {
      text-align: center;
    }

    .box {
      width: 200px;
      height: 200px;
      background: white;
      border-radius: 20px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 24px;
      font-weight: bold;
      color: #333;
      
      /* 应用动画 */
      animation: fadeInScale 1.5s ease-out forwards;
      opacity: 0; /* 初始不可见 */
    }

    @keyframes fadeInScale {
      0% {
        opacity: 0;
        transform: scale(0.5);
      }
      100% {
        opacity: 1;
        transform: scale(1);
      }
    }

    .delay-1 { animation-delay: 0.2s; }
    .delay-2 { animation-delay: 0.4s; }
    .delay-3 { animation-delay: 0.6s; }
  </style>
</head>
<body>
  <div class="container">
    <div class="box delay-1">Hello!</div>
    <div class="box delay-2">CSS</div>
    <div class="box delay-3">Animation</div>
  </div>
</body>
</html>

示例 2: 旋转加载动画

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>旋转加载动画</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      margin: 0;
      background: #1a1a2e;
    }

    .loader {
      width: 100px;
      height: 100px;
      position: relative;
    }

    .loader::before,
    .loader::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      border-radius: 50%;
      border: 4px solid transparent;
    }

    .loader::before {
      border-top-color: #00d2ff;
      border-right-color: #00d2ff;
      animation: spin 1s linear infinite;
    }

    .loader::after {
      border-bottom-color: #ff0055;
      border-left-color: #ff0055;
      animation: spin 1.5s linear infinite reverse;
    }

    @keyframes spin {
      0% {
        transform: rotate(0deg);
      }
      100% {
        transform: rotate(360deg);
      }
    }

    .loading-text {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      color: white;
      font-size: 14px;
      font-weight: bold;
      animation: pulse 1.5s ease-in-out infinite;
    }

    @keyframes pulse {
      0%, 100% {
        opacity: 1;
      }
      50% {
        opacity: 0.5;
      }
    }
  </style>
</head>
<body>
  <div class="loader">
    <span class="loading-text">Loading...</span>
  </div>
</body>
</html>

示例 3: 悬浮卡片效果

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>悬浮卡片效果</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
      font-family: 'Arial', sans-serif;
    }

    .card {
      width: 300px;
      background: white;
      border-radius: 20px;
      overflow: hidden;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
      transition: transform 0.3s ease;
    }

    .card:hover {
      animation: float 3s ease-in-out infinite;
    }

    @keyframes float {
      0%, 100% {
        transform: translateY(0);
      }
      50% {
        transform: translateY(-20px);
      }
    }

    .card-image {
      width: 100%;
      height: 200px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-size: 48px;
    }

    .card-content {
      padding: 20px;
    }

    .card-title {
      font-size: 24px;
      font-weight: bold;
      color: #333;
      margin-bottom: 10px;
    }

    .card-description {
      color: #666;
      line-height: 1.6;
    }

    .card-button {
      display: inline-block;
      margin-top: 15px;
      padding: 10px 20px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      text-decoration: none;
      border-radius: 25px;
      font-weight: bold;
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }

    .card-button:hover {
      transform: scale(1.05);
      box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
    }

    .card-button:active {
      transform: scale(0.95);
    }
  </style>
</head>
<body>
  <div class="card">
    <div class="card-image">🎨</div>
    <div class="card-content">
      <h2 class="card-title">CSS 动画</h2>
      <p class="card-description">
        悬停在卡片上查看浮动效果。CSS 动画让网页更加生动有趣!
      </p>
      <a href="#" class="card-button">了解更多</a>
    </div>
  </div>
</body>
</html>

示例 4: 打字机效果

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>打字机效果</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      margin: 0;
      background: #1e1e1e;
      font-family: 'Courier New', monospace;
    }

    .typewriter {
      font-size: 32px;
      color: #00ff00;
      white-space: nowrap;
      overflow: hidden;
      border-right: 3px solid #00ff00;
      animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite;
    }

    @keyframes typing {
      from {
        width: 0;
      }
      to {
        width: 100%;
      }
    }

    @keyframes blink-caret {
      from,
      to {
        border-color: transparent;
      }
      50% {
        border-color: #00ff00;
      }
    }

    .container {
      text-align: center;
    }

    h1 {
      color: white;
      margin-bottom: 30px;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>打字机效果</h1>
    <div class="typewriter">Hello, World! This is a typewriter effect.</div>
  </div>
</body>
</html>

示例 5: 波浪动画

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>波浪动画</title>
  <style>
    body {
      margin: 0;
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      background: linear-gradient(135deg, #00c6ff 0%, #0072ff 100%);
      font-family: 'Arial', sans-serif;
    }

    .wave-container {
      position: relative;
      width: 200px;
      height: 200px;
    }

    .wave {
      position: absolute;
      width: 100%;
      height: 100%;
      border-radius: 50%;
      border: 2px solid rgba(255, 255, 255, 0.5);
      animation: wave 3s linear infinite;
    }

    .wave:nth-child(1) {
      animation-delay: 0s;
    }

    .wave:nth-child(2) {
      animation-delay: 0.5s;
    }

    .wave:nth-child(3) {
      animation-delay: 1s;
    }

    .wave:nth-child(4) {
      animation-delay: 1.5s;
    }

    .wave:nth-child(5) {
      animation-delay: 2s;
    }

    @keyframes wave {
      0% {
        transform: scale(0.5);
        opacity: 1;
      }
      100% {
        transform: scale(2);
        opacity: 0;
      }
    }

    .center-icon {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 48px;
      color: white;
    }
  </style>
</head>
<body>
  <div class="wave-container">
    <div class="wave"></div>
    <div class="wave"></div>
    <div class="wave"></div>
    <div class="wave"></div>
    <div class="wave"></div>
    <div class="center-icon">🌊</div>
  </div>
</body>
</html>

示例 6: 3D 翻转卡片

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>3D 翻转卡片</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
      font-family: 'Arial', sans-serif;
      perspective: 1000px;
    }

    .flip-card {
      width: 300px;
      height: 400px;
      position: relative;
      transform-style: preserve-3d;
      transition: transform 0.8s;
      cursor: pointer;
    }

    .flip-card:hover {
      animation: flip 1s ease-in-out forwards;
    }

    @keyframes flip {
      0% {
        transform: rotateY(0deg);
      }
      100% {
        transform: rotateY(180deg);
      }
    }

    .flip-card-front,
    .flip-card-back {
      position: absolute;
      width: 100%;
      height: 100%;
      backface-visibility: hidden;
      border-radius: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
    }

    .flip-card-front {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
    }

    .flip-card-back {
      background: white;
      color: #333;
      transform: rotateY(180deg);
    }

    .card-icon {
      font-size: 80px;
      margin-bottom: 20px;
    }

    .card-title {
      font-size: 28px;
      font-weight: bold;
      margin-bottom: 10px;
    }

    .card-text {
      font-size: 16px;
      text-align: center;
      padding: 20px;
      line-height: 1.6;
    }

    .card-button {
      margin-top: 20px;
      padding: 12px 24px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 25px;
      font-size: 16px;
      font-weight: bold;
      cursor: pointer;
      transition: transform 0.2s;
    }

    .card-button:hover {
      transform: scale(1.05);
    }
  </style>
</head>
<body>
  <div class="flip-card">
    <div class="flip-card-front">
      <div class="card-icon">🎴</div>
      <div class="card-title">悬停翻转</div>
      <div class="card-text">把鼠标放在卡片上查看背面</div>
    </div>
    <div class="flip-card-back">
      <div class="card-icon">✨</div>
      <div class="card-title">3D 翻转效果</div>
      <div class="card-text">
        这是一个使用 CSS 3D 变换和动画创建的翻转卡片效果。
      </div>
      <button class="card-button">点击了解</button>
    </div>
  </div>
</body>
</html>

⚠️ 重要注意事项

1. 性能优化

最佳实践:

/* ✅ 推荐:使用 transform 和 opacity */
.element {
  animation: move 2s ease-in-out;
}

@keyframes move {
  to {
    transform: translateX(100px);
    opacity: 0.5;
  }
}

/* ❌ 避免:使用 left/top/width/height */
.element {
  animation: move 2s ease-in-out;
}

@keyframes move {
  to {
    left: 100px;  /* 触发重排,性能差 */
    width: 200px;
  }
}

原因: transformopacity 可以由 GPU 加速,不会触发重排(reflow)。


2. will-change 提示浏览器

/* 提示浏览器该元素将会变化,浏览器可以提前优化 */
.element {
  will-change: transform, opacity;
}

/* 动画结束后清除 */
.element.finished {
  will-change: auto;
}

注意: 不要滥用 will-change,只在需要时使用。


3. 减少重绘和重排

/* ✅ 好:使用复合层 */
.element {
  transform: translateZ(0);  /* 创建新的复合层 */
  animation: slide 2s ease-in-out;
}

/* ❌ 差:频繁触发重排 */
.element {
  animation: changeSize 2s ease-in-out;
}

@keyframes changeSize {
  to {
    width: 200px;  /* 触发重排 */
    height: 200px;
  }
}

4. 动画填充模式

/* 保持动画结束后的状态 */
.element {
  animation: fadeIn 1s ease-out forwards;
}

/* 避免:动画结束后元素回到初始状态 */
.element {
  animation: fadeIn 1s ease-out;  /* 默认 fill-mode: none */
}

5. 响应式设计

/* 在小屏幕上减少动画效果 */
@media (max-width: 768px) {
  .element {
    animation: none;  /* 禁用动画 */
  }
}

/* 或者减少动画时长 */
@media (max-width: 768px) {
  .element {
    animation-duration: 0.5s;  /* 更快的动画 */
  }
}

6. 可访问性

/* 尊重用户的动画偏好设置 */
@media (prefers-reduced-motion: reduce) {
  .element {
    animation: none !important;
    transition: none !important;
  }
}

🎓 实战应用场景

场景 1: 加载动画

.spinner {
  width: 50px;
  height: 50px;
  border: 5px solid #f3f3f3;
  border-top: 5px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

场景 2: 通知提示动画

.notification {
  position: fixed;
  top: 20px;
  right: 20px;
  padding: 15px 25px;
  background: #4caf50;
  color: white;
  border-radius: 5px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  animation: slideIn 0.5s ease-out, fadeOut 0.5s ease-in 2.5s forwards;
}

@keyframes slideIn {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes fadeOut {
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}

场景 3: 按钮点击效果

.button {
  padding: 10px 20px;
  background: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.button::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  transform: translate(-50%, -50%);
  transition: width 0.6s, height 0.6s;
}

.button:active::before {
  width: 300px;
  height: 300px;
}

✍️ 练习任务

基础练习

  1. 淡入动画: 创建一个淡入动画,元素从透明变为不透明
  2. 移动动画: 创建一个元素从左到右移动的动画
  3. 旋转动画: 创建一个元素持续旋转的动画
  4. 缩放动画: 创建一个元素从小变大的动画

进阶练习

  1. 组合动画: 创建一个同时进行旋转、缩放和移动的动画
  2. 关键帧动画: 创建一个使用多个关键帧的复杂动画
  3. 交替动画: 创建一个使用 alternate 方向的往复动画
  4. 无限循环: 创建一个无限循环的加载动画

实战挑战

  1. 导航菜单: 创建一个带有动画效果的下拉菜单
  2. 卡片翻转: 创建一个 3D 翻转卡片效果
  3. 进度条: 创建一个带有动画效果的进度条
  4. 粒子效果: 创建一个简单的粒子爆炸效果

💡 常见问题 FAQ

Q1: CSS 动画和 JavaScript 动画有什么区别?

A:

  • CSS 动画: 声明式、性能好、易于实现、控制较少
  • JavaScript 动画: 命令式、更灵活、控制力强、可能性能较差

选择建议:

  • 简单动画 → CSS 动画
  • 复杂交互 → JavaScript 动画
  • 最佳实践 → 结合使用(CSS 处理简单动画,JS 处理复杂逻辑)

Q2: 如何调试 CSS 动画?

A:

  1. 使用浏览器开发者工具(F12)的 "Animations" 面板
  2. 调整动画速度:animation-duration: 10s(减慢速度)
  3. 使用 animation-play-state: paused 暂停动画
  4. 检查关键帧:在开发者工具中查看 @keyframes 规则

Q3: 动画性能不好怎么办?

A:

  1. 使用 transformopacity(GPU 加速)
  2. 避免使用 lefttopwidthheight
  3. 使用 will-change 提示浏览器
  4. 减少动画元素数量
  5. 使用 requestAnimationFrame(JavaScript)

Q4: 如何让动画只在第一次加载时播放?

A:

.element {
  animation: fadeIn 1s ease-out forwards;
}

/* 使用 JavaScript 添加类名 */
.element.animated {
  animation: fadeIn 1s ease-out forwards;
}
// JavaScript
element.classList.add('animated');

Q5: 动画结束后如何执行 JavaScript 代码?

A:

element.addEventListener('animationend', () => {
  console.log('动画结束');
  // 执行后续操作
});

Q6: 如何创建复杂的多阶段动画?

A:

@keyframes complex {
  0% {
    transform: scale(1) rotate(0deg);
    opacity: 0;
  }
  25% {
    transform: scale(1.2) rotate(90deg);
    opacity: 1;
  }
  50% {
    transform: scale(1) rotate(180deg);
    background: #ff0000;
  }
  75% {
    transform: scale(1.2) rotate(270deg);
    background: #00ff00;
  }
  100% {
    transform: scale(1) rotate(360deg);
    opacity: 0;
  }
}

Q7: 动画在移动设备上卡顿怎么办?

A:

  1. 减少动画复杂度
  2. 使用 transform 代替其他属性
  3. 减少同时动画的元素数量
  4. 在小屏幕上禁用动画
  5. 使用 will-change 创建新的复合层

📚 拓展阅读

  • MDN 文档: CSS Animations
  • caniuse: 查看浏览器兼容性
  • 相关主题: CSS Transitions、CSS Transforms、JavaScript Web Animations API

📝 总结

CSS 动画 是创建网页动态效果的重要工具,掌握后可以让你的网页更加生动有趣。

关键要点:

  1. 使用 @keyframes 定义动画关键帧
  2. 使用 animation 属性应用和控制动画
  3. 优先使用 transformopacity 优化性能
  4. 考虑可访问性和用户体验
  5. 合理使用 animation-fill-mode 保持动画状态

最佳实践:

  • ✅ 使用 GPU 加速属性(transform、opacity)
  • ✅ 使用 will-change 提示浏览器
  • ✅ 尊重用户的动画偏好设置
  • ✅ 在移动设备上简化动画
  • ❌ 避免使用触发重排的属性(left、top、width、height)

下一课: Day 18 – 响应式设计与媒体查询