你以为 View Transitions 只能硬编码动画方向?今天 CSS 自己会认路了
每次写 View Transitions 都要在 CSS 里把动画写死——前进后退同一个 fade,加个删除动画要改两处代码——今天 CSS 自己会认导航方向了。
View Transitions 的最大缺陷:浏览器不知道你在往哪走
View Transitions API 出来的时候解决了一个十年难题——不用框架就能做 SPA 级过渡动画。但真上手会发现一个问题:动画是对所有导航一视同仁的。
你从首页点进详情页是一个 fade,从详情页点返回首页还是同一个 fade。用户在博客里点「下一页」,动画方向和点「上一页」完全一样。用户点击「后退」的时候,你没有办法让他感觉真的在往回走。
为什么?因为 ::view-transition-old() 和 ::view-transition-new() 是静态的,浏览器快照截完就按你写的跑,没有上下文可言。
动态注册过渡类型,三个 API 搞定
解决这个问题的组合拳是三个浏览器原生 API:
pageswap 和 pagereveal 事件
这两个事件在跨文档导航时触发,pagereveal 在新文档展示前,pageswap 在旧文档被替换前。它们身上有一个 event.viewTransition 对象,可以在运行时动态添加类型:
// 在新页面里监听 pagereveal
window.addEventListener("pagereveal", (event) => {
if (!event.viewTransition) return;
// 动态判断:从哪个 URL 来的?是前进还是后退?
const fromURL = navigation.currentEntry?.url;
const toURL = location.href;
const fromPage = new URL(fromURL).pathname;
const toPage = new URL(toURL).pathname;
if (fromPage > toPage) {
event.viewTransition.types.add("backwards");
} else if (fromPage < toPage) {
event.viewTransition.types.add("forwards");
} else {
event.viewTransition.types.add("reload");
}
});
:active-view-transition-type() 伪类
这个 CSS 伪类匹配的是「正在进行的、带有特定类型的 View Transition」。配合刚才动态注册的类型名,CSS 可以针对性地写动画:
/* 默认:简单淡入淡出 */
html:active-view-transition {
&::view-transition-old(root) {
animation: fade-out 0.3s ease-out;
}
&::view-transition-new(root) {
animation: fade-in 0.3s ease-in;
}
}
/* 前进:内容从右侧滑入 */
html:active-view-transition-type(forwards) {
&::view-transition-old(root) {
animation: slide-out-to-left 0.4s ease-in-out;
}
&::view-transition-new(root) {
animation: slide-in-from-right 0.4s ease-in-out;
}
}
/* 后退:内容从左侧滑入 */
html:active-view-transition-type(backwards) {
&::view-transition-old(root) {
animation: slide-out-to-right 0.4s ease-in-out;
}
&::view-transition-new(root) {
animation: slide-in-from-left 0.4s ease-in-out;
}
}
/* 刷新:轻微缩放 */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation: scale-down 0.25s ease-in;
}
&::view-transition-new(root) {
animation: scale-up 0.25s ease-out;
}
}
注意 :active-view-transition 是必须的——只有当页面处于 View Transition 激活状态时,类型选择器才会生效。这是一种双重保险。
SPA 里的 types 参数
同文档过渡里用 startViewTransition() 也可以传类型:
const direction = nextIndex > currentIndex ? "forwards" : "backwards";
document.startViewTransition({
update() {
renderPage(nextIndex);
},
types: [direction],
});
浏览器支持:87% 覆盖率,已进 Baseline
| 浏览器 | 版本 |
|---|---|
| Chrome/Edge | 125+ |
| Safari | 18.2+ |
| Firefox | 147+ |
全球覆盖率 ~87%,Baseline Newly Available。有一点需要注意:event.viewTransition.types.add() 只能在 pagereveal/pageswap 回调里调用,浏览器给的时间窗口只有 10-50ms,错过了就没有。
哪些场景真正用得上
电商列表 → 详情:前进滑入详情、后退滑回列表,让用户有空间感。
分页导航:前进页码动画和后退方向不一样,让用户知道自己在往前翻还是往回翻。
删除/新增内容:同文档过渡里,删除用收缩动画、新增用展开动画,不需要写两套完整的 transition 组件。
下一步:从哪开始
第一步:把 pageswap/pagereveal 监听器加上。先不写动画,先把类型打印出来,确认「前进」「后退」「刷新」能正确区分。
第二步:挑一个最常用的场景,比如列表→详情,把前进/后退两种动画写出来。CSS 写好之后只需要改 JS 里的判断逻辑。
第三步:包 @supports。View Transitions 的设计哲学是「失败静默」——不支持就当它不存在,不会报错:
@supports (view-transition-name: none) {
html:active-view-transition-type(forwards) {
/* ... */
}
}
这个组合拳打完之后,你的 MPA 终于有了「知道自己要去哪」的过渡动画。不是框架在控制,是 CSS 自己认识路了。
评论区
登录后可评论。