配了五年全栈,每次用户点链接都要闪一下白屏——今天这件事被两行 CSS 彻底原生化了

配了五年全栈,每次用户点链接都要闪一下白屏——今天这件事被两行 CSS 彻底原生化了

那个白屏是怎么来的?浏览器发起跨页面导航时,旧文档被卸载,新文档还没开始渲染,中间那段空白就叫 blank time。传统 MPA(多页应用)里没有任何机制能填补这个间隙,这是浏览器的默认行为,不是你的 bug。

为了绕开它,不少团队咬牙上了整套 SPA 架构:React Router 做客户端路由,所有跳转变成 DOM 状态切换,View Transitions 在内存里完成,用户永远看不到白屏。但代价是 SSR 和 SEO 全部放弃,初始加载变慢,框架本身接近 100KB。

两行 CSS,替代一整套 SPA。这是 cross-document View Transitions,Chrome 126(2024年中期)和 Safari 18.2 稳定支持,覆盖全球约 75% 的浏览器用户。

怎么做到的?浏览器在导航前后各截一次图,旧页面快照 + 新页面快照,自动在两张图之间做 cross-fade。过程全在浏览器合成器线程,不走主线程,不卡 JavaScript。两行 CSS 如下,两端页面都要加:

/* 两端页面都放进全局 CSS 文件 */
@view-transition { navigation: auto; }

加完保存,用户点击任意同源链接,浏览器自动接管过渡,完全零 JavaScript。

第一件事:view-transition-name 让元素跨页变形而不是消失。 cross-fade 默认是全屏淡入淡出,但真正有价值的是共享元素变形:列表页的缩略图跳转到详情页时,图片不是消失再出现,而是从原位置直接飞到目标位置。

/* 列表页,product-card.astro */
.product-thumb {
  view-transition-name: product-hero;
}

/* 详情页,product-detail.astro */
.detail-hero {
  view-transition-name: product-hero;
}

点击卡片,用户看到缩略图放大并滑入详情页 Hero 区,像原生 App 一样。完全不需要 JS,不需要组件库,只需要两行 CSS 加两个同名属性。

一个硬约束:view-transition-name 在同一页面内必须唯一,不能有两个元素叫同一个名字。如果是在循环里生成的卡片,需要动态设置:

// 点击时给当前卡片单独设置名字
card.addEventListener('click', () => {
  card.querySelector('.product-thumb').style.viewTransitionName = 'product-hero';
});

第二件事:这些情况 transition 不触发。 navigation: auto 有触发条件:必须是同源导航(跨域不行)、两端页面都声明了 @view-transition、是用户手势触发的链接点击或表单提交。如果页面带 download 属性或者目标是 target=”_blank”,transition 不会触发,新页面直接加载,这是合理的降级行为。

还有一个性能陷阱:新页面必须在 4 秒内完成渲染,否则 transition 会被强制取消。这意味着如果你的服务器 TTFB 本身就很慢,transition 会让旧页面冻结在屏幕上,直到新页面准备好,反而让慢网站显得更慢。正确顺序是先修服务器响应速度,再用 transition 锦上添花。

第三件事:无障碍必须做。 prefers-reduced-motion 是必选项,不是可选项:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }
}

对于前庭功能障碍用户来说,一个卡片从屏幕左边飞到右边的动画可能是晕动症的触发器。框架级方案如 Astro 5 已经默认内置了这个处理,但如果你是自己加的,记得手动补上。

框架支持情况: Astro 的 ClientRouter 组件把这个 API 封装成了指令,transition:name 加在元素上就能用,框架内部自动处理动态命名。React(Canary)和 Next.js 16.2 实验性地引入了 ViewTransition 组件,底层走的是 document.startViewTransition(),但这已经是 SPA 层面的用法了,和纯 CSS 的 cross-document 是两个不同的路径。Next.js App Router 默认是客户端导航,不会触发 cross-document transition,除非你显式配置让某些路由走原生导航。

什么时候用它,什么时候不用。 内容站、博客、文档站(80%以上的场景),从 @view-transition { navigation: auto; } 开始,不需要任何 JavaScript,这是正确的起点。SPA 框架里的状态切换动效、卡片列表到详情页的变形动画,继续用 GSAP 或 Framer Motion,这些库在复杂场景下仍然有优势。如果你要做前进/后退方向区分(向前滑入、向右滑出),需要借助 pageswap 和 pagereveal 两个事件加少量 JS,但这是进阶用法,先把基础打好。

这件事真正的意义是:全栈 MPA 架构(SSR + SEO + 初始加载速度)再也不需要为了过渡动画放弃一整套技术选型了。五年前为了去掉白屏而迁移到 SPA 的团队,现在可以把 transition 看成 MPA 的一个增强层,核心架构完全不用动。Chrome 126 以来这个能力已经稳定了两年,现在 Safari 也跟上了,不是实验性功能,是可以进生产的标准能力。

下一步:打开任意一个 MPA 页面的 DevTools Elements 面板,找到全局 CSS 文件,加两行 @view-transition { navigation: auto; },保存,在同一个 origin 内点击任意链接看效果。快的话,五分钟能跑通第一个 demo。

评论区

0 条评论

登录后可评论。