animation-fill-mode: forwards 必须显式设置,因默认值 none 导致动画结束后元素回退初始状态;forwards 使元素停在正向播放末帧,与 alternate 方向相关,且不适用于 transition 或 animation 中断场景。

animation-fill-mode: forwards 为什么必须显式设置
CSS动画默认播完就回退到初始状态,不是bug,是规范设计——animation-fill-mode 默认值是 none,意味着动画只在播放期间影响元素,结束后立刻“撤场”。不加这句,哪怕 @keyframes 最后一帧写了 transform: translateX(100px),动画停住的瞬间元素也会弹回原位。
实操建议:
- 只要需要停在最后一帧,就必须写
animation-fill-mode: forwards,不能依赖 keyframes 里写终态就自动保持 - 它和
animation-direction有关联:如果用了alternate,forwards会让元素停在第偶数轮(即正向播放的末尾),不是所有循环的最后一帧 - 不要和
animation-play-state: paused混用判断“是否结束”——暂停时也触发forwards,但那不是播放完毕
transition 和 animation 在“保持终态”上的根本区别
transition 没有“播放完毕”的概念,它只响应属性变化;而 animation 是独立时序逻辑。所以 transition 要保持终态,靠的是让目标属性值真实生效并留存,比如 hover 结束后仍保留 opacity: 0.5 —— 这得靠 JS 改 class 或直接设 style,不是 CSS 自己“记住”。animation 则靠 forwards 告诉浏览器:“播完别清场,把最后一帧的计算结果挂载到渲染树上”。
常见错误现象:
- 用
transition做“淡入后常驻”,却只在 hover 里写opacity: 0.2,鼠标移开就恢复,误以为缺了类似forwards的开关 - 给同一个元素同时写
transition和animation,结果animation的forwards被transition的属性覆盖,终态被冲掉 - JS 动态添加动画类后立即读
getComputedStyle,此时动画还没开始,forwards还没生效,拿到的是初始值
JavaScript 监听 animationend 后手动 fix 终态的场景
有些情况 forwards 不够用:比如动画中途被 JS 中断(element.style.animation = 'none'),或需要兼容极老 Safari(forwards 在 iOS
实操建议:
- 监听
animationend事件时,检查event.animationName是否匹配,避免多个动画互相干扰 - 补终态别直接改
style,优先复用 keyframes 里已定义的终值,例如从@keyframes slide { to { transform: translateX(200px); } }提取200px写死,而不是凭空猜 - 如果动画含
opacity、visibility等影响交互的属性,补终态后记得同步更新 ARIA 状态或 tabindex,否则屏幕阅读器可能卡在旧状态
flex/grid 容器内动画元素保持终态的布局陷阱
当动画元素是 flex item 或 grid item 时,forwards 虽然保住了视觉位置,但它的几何尺寸(如 width、height)没变,可能导致父容器布局重排失败。比如一个 flex item 用 transform: scaleX(0) 隐藏,forwards 后它“看起来”消失了,但仍在 flex flow 中占原始空间。
使用场景提醒:
- 要真正释放空间,得配合
visibility: hidden或display: none(后者需在animationend后 JS 设置,因为display不可过渡) - 用
transform动画时,forwards只保留 transform 效果,不影响盒模型;但用width、left等触发布局的属性动画,forwards同样只保留终值,父容器不会自动 reflow - Grid 中用
grid-column动画时,forwards无效——CSS Grid 轨道定位不支持动画,所谓“动画”其实是靠 JS 搞的伪帧,终态必须 JS 控制
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











