View Transitions API仅对单页内同步更新已挂载DOM生效,必须用document.startViewTransition()包裹同步操作,view-transition-name须成对、稳定、大小写敏感且元素已挂载。

View Transitions API 不是给 a href 或 location.href 加动画的“开关”,它只对单页内同步更新已挂载 DOM 的操作生效。直接跳转页面、异步加载内容、或命名不一致,都会导致动画完全不触发——不是浏览器不支持,而是没满足它的三个硬性前提。
document.startViewTransition() 必须包裹同步 DOM 更新
这个函数调用后,浏览器会立刻拍“旧快照”,然后**立即执行回调里的 DOM 变更**,再拍“新快照”。一旦回调里出现异步逻辑,快照就断层了。
- ✅ 正确:
document.startViewTransition(() => { main.replaceChildren(newContent); })——replaceChildren是同步 DOM 替换 - ❌ 错误:
document.startViewTransition(() => { fetch('/page').then(r => r.text()).then(html => main.innerHTML = html); })——fetch是异步,回调早已结束 - ⚠️ 注意:
main容器必须已在 HTML 中存在(不能是document.createElement('main')后还没append的节点)
view-transition-name 必须成对、稳定、大小写敏感
90% 的“没动画”问题出在这里。浏览器靠字符串精确匹配来关联新旧元素,任何偏差都会让过渡失效。
- ✅ 正确:旧元素有
style="view-transition-name: hero",新 DOM 中同语义位置的元素也带一模一样的style="view-transition-name: hero" - ❌ 错误:
view-transition-name: item-${id}但前后id不同;或一个写hero,另一个写Hero - ⚠️ 同一时刻页面中不能有两个元素共用同一个
view-transition-name值,否则匹配错乱 - ⚠️ 元素必须已挂载到
document才能被拍进快照 —— 刚createElement还没append的节点不会参与过渡
::view-transition-old 和 ::view-transition-new 的样式必须保可见性
浏览器把旧/新视图分别渲染为独立合成层,但如果旧元素在快照前就被设为不可见,快照就是空的,只剩新元素淡入。
立即学习“前端免费学习笔记(深入)”;
- ❌ 危险:
opacity: 0、visibility: hidden、display: none—— 这些会让旧元素无法绘制,快照失败 - ✅ 安全:
opacity: 0.001占位,或用backdrop-filter: blur(2px)+ 半透背景模拟遮罩 - ⚠️ 自定义动画时,务必统一
animation-duration:如果::view-transition-old(root)动画时长是0.4s,::view-transition-new(root)也得设成0.4s,否则会出现空白帧
调试时必须确认合成层是否创建成功
View Transitions 依赖 GPU 合成层,光写 CSS 不代表它真在跑。最可靠的验证方式是打开 Chrome DevTools 的 Layers 面板,搜索 “View Transition”,看目标元素是否出现在带该标签的独立图层中。
- 如果没看到 —— 很可能
view-transition-name没配对,或元素尚未挂载 - 如果看到但动画卡顿 —— 检查是否有
will-change: transform缺失,或伪元素里用了触发重排的属性(如height、width) - ⚠️
::view-transition-group伪类可用于整体缩放/位移控制,避免遮罩撕裂,但它不接受所有 CSS 属性,慎用clip-path外的变换
真正难的不是写出第一段动画,而是确保每次 DOM 更新都满足“同步 + 已挂载 + 命名一致”这三个条件。尤其在 Vue/React 中,容易下意识把 startViewTransition 放在数据响应式更新之后,却忘了等 nextTick 或 flushSync 确保真实 DOM 已刷新。


















