直接用transition给页面加动画根本不会动,因为transition只作用于单个元素的opacity、transform等原生可插值属性,而页面切换是dom替换或类名变更,非属性线性变化;css变量不可动画,浏览器完全无视;真正可行的是document.startviewtransition()(chrome/edge/firefox已支持)或@keyframes+状态类名控制容器动画。

直接用 transition 给页面加动画根本不会动
因为 transition 只作用于单个元素的属性变化(比如 opacity、transform),而页面切换本质是 DOM 结构替换或类名批量变更,不是某个属性“从 A 到 B”的线性插值。你写 transition: all 0.3s 在 body 上,或者给 :root 的 CSS 变量加 transition,浏览器完全无视——--theme-color 这类自定义属性本身不可动画,标准明确禁止。
常见错误现象包括:点击后无动画、闪一下就切完、transitionend 事件永不触发、控制台静默失败。
- SCSS 里用
@mixin封装transition属性没用,除非它最终落在可动画的原生属性上 - 不要试图靠改一堆
var(--xxx)然后等过渡生效——变量只是占位符,动画必须落到background-color: var(--bg)这种实际渲染属性上 - 页面级切换必须区分“状态变更”和“动画执行”,二者不能异步分离
document.startViewTransition() 是当前唯一靠谱的原生方案
它不是锦上添花的动效补丁,而是强制你把 DOM 更新逻辑包进回调里,由浏览器统一调度快照与合成帧。兼容性已覆盖 Chrome 111+、Edge 111+、Firefox 124+(2026 年 9 月数据),Safari 尚未支持但有明确实现路线图。
关键约束:
- DOM 更新必须同步完成:不能先发 API 请求,再异步插入新内容;必须在回调内直接修改
innerHTML、classList或调用同步渲染函数 - 不能在 React/Vue 的 effect 或
setState后再调用——得用flushSync(React)或nextTick+ 强制同步更新(Vue)确保 DOM 已落地 - SCSS 可用于生成
::view-transition-old(root)和::view-transition-new(root)的样式,但动画定义必须用原生@keyframes,不能依赖 SCSS 变量做动画主体
最小可行示例(纯 JS + 原生 CSS):
button.addEventListener('click', () => {
document.startViewTransition(() => {
document.documentElement.classList.toggle('theme-dark');
});
});
对应 SCSS 片段(仅负责样式组织,不参与动画逻辑):
::view-transition-old(root) {
animation: fade-out 0.35s ease-out;
}
::view-transition-new(root) {
animation: fade-in 0.35s ease-in;
}
@keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
退而求其次:用 @keyframes + 类名控制页面容器动画
当需要兼容 Safari 或服务端渲染首屏动画时,只能手动管理页面容器的进入/离开状态。核心是「谁播、播什么、播多久」三者严格绑定,且必须用 GPU 加速属性。
- 动画必须绑定在语义化类名上,如
.page-enter-active、.page-leave-active,不能直接写在.page上 - 务必加
animation-fill-mode: both,否则动画结束瞬间transform会回退到初始值,造成跳变 - 只用
transform(translateX、scale)和opacity;禁用height、top、margin,它们触发 Layout,卡顿明显 - SCSS 可用
@extend或@include复用动画声明,但不能把动画逻辑藏在嵌套规则里(比如.page { &.enter { ... }容易漏掉animation-fill-mode)
例如滑动切换的 SCSS 片段:
.page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transform: translateX(100%);
}
.page-enter-active,
.page-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
transform: translateX(100%);
}
.page-enter-to,
.page-leave-from {
opacity: 1;
transform: translateX(0);
}
为什么翻页/3D 转场容易出问题
翻页效果依赖 perspective + rotateY + backface-visibility,看似炫酷,实则对 DOM 结构、z-index 层级、渲染上下文要求极严。一个常见坑是:两页内容共用同一个父容器,但未设 transform-style: preserve-3d,导致旋转时背面不可见或透视失效。
性能上更敏感:3D 变换虽走 GPU,但若页面内有大量非合成层元素(如未设 will-change: transform 的兄弟节点),仍可能触发重绘风暴。
- 必须为翻页容器显式设置
perspective(建议 1000px~2000px),且不能写在动画 class 里,得放在基础样式中 -
backface-visibility: hidden要加在每一页内容自身上,不是父容器 - SCSS 中避免用
@for动态生成多页动画——浏览器无法复用帧缓存,每页都新建动画实例,内存暴涨 - 移动端需额外处理 touch 事件中断动画,否则用户快速滑动时动画队列堆积,出现撕裂或延迟响应
真正难的不是写出翻页动画,而是让每次切换都稳定落在 60fps,且不因 DOM 批量更新、样式计算或布局抖动而掉帧。这点上,startViewTransition() 的原生调度优势无法替代。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











