
一个动画差点让全站白屏
2026-10-07
✨ 心情:😱
# 开发
# BUG
# 前端
今天修出图历史的灯箱时,遇到一个很怪的现象:点开是全透明的,而且关不掉。
一开始以为是状态写错了,查了半天状态没问题——点另一张图,灯箱里的图片会跟着换,说明状态是好的。那为什么关不掉?
真正的根因
最后发现是 requestAnimationFrame 根本不触发。
document.hidden // true
visibilityState // "hidden"
requestAnimationFrame // 当帧都没触发
原因是这个标签页在后台,浏览器在后台不产帧,rAF 也就不会执行。
而 framer-motion 全靠 rAF 推进。rAF 不跑,动画就永远停在第 0 帧——也就是 initial 里的 opacity: 0。
更麻烦的是第二层:AnimatePresence 要等退出动画结束才把子节点卸载。动画不结束,元素就永远留在 DOM 里。所以它不是「看不见」,是「既看不见也关不掉」。
我自己也被同一个坑绊了两次
第一次改法:把 framer-motion 换成 CSS @keyframes。
结果照样冻住——内层的 transform: matrix(0.94…) 明明白白说明 from 帧生效了但没推进。后台标签页连 CSS 动画的时间轴也不走。
这让我想明白了:只要动画的 from 是 opacity: 0,冻住就等于隐形。 跟用什么库无关。
顺着查下去发现是全站的事
同样的写法在项目里有 94 处,分布在 31 个文件。更要命的是 PageTransition 这个组件:
<div style={{ opacity: 0, animation: `${animName} 0.6s ease-out forwards` }}>
{children}
</div>
它包着 41 个页面。也就是说,任何一个页面只要动画没跑起来,整页内容全是空的。
最后的改法
一条规则:动画只动 transform,绝不动 opacity。
- 遮罩层干脆不加任何动画,保证任何时候都不透明
- 位移和缩放可以留 —— 动画卡住最多是位置偏一点、略小一点,内容照常显示
- 不加
animation-fill-mode/forwards/both,这些会把静止状态钉死在某一帧 - 灯箱从
AnimatePresence改成条件渲染,setPicked(null)立刻卸载,任何情况下都关得掉
代价是失去淡入效果。换来的是「任何情况下页面都不会消失」。
教训记一下:动画的第一帧就是元素在动画没跑起来时的样子。 把 opacity: 0 写进第一帧,就等于把「不可见」设成了默认值,然后祈祷动画一定会跑。
