曾几何时,想给网站加一个"点卡片 → 大图展开"的流畅过渡,或者"滚动到哪里、动画播到哪里"的效果,默认动作就是安装依赖:React 项目上 Framer Motion,通用页面上 GSAP + ScrollTrigger,老派一点用 Barba.js。它们确实好用,但代价是每个页面都多出几十到几百 KB 的 JavaScript,以及一层横在你和浏览器原生能力之间的抽象。

到了 2026 年,这件事终于变了:View Transitions API(视图过渡 API)和滚动驱动动画(Scroll-Driven Animations)已经在 Chrome、Edge、Firefox、Safari 四大浏览器全面落地,同文档视图过渡在 2025 年底进入 Baseline,跨文档过渡随 Safari 18.2 补齐最后一块拼图,Interop 2026 更把两者都列为重点互操作领域。本文用一套可以直接复制的代码,把这两个原生 API 讲清楚。

View Transitions 是什么:浏览器替你补间

视图过渡的核心思路非常简单:你只管正常修改 DOM,浏览器在修改前后各截一张"快照",自动在两张快照之间做动画。

最基础的用法,同文档(SPA 内视图切换)只需要把 DOM 更新包进 document.startViewTransition()

1
2
3
4
5
6
7
8
9
10
function switchView(newHTML) {
// 不支持的浏览器直接更新,没有动画但功能完整
if (!document.startViewTransition) {
document.getElementById('app').innerHTML = newHTML
return
}
document.startViewTransition(() => {
document.getElementById('app').innerHTML = newHTML
})
}

默认效果是新旧内容的交叉淡入淡出,一行动画代码都不用写。浏览器会生成一组伪元素树(::view-transition-old(root)::view-transition-new(root)),你可以用 CSS 定制它们:

1
2
3
4
5
6
7
8
::view-transition-old(root) {
animation: fade-out 200ms ease-out both;
}
::view-transition-new(root) {
animation: fade-in 250ms ease-in both;
}
@keyframes fade-out { to { opacity: 0; } }
@keyframes fade-in { from { opacity: 0; } }

共享元素变形:小图变大图的原生做法

视图过渡真正惊艳的地方是共享元素(shared element)。给新旧两个状态里的同一个元素起同一个 view-transition-name,浏览器就会把它从旧位置、旧尺寸平滑"变形"到新位置——这正是原生 App 里照片缩略图点开成大图的效果。

1
.photo-thumb { view-transition-name: photo-hero; }
1
2
3
thumb.addEventListener('click', () => {
document.startViewTransition(() => openLightbox(thumb.dataset.src))
})

灯箱里的大图同样声明 view-transition-name: photo-hero,浏览器自动完成位置、尺寸、形状的插值。有三条必须遵守的规则:

  1. 同一帧画面里名字必须唯一。两个可见元素重名会导致整个过渡中止,动态列表里用完要及时清掉名字。
  2. 先做特性检测。老浏览器没有 document.startViewTransition,走无动画降级路径即可。
  3. 尊重 prefers-reduced-motion。用户在系统里开启了"减少动态效果",就应该跳过或缩短动画:
1
2
3
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*) { animation: none; }
}

React 里的 flushSync 陷阱

在 React/Vue 这类异步批量更新的框架里,最常见的"视图过渡不生效"原因是:startViewTransition 的回调返回时,DOM 其实还没更新,浏览器截到了两张一样的快照。React 的解法是用 flushSync 强制同步刷新:

1
2
3
4
5
6
7
8
import { flushSync } from 'react-dom'

function navigate(next) {
if (!document.startViewTransition) return setView(next)
document.startViewTransition(() => {
flushSync(() => setView(next)) // DOM 先更新,快照才正确
})
}

跨文档过渡:多页应用(MPA)也能有页面转场

2026 年更具革命性的是跨文档视图过渡(Cross-Document View Transitions):传统多页网站、Hexo 这类静态博客,点链接整页刷新也能有平滑转场,不需要 SPA 路由。代码量少到离谱——一行 CSS:

1
2
3
@view-transition {
navigation: auto;
}

加上共享元素名字,列表页的封面图就能在跳转到文章页时"飞"过去成为头图。前提是两个页面同源。这项能力随 Chrome 125 首发、Safari 18.2 跟进,2026 年上半年三大引擎全部支持,覆盖超过 95% 的全球流量。

滚动驱动动画:动画进度绑在滚动条上

过去做"滚到哪里动画播到哪里",要监听 scroll 事件、getBoundingClientRect() 算位置、再写内联样式——主线程一忙就掉帧。滚动驱动动画直接跑在合成器线程上,JavaScript 再忙动画也不卡。

两种时间线:

  • scroll 时间线:进度绑定滚动容器的滚动位置,适合阅读进度条、视差背景。
  • view 时间线:进度绑定元素进入/离开视口的过程,适合元素入场动画。

纯 CSS 的阅读进度条,以前需要十几行 JS,现在三行关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
.progress-bar {
position: fixed;
top: 0; left: 0;
height: 3px;
transform-origin: 0 50%;
background: linear-gradient(90deg, #6366f1, #8b5cf6);
animation: grow linear both;
animation-timeline: scroll(root);
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}

元素随滚动进入视口时上浮淡入,用 view 时间线:

1
2
3
4
5
6
7
8
9
.reveal {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes fade-up {
from { opacity: 0; transform: translateY(48px); }
to { opacity: 1; transform: translateY(0); }
}

视差效果同理,给背景层绑定 animation-timeline: scroll(),在关键帧里平移 translateY 即可——没有任何事件监听。

两个 API 怎么配合

视图过渡负责"状态与状态之间"的瞬间转换,滚动驱动动画负责"滚动过程中"的连续变化,两者正好覆盖了过去依赖动画库的两大类场景。选型上可以简单判断:

  • 缩略图展开详情、主题切换、列表重排 → 视图过渡
  • 阅读进度、视差、入场动效 → 滚动驱动动画
  • 循环动画、手势跟手的物理效果 → 仍然交给 CSS 动画或专业动画库

需要注意的边界:视图过渡的快照阶段会短暂暂停渲染,转场要控制在 300~500ms;跨文档过渡有 4 秒超时,慢网络下会静默放弃动画直接跳转——这都属于优雅降级,不会影响内容可用性。

小结

2026 年的前端动画格局已经改写:能原生就不引库。视图过渡和滚动驱动动画带来的不仅是少几个依赖,更是合成器线程级别的流畅度、对 prefers-reduced-motion 的天然友好,以及"不支持也不报错"的渐进增强体验。

如果你对同一时期落地的其他浏览器原生能力感兴趣,可以接着看这篇《2026 CSS 新特性盘点:锚点定位、容器样式查询与 contrast-color() 实战》,下一篇我们聊 AI 时代另一个绕不开的基础设施——MCP 协议的 2026 新规范。