css transition动画不触发,根本原因是未满足三个硬性前提:元素已挂载且可绘制、起始与结束值不同、两次样式设置间存在浏览器重排机会;常见于spa中dom未重置、pushstate后无变化感知或view transitions api配对失败。

路由切换时加了 transition 却没动画,不是 CSS 写错,而是 DOM 状态没“重置”或浏览器没拍到过渡快照——常见于单页应用中手动更新内容、history.pushState 后、或 View Transitions API 用法偏差。
transition 动画不触发的三个硬性前提
CSS transition 只响应属性值的**变化过程**,不是类名一加就动。必须同时满足:
- 目标元素已挂载且处于可绘制状态(不能是
display: none或刚createElement未append) - 起始值与结束值不同(比如
opacity: 0→opacity: 1,而非opacity: 1→opacity: 1) - 两次样式设置之间有浏览器重排(re-layout)机会,否则会合并为一次渲染
典型错误写法:el.classList.add('fade-in'); 后立刻进入下一帧——浏览器没机会读旧值。补救方式:在设新类前加 el.offsetHeight 或 void el.offsetWidth 强制同步读取布局。
View Transitions API 的匹配失效点
document.startViewTransition() 不是“给路由加动画”的万能钩子,它只对**同步 DOM 更新**生效,且依赖 view-transition-name 字符串精确配对。
- 旧元素和新元素都必须带相同值的
style="view-transition-name: title"(大小写敏感、无空格、不可动态拼接) - 过渡开始前,旧元素不能提前被设为
opacity: 0或visibility: hidden,否则快照捕获失败,只剩新元素淡入 - 若用框架(如 Vue/React),确保操作的是真实 DOM 节点,或等
nextTick/flushSync完成后再进startViewTransition
检查方法:打开 DevTools → Layers 面板,确认过渡元素是否生成了独立合成层;若没出现 ::view-transition-old 图层,基本就是 view-transition-name 没对上或旧元素不可见。
history.pushState 后如何手动触发入场动画
pushState 不刷新页面,DOM 没重建,浏览器认为“元素一直在这”,加类等于没变。你得模拟一次“进入”流程:
- fetch 新 HTML 后,先插入目标容器(如
main),但保持初始状态:opacity: 0; transform: translateY(20px); - 立即读取一个布局属性(如
main.scrollHeight)触发重排 - 再添加动画类(如
fade-in slide-up),让浏览器感知到变化 - 监听
transitionend时务必校验e.target === element和e.propertyName === 'opacity',避免子元素冒泡干扰
别用 setTimeout 等固定毫秒数——GPU 加速开关、系统缩放、CSS 覆盖都会让实际耗时偏离预设值。
iOS Safari 上 opacity 动画卡顿或闪白
这不是性能差,是 Safari 对纯 opacity 动画的合成策略保守,容易退回到软件渲染,帧率骤降甚至残留白边。
- 优先用
transform: translateX()替代位移类动画,它天然触发 GPU 加速 - 对动画容器加
will-change: transform,提前告知浏览器该元素将变化 - 加上
backface-visibility: hidden,防止 3D 变换翻转面闪烁 - 避免混用
opacity和transform做同一入场动画——iOS 容易错层,节奏难对齐
最常被忽略的一点:动画容器若被父级设了 overflow: hidden,且自身有 transform,可能意外裁剪过渡图层——要检查父容器是否无意中截断了伪元素渲染区域。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











