animation-fill-mode: forwards 必须显式写入 animation 简写中,否则动画结束后样式会恢复原状;若分开写需确保与 animation 同选择器且无覆盖;关键帧必须完整定义 100% 终态,否则 forwards 锁住的是原始值;js 触发时需保证动画执行完毕,且避免高优先级样式覆盖。

animation-fill-mode: forwards 必须显式写进 animation 简写中
不写 forwards,动画播完就撤样式——这不是 bug,是浏览器默认行为。很多人只写 animation: fadeIn 0.3s ease;,再单独加一行 animation-fill-mode: forwards;,结果无效。因为 animation 简写会重置所有未显式声明的子属性,animation-fill-mode 就被强制设回 none。
- 正确写法只有两种:
animation: fadeIn 0.3s ease forwards;(注意分号前有空格) - 或分开写,但必须确保
animation-fill-mode: forwards;和animation在同一选择器、同一 CSS 块里,且后面没出现animation-fill-mode: none;这类覆盖 -
forwards不继承,父元素设了对子元素完全无效
@keyframes 里漏了 100% 就锁不住终态
写了 forwards 却还是闪回,大概率是因为关键帧没定义完整终值。浏览器不会“猜”你想要什么,它只照搬你在 100% 或 to 里写的那几行。漏了,就用元素原始值补,forwards 锁住的就是这个错值。
- 必须显式写出
100% { opacity: 1; transform: translateX(100px) scale(1); },不能只写0%和50% - 移动端尤其要注意:部分 Android WebView 对
to解析不稳定,统一用100%更稳妥 -
transform是复合属性,只写translateX不代表保留原有scale或rotate;想保持就得全写进去
JS 触发动画时最容易忽略的三个时机问题
用 JS 控制 class 添加/移除,是最容易让 forwards “看起来没用”的场景——不是属性失效,而是动画根本没走到终点。
- 刚加
animate类就立刻remove,动画甚至没开始,forwards永远不触发 - 在
animationend事件回调里直接调用getComputedStyle(el).opacity,可能拿到中间值而非终态;建议用requestAnimationFrame延迟一帧再读 - 动态设置内联样式时,只改
el.style.animation = 'slideIn 0.4s'不够,还得同步写el.style.animationFillMode = 'forwards',否则 CSS 文件里的forwards会被内联样式覆盖
高优先级样式会直接覆盖 forwards 保持的值
forwards 只是让动画结束帧“生效”,但它不改变 CSS 层叠规则。任何比它权重更高的样式都会把它压掉。
- 内联样式如
style="opacity: 0"会盖过forwards保持的opacity: 1 - 带
!important的类,或后续更具体的选择器(比如.btn.disabled { opacity: 0; }),同样能覆盖 - 用浏览器开发者工具检查 computed 样式,确认最终生效的是哪条规则;如果
opacity显示为0且来源是某个类,那就不是forwards失效,而是被覆盖了
forwards 本身,而在于它依赖的前提是否成立:动画是否真走到了终点、关键帧是否定义了你想锁住的全部属性、有没有别的样式在它落地前就把它掀翻了。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











