你以为 View Transitions 只能硬编码动画方向?今天 CSS 自己会认路了

每次写 View Transitions 都要在 CSS 里把动画写死——前进后退同一个 fade,加个删除动画要改两处代码——今天 CSS 自己会认导航方向了。

View Transitions 的最大缺陷:浏览器不知道你在往哪走

View Transitions API 出来的时候解决了一个十年难题——不用框架就能做 SPA 级过渡动画。但真上手会发现一个问题:动画是对所有导航一视同仁的。

你从首页点进详情页是一个 fade,从详情页点返回首页还是同一个 fade。用户在博客里点「下一页」,动画方向和点「上一页」完全一样。用户点击「后退」的时候,你没有办法让他感觉真的在往回走。

为什么?因为 ::view-transition-old()::view-transition-new() 是静态的,浏览器快照截完就按你写的跑,没有上下文可言。

动态注册过渡类型,三个 API 搞定

解决这个问题的组合拳是三个浏览器原生 API:

pageswappagereveal 事件

这两个事件在跨文档导航时触发,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 自己认识路了。

评论区

0 条评论

登录后可评论。