返回博客
技术 2025年3月3日 7 分钟阅读 · 1686 字

CSS 动画进阶:从 transition 到 @keyframes 与 will-change

掌握 CSS 动画的核心原理与性能优化,打造流畅的页面交互体验

#CSS #动画 #性能优化 #transition
本文由 AI 辅助生成,经人工审核发布

CSS 动画是前端交互体验的基石。从简单的 hover 过渡到复杂的连续动画,理解 transition、animation、缓动函数与浏览器渲染原理,才能写出既流畅又高效的动画代码。

一、transition:状态过渡

transition 是最基础的 CSS 动画方式,定义属性从一个值到另一个值的过渡过程。

1.1 基本语法

.box {
  transition: transform 0.3s ease 0.1s;
  /* 等价于:property | duration | timing-function | delay */
}

.box:hover {
  transform: scale(1.1);
}

1.2 多属性过渡

.card {
  transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 0.5s ease;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0,0,0,0.15);
  background-color: #f8f9fa;
}

/* 或统一过渡 */
.card {
  transition: all 0.3s ease;  /* 不推荐:性能差 */
}

避免使用 transition: all,它会导致非动画属性也参与过渡计算,影响性能。

1.3 transition 的局限

transition 只能在两个状态间过渡,无法定义中间关键帧,也无法自动循环。需要更复杂的动画时,用 @keyframes

二、@keyframes 与 animation

2.1 定义关键帧

@keyframes bounce {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(-30px); }
  100% { transform: translateY(0); }
}

.element {
  animation: bounce 1s ease-in-out infinite;
}

2.2 animation 简写属性

.element {
  animation:
    bounce          /* name */
    1s              /* duration */
    ease-in-out     /* timing-function */
    0s              /* delay */
    infinite        /* iteration-count */
    alternate       /* direction */
    both            /* fill-mode */
    running;        /* play-state */
}
属性说明常用值
animation-name关键帧名称自定义标识符
animation-duration持续时间0.3s 1s
animation-timing-function缓动函数ease linear cubic-bezier()
animation-delay延迟开始0s 0.5s
animation-iteration-count重复次数1 3 infinite
animation-direction方向normal reverse alternate
animation-fill-mode填充模式none forwards backwards both
animation-play-state播放状态running paused

2.3 百分比关键帧

@keyframes pulse {
  0%   { opacity: 1; transform: scale(1); }
  25%  { opacity: 0.8; transform: scale(1.05); }
  50%  { opacity: 0.6; transform: scale(1.1); }
  75%  { opacity: 0.8; transform: scale(1.05); }
  100% { opacity: 1; transform: scale(1); }
}

2.4 多动画组合

.element {
  animation:
    bounce 1s ease-in-out infinite,
    spin 3s linear infinite;
}

三、缓动函数深入

缓动函数决定了动画的”节奏感”,是区分业余动画和专业动画的关键。

3.1 内置缓动函数

函数效果典型场景
linear匀速加载进度条
ease慢-快-慢(默认)通用过渡
ease-in慢入元素退出
ease-out快出慢停元素进入
ease-in-out两端慢中间快位移动画

3.2 cubic-bezier 自定义曲线

.element {
  /* 自定义贝塞尔曲线:x1, y1, x2, y2 */
  transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

四个参数定义两个控制点,形成动画的速度曲线。y 值可以超出 [0,1] 范围,产生”回弹”效果。

3.3 常用曲线预设

:root {
  /* 弹性回弹 */
  --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* 丝滑减速 */
  --ease-smooth: cubic-bezier(0.25, 0.1, 0.25, 1);
  /* 强力弹出 */
  --ease-snap: cubic-bezier(0.87, 0, 0.13, 1);
  /* 惯性滑动 */
  --ease-inertia: cubic-bezier(0.16, 1, 0.3, 1);
}

.modal {
  transition: transform 0.4s var(--ease-bounce);
}

3.4 steps 阶跃函数

/* 精灵图动画 */
.sprite {
  width: 64px;
  height: 64px;
  background: url('sprite.png') no-repeat;
  animation: walk 0.8s steps(8) infinite;
}

@keyframes walk {
  to { background-position: -512px 0; }
}

四、浏览器渲染与性能优化

4.1 动画性能的核心原则

浏览器渲染流程:JavaScript → Style → Layout → Paint → Composite

要让动画流畅(60fps,每帧 16.7ms),关键是避免触发 Layout(重排)和 Paint(重绘),只用 Composite(合成层)。

属性类型触发阶段性能示例属性
布局属性Layout → Paint → Composite最差width height top left margin
绘制属性Paint → Composite一般color background box-shadow
合成属性Composite最佳transform opacity filter

4.2 只动画 transform 和 opacity

/* 错误:触发重排 */
.bad {
  transition: left 0.3s, top 0.3s;
}
.bad:hover { left: 100px; top: 50px; }

/* 正确:使用 transform,仅触发合成 */
.good {
  transition: transform 0.3s;
}
.good:hover { transform: translate(100px, 50px); }

4.3 will-change 提前告知浏览器

.card {
  will-change: transform;
}

/* 注意:不要滥用,仅在确实需要时使用 */

will-change 告诉浏览器某属性即将变化,浏览器会提前创建独立的合成层。但每个合成层都消耗内存,不应长期对所有元素设置。

最佳实践:动画开始前设置,结束后移除。

/* CSS 方案:hover 时设置 */
.card { will-change: auto; }
.card:hover { will-change: transform; }

/* JS 方案:动态控制 */
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});

