CSS 过渡与动画实战:从 transition 到滚动驱动动画
动效不是"锦上添花",而是界面反馈与品牌感的一部分。CSS 过渡(transition)与动画(@keyframes)让开发者用几行声明式代码实现流畅动效,而不必依赖 JS 逐帧操作。正如 MDN 指出的,相比脚本驱动动画,CSS 动画更省心:无需 JavaScript、在中等负载下也运行良好、浏览器可自动跳过不可见标签页的帧更新。
本文是前端搭建分类的实战内容。动画的深度性能优化可参见Web 动画性能优化;无障碍视角的"减少动态"可参考无障碍指南。
transition:让状态变化更平滑
过渡是"从 A 到 B"的隐式动画:属性发生变化时,浏览器自动补出中间帧。使用 transition 简写配置,包含 property duration timing-function delay 四要素:
a {
background-color: #333; color: #fff;
transition: all 0.5s ease-out;
}
a:hover, a:focus { background-color: #fff; color: #333; }
- 只列出要动的属性:
transition: background-color 0.3s, transform 0.3s,避免all引入意外属性; - 缓动函数:用
cubic-bezier或 easings.net 上的常用曲线,别全部用默认; transitionend事件:JS 里可用它感知过渡结束(需注意display:none或中途改值会中断事件)。
@keyframes:定义动画序列
animation 简写与 @keyframes 一起使用。from/0% 是起点,to/100% 是终点,中间可加任意百分比关键帧:
p {
animation: 3s infinite alternate slide-in;
}
@keyframes slide-in {
from { translate: 150vw 0; scale: 200% 1; }
to { translate: 0 0; scale: 100% 1; }
}
常用子属性:animation-duration(时长)、animation-iteration-count(循环次数,infinite 无限)、animation-direction(alternate 往返)、animation-delay(延迟)、animation-fill-mode(forwards/backwards 控制前后状态)。还可用 animationstart/animationend/animationiteration 事件与 JS 联动。
transition 还是 animation:怎么选
两者定位不同:transition 适合"状态 A 与状态 B 之间切换"(hover、选中、展开收起),由状态变化触发;animation 适合"不受外界触发、自带时间线"的动画(循环的 loading、自动播放的跑马灯、入场后播放的序列)。判断标准很简单:动画是否依赖某个状态变化?依赖则用 transition,否则用 animation。混用时注意 animation 会覆盖同名属性上的 transition,避免写重复规则。把差异列成一张表更好记:
| 维度 | transition | animation |
|---|---|---|
| 触发方式 | 状态变化触发 | 自动播放,自带时间线 |
| 关键帧 | 只有起止两态 | from/to 加任意中间帧 |
| 循环 | 不支持 | 支持 infinite |
| 暂停/倒放 | 不支持 | direction 可控制 |
| 典型场景 | hover、展开、选中 | loading、轮播、入场序列 |
入场与退场动画:display 也能动
过去"淡入淡出 + 移除元素"很麻烦,因为 display:none 无法直接过渡。现在支持浏览器对 display 与 content-visibility 做离散动画:配合 transition-behavior: allow-discrete 与 @starting-style,可以让元素在首次出现时也能平滑过渡(详见 MDN 的 Using CSS transitions):
div {
display: none; opacity: 0;
transition: opacity 1s, display 1s allow-discrete;
}
div.showing {
opacity: 1; display: block;
}
@starting-style { div.showing { opacity: 0; } }
注意:从 display:none 过渡到可见值时,浏览器会在 0% 就切换为可见,从而全程可见地完成淡入;反之在 100% 才隐藏。
一个完整示例:弹层进出
把前面几招组合起来,做一个不依赖 JS 手写帧动画的弹层:
.dialog {
opacity: 0;
transform: translateY(12px);
transition: opacity .25s ease, transform .25s ease,
display .25s allow-discrete;
}
.dialog.open {
opacity: 1;
transform: none;
display: block;
}
@starting-style { .dialog.open { opacity: 0; } }
配合 aria-hidden 与焦点管理,这个弹层打开、关闭时都有平滑过渡:关闭时浏览器会在过渡结束后才真正把元素从布局中移除,避免"瞬间消失"的跳变;首次打开时则靠 @starting-style 补上起点状态。对弹窗这类高频交互,这段代码就是"反馈优先"的具体实现。
滚动驱动动画:让滚动成为时间线
滚动驱动动画(scroll-driven animations,详见 web.dev)允许把滚动进度映射为动画进度。核心是 animation-timeline:
.progress-bar {
animation: grow linear both;
animation-timeline: scroll(); /* 跟随页面滚动 */
}
@keyframes grow { from { width: 0 } to { width: 100% } }
scroll():基于滚动容器的滚动进度;view():基于元素进入视口的时间线,适合"滚动到该区域时播放"的揭示动画;- 可再叠加
view-timeline-name、animation-range精确控制起止范围。
这类动效天然与滚动同步、无需 JS 监听 scroll,性能更好,也更容易做"滚动到哪、进度到哪"的叙事式页面。
性能要点
- 只动合成器属性:优先动画
transform与opacity,避免动画width/height/top等触发布局与绘制; - 慎用
will-change:仅在确有动画时声明,避免长期占用 GPU 内存; - 尊重
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;
}
}
这段规则把所有动画与过渡压到近乎瞬间完成,元素该出现出现、该消失消失,只是不动了,正好照顾到对动态敏感的用户,也不破坏内容本身的可用性。
16IDC 观察
动效的最高性价比用法是"反馈优先":按钮 hover、表单校验、弹层进出——这些高频交互的顺滑度,比首页大 hero 动画更能提升真实体验。把过渡、入场/退场与滚动驱动的组合沉淀成可复用类(配合Tailwind 工具类模板),能显著加快页面开发。记得始终为低性能设备与"减少动态"用户留好退路。
参考:MDN 使用 CSS 动画 https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations · MDN 使用 CSS 过渡 https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions · web.dev 滚动驱动动画 https://web.dev/articles/scroll-driven-animations
原文来源:https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations