animation-fill-mode: forwards 是阻止闪回的唯一合法手段,但需确保 @keyframes 含 100%/to、动画真正结束、简写中显式包含 forwards、终态属性全部明确定义,且避开 iOS/Android 兼容性陷阱。

animation-fill-mode: forwards 是唯一能阻止闪回的合法手段,不加它,动画一停就还原——这不是 bug,是 CSS 动画规范定义的默认行为。
为什么写了 animation-fill-mode: forwards 还是闪回
根本不是属性写错了,而是浏览器没拿到“可保持的终值”。常见情况包括:
-
@keyframes里漏了100%或to块:比如只写了0%和50%,浏览器会用元素原始transform补全终态,forwards锁住的就是这个错误值 - 动画根本没“结束”:比如
animation-iteration-count: infinite,或被animation-play-state: paused拦在中途,forwards不触发 - 内联样式(如
style="opacity: 0")或高优先级类(如.disabled { opacity: 0 !important; })直接覆盖了forwards保持的值 - 用了
animation-direction: alternate却没配animation-iteration-count: 1,动画播完停在反向最后一帧(即起始帧),看起来像“回去了”
animation 简写中必须显式包含 forwards
animation 简写会重置所有未写出的子属性,包括把 animation-fill-mode 强制设回 none。很多人只写 animation: fadeIn 0.3s ease;,以为单独补一句 animation-fill-mode: forwards; 就行,实际无效。
- 正确写法一(推荐):
animation: fadeIn 0.3s ease forwards;(注意分号前有空格) - 正确写法二:分开写,但必须确保
animation-fill-mode: forwards;和animation在同一选择器、同一权重下,且后面没出现animation-fill-mode: none;类似重置 - JS 动态设置时别只改
element.style.animation,记得同步设element.style.animationFillMode = 'forwards'
@keyframes 中必须显式写出所有需保留的终态属性
forwards 不会补全、不继承、不推断——它只保留你在 100% 或 to 里明确定义的声明。例如:
立即学习“前端免费学习笔记(深入)”;
- 元素原本有
transform: scale(0.9) rotate(2deg),动画只写to { transform: translateX(100px); }→ 结束时scale和rotate都归none - 正确写法是:
to { transform: translateX(100px) scale(0.9) rotate(2deg); } - 同理,
opacity、filter、color等只要你想保留,就必须在100%中显式写出 - 移动端建议统一用
100%而非to,部分老旧 Android WebView 对to解析不稳定
iOS Safari 和旧版 WebView 的硬性限制
即使上述都做对了,iOS Safari(尤其 16.4 之前)和旧版 Android WebView 仍可能失效,典型诱因有:
- 动画中同时修改
display(如none → block):iOS 会直接忽略forwards,强制重置 -
animation-name是 JS 动态插入但未及时生效的@keyframes,forwards无作用 - 依赖
getComputedStyle(el).transform判断动画是否到位:该值永远是初始声明值(如none),不是终态计算值,几乎总是错的 -
will-change: transform可提升渲染稳定性,但滥用会引发额外图层和内存开销,慎加
真正麻烦的不是加不加 forwards,而是它只管动画层,不管布局层——如果动画结束后的状态依赖父容器尺寸、媒体查询或滚动位置,已“定格”的样式可能在重排后意外失效。


