element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});

4.4 contain 属性隔离重绘

.card-list {
  contain: layout paint;
  /* 或 */
  contain: strict;  /* 等同于 layout paint size style */
}

contain 告诉浏览器该元素的渲染独立于外部,改变该元素不会影响外部布局,从而减少重排范围。

4.5 硬件加速的陷阱

transform: translateZ(0)will-change: transform 会触发硬件加速,创建 GPU 层。但过多 GPU 层会导致:

  • 内存消耗增大
  • 层合并开销
  • 大面积图层反而降低性能

五、实战动画模式

5.1 模态框淡入缩放

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.modal-overlay.active {
  opacity: 1;
}

.modal {
  transform: scale(0.9) translateY(20px);
  opacity: 0;
  transition: transform 0.3s var(--ease-bounce), opacity 0.3s ease;
}

.modal-overlay.active .modal {
  transform: scale(1) translateY(0);
  opacity: 1;
}

5.2 列表交错入场

.list-item {
  opacity: 0;
  transform: translateY(20px);
  animation: fadeInUp 0.5s ease forwards;
}

.list-item:nth-child(1) { animation-delay: 0.1s; }
.list-item:nth-child(2) { animation-delay: 0.2s; }
.list-item:nth-child(3) { animation-delay: 0.3s; }
.list-item:nth-child(4) { animation-delay: 0.4s; }

@keyframes fadeInUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

5.3 加载骨架屏脉冲

.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
}

@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

六、prefers-reduced-motion 无障碍

部分用户对动画敏感(前庭功能障碍、眩晕症),应尊重系统设置:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

这条媒体查询会禁用所有动画,保留状态变化。它应该出现在全局样式中,确保覆盖所有动画。

6.1 条件性应用动画

.element {
  /* 默认无动画 */
  transition: none;
}

@media (prefers-reduced-motion: no-preference) {
  .element {
    transition: transform 0.3s ease;
  }
}

这种写法更”积极”:默认无动画,仅当用户明确表示不介意时才启用。

七、调试与性能检测

7.1 Chrome DevTools 动画面板

  • Animations 面板:录制和调节动画时间线
  • Performance 面板:查看帧率(FPS)和渲染耗时
  • Layers 面板:查看合成层结构

7.2 帧率检测

let lastTime = performance.now();
let frames = 0;

function checkFPS() {
  frames++;
  const now = performance.now();
  if (now >= lastTime + 1000) {
    console.log(`FPS: ${frames}`);
    frames = 0;
    lastTime = now;
  }
  requestAnimationFrame(checkFPS);
}
checkFPS();

7.3 性能优化清单

检查项说明
是否只用 transform/opacity避免动画布局和绘制属性
will-change 是否合理使用动画时启用,结束后移除
动画是否在主线程合成层动画不阻塞主线程
是否有过多合成层检查 Layers 面板
是否支持 prefers-reduced-motion尊重用户系统设置
动画是否流畅(60fps)Performance 面板查看掉帧情况

总结

CSS 动画的核心原则可以总结为一句话:用最简单的工具解决问题,用最少的属性触发渲染。

  • 简单过渡用 transition,复杂序列用 @keyframes
  • 缓动函数是动画质感的关键,善用 cubic-bezier
  • 性能优化的核心是只动画 transformopacity
  • will-change 是双刃剑,按需使用
  • 始终尊重 prefers-reduced-motion
  • 用 DevTools 检测帧率,确保 60fps 流畅体验

动画不是炫技,而是提升用户体验的工具。流畅、适度、尊重用户偏好的动画,才是好动画。