讲解
transition 是「两个状态之间」的补间,@keyframes 则是「自由编排的剧本」:定义若干关键时刻的样式,浏览器自动补全中间过程。语法分两步——先用 @keyframes 动画名 { ... } 写剧本(from/to 或百分比 0%、50%、100% 关键帧),再在元素上用 animation 属性引用它。子属性包括:animation-name、animation-duration、animation-timing-function、animation-delay、animation-iteration-count(次数,infinite 无限)、animation-direction(alternate 来回播放)、animation-fill-mode(动画结束后停在哪个状态,forwards 停在最后一帧最常用)、animation-play-state(paused 暂停)。
选择标准:状态切换(悬停、开关)用 transition;需要多阶段、循环、自动播放的用 @keyframes。两者可以混用,不冲突。写循环动画(loading 转圈、呼吸灯)时 timing-function 用 linear 更自然;写入场动画用 ease-out,元素「减速到位」符合物理直觉。
性能与可访问性两条铁律和过渡一脉相承:只动画 transform 和 opacity;尊重 prefers-reduced-motion——用户的系统开了「减弱动态效果」,就要停掉非必要动画,尤其是无限循环类,前庭功能障碍用户看旋转动画会真的眩晕。无限动画还有电量代价,页面不可见时浏览器会自动暂停,不用自己处理,但别在一屏里放十几个。
示例
/* loading 转圈:无限循环 + 匀速 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading {
width: 24px;
height: 24px;
border: 3px solid #dee2e6;
border-top-color: #1c7ed6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* 入场淡入上浮:forwards 停在最后一帧 */
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fade-up 0.4s ease-out both; /* both = backwards + forwards */
}
/* 呼吸提示点 */
@keyframes pulse {
50% {
opacity: 0.4;
}
}
.dot {
animation: pulse 1.5s ease-in-out infinite;
}
/* 尊重系统「减弱动效」设置 */
@media (prefers-reduced-motion: reduce) {
.loading,
.dot {
animation: none;
}
}
常见坑
- 动画结束瞬间跳回初始态:没设 animation-fill-mode,默认动画结束后样式回到没有动画的样子。入场动画加 forwards(或 both)。
- @keyframes 名字冲突:全局命名空间,两个同名 @keyframes 后定义的覆盖先定义的。起名带组件前缀,如 card-fade-up。
- 动画布局属性卡成 PPT:animation 里改 height/margin 会每帧触发重排。改写 transform/opacity 版本。
- 百分比与 from/to 混用迷糊:from 即 0%、to 即 100%,可以混写;缺少 0% 或 100% 时浏览器用元素当前样式补全——利用这一点可以只写 to。
- 无限动画无视 reduced-motion:转圈、漂浮、轮播都必须有 prefers-reduced-motion 兜底,这是无障碍硬要求不是可选项。
小结
@keyframes 编剧本、animation 引用;fill-mode 控制收尾状态;循环用 linear、入场用 ease-out;只动画 transform/opacity 并尊重减弱动效设置。下一章学习现代 CSS 的变量系统:自定义属性。