根本原因是animation-fill-mode: forwards生效需满足动画真正执行完毕且末帧属性未被覆盖,常见问题包括无限循环、duration为0、关键帧漏属性、CSS层叠覆盖、前缀写法不当、JS读取时机错误及与transition冲突。

animation-fill-mode: forwards 写了但动画一停就闪回
根本原因不是属性写错了,而是 forwards 的生效有明确前提:动画必须真正执行完毕,且末帧声明的属性不能被覆盖。
常见卡点如下:
-
animation-iteration-count是infinite或负值 —— 动画永不停止,forwards不触发 -
animation-duration为0s(或压根没写)—— 浏览器认为“动了 0 秒”,直接跳过播放,末帧不应用 - 关键帧里漏写了某个属性,比如只设了
transform却没提opacity——forwards只保留你明确定义的那些值,不会继承、不会猜 - 动画结束后,其他 CSS 规则(如类名、标签选择器、内联样式)又把同一属性重设了 —— 层叠优先级赢了,
forwards白搭
用了 -webkit-animation-fill-mode 但没效果
现代浏览器(Chrome ≥ 74、Firefox ≥ 63、Safari ≥ 12.1、Edge ≥ 79)已完全支持无前缀的 animation-fill-mode。如果同时写了带前缀和不带前缀的版本,后者会覆盖前者;但如果只写了 -webkit-animation-fill-mode: forwards,而 animation 简写里没包含 fill-mode,那它很可能被忽略。
正确做法是统一用标准语法:
立即学习“前端免费学习笔记(深入)”;
.box {
animation: slideIn 0.4s ease-out;
animation-fill-mode: forwards; /* 不要只写 -webkit- 版本 */
}顺便检查:animation 简写中是否漏掉了 duration?比如 animation: slideIn; 就等价于 animation-duration: 0s,动画不会播。
JS 控制动画时 getComputedStyle 读不到末帧值
在 animationend 事件回调里立刻调用 getComputedStyle(el).transform,大概率拿到的是初始值或中间值,不是 forwards 承诺的终值。这是因为渲染管线还没完成提交,计算样式尚未更新。
安全读取方式:
- 用
requestAnimationFrame延迟到下一帧再读:requestAnimationFrame(() => console.log(getComputedStyle(el).transform)) - 避免用
el.style.animation = 'none'强制中断动画 —— 这会立即丢弃forwards效果,元素回归内联样式或层叠值 - 若用 JS 动态设置动画,记得把
animation-fill-mode也一并设上,别只依赖 CSS 规则(JS 设置的animation值会整体覆盖 CSS 中的简写)
和 transition 同时存在时状态混乱
animation-fill-mode 和 transition 机制完全不同:前者锁定动画末帧,后者响应属性变化做渐变。两者混用极易冲突。
典型表现:
- 动画结束保持了
transform: scale(1.2),但紧接着 JS 给元素加了个style.transform = 'scale(0.8)'—— 后者直接覆盖,forwards失效 - 元素既有
transition: transform 0.2s,又触发了animation,动画一结束,transition 突然把元素从末帧“滑”回初始值 —— 因为 transition 监听到属性变化,而动画末帧改变了 computed 值 - hover 触发动画后想保持高亮?别硬套
forwards,改用 class 切换 +transition更可控
最易被忽略的一点:动画末帧若触发了布局(比如改了 height 或 top),即使 fill-mode 生效,仍会引发重排 —— 这跟 forwards 无关,但会影响性能感知。


















