animation-fill-mode: forwards 有效需满足:@keyframes 必须显式定义100%终值、动画须真正结束、无高优先级样式覆盖、与animation同规则声明,且移动端注意display/visibility及Safari渲染问题。

加 animation-fill-mode: forwards 是对的,但只加这一行大概率没用——问题几乎全出在关键帧定义、样式覆盖或 JS 触发方式上。
为什么写了 animation-fill-mode: forwards 还是闪回
根本不是属性写错了,而是浏览器没拿到“可保持的终值”。常见情况包括:
-
@keyframes里漏了100%或to块:比如只写了0%和50%,没写100%,浏览器会用初始值“补”终态,forwards锁住的就是这个错误值 - 动画被
animation-iteration-count: infinite或animation-play-state: paused拦住,根本没“结束”,forwards不触发 - 元素有内联样式(如
style="opacity: 0")或高优先级类(如.disabled { opacity: 0; }),直接覆盖了forwards保持的opacity: 1 - 用了
animation-direction: alternate却没配animation-iteration-count: 1,动画播完停在反向最后一帧(即起始帧),看起来像“回去了”
animation-fill-mode: forwards 必须和 animation 同时生效
它不是独立开关,而是依附于正在运行的动画。很多人把它单独写在另一个 CSS 规则里,或写在被 JS 覆盖的样式块中,结果无效。
- 推荐直接用简写:
animation: slideIn 0.3s ease-out forwards;,避免遗漏或覆盖 - 如果必须分开写,确保
animation-fill-mode和animation-name/animation-duration在同一选择器、同一权重下声明 - JS 动态设置动画时,别只改
element.style.animation,记得同步设element.style.animationFillMode = 'forwards',否则内联样式会丢掉 CSS 文件里的fill-mode
@keyframes 的 100% 必须显式写全所有要保留的属性
forwards 只照搬你在 100% 或 to 里写的那一条,不会“智能合并”或继承原始 transform 值。
立即学习“前端免费学习笔记(深入)”;
- 比如元素原本有
transform: scale(0.95) rotate(2deg),动画只写了to { transform: translateX(100px); },那结束时scale和rotate就会被清空为none - 正确写法是在
100%中一次性写出全部:transform: translateX(100px) scale(0.95) rotate(2deg); - 避免用
to代替100%——部分老旧 Android WebView 对to解析不稳定,显式写100%兼容性更稳
移动端特别要注意的渲染细节
移动端对 transform、display、visibility 更敏感,且 JS 读取时机容易出错:
-
display: none或visibility: hidden会中断动画,导致forwards失效;改用opacity: 0+pointer-events: none更稳妥 - JS 在
animationend回调里直接读getComputedStyle,可能拿不到终值——加一层requestAnimationFrame延迟读取 - Safari ≤14.1 对多属性
transform(如scale+rotate同时存在)偶有渲染残留,可在末帧额外加transform: translateZ(0)强制重绘
真正容易被忽略的是:forwards 只维持动画结束那一刻的计算值;如果父容器尺寸变化、媒体查询切换或视口滚动,这些外部变动仍会触发重排,让“保持住的样式”看起来又变了——这时候问题已不在 animation-fill-mode,而在响应式上下文本身。


















