别再用 JavaScript 写页面切换动画了------你的浏览器 2026 年已经内置了一个动画引擎,而且它的性能比你的代码好十倍。
一、我遇到的一个真实问题
上个月做公司官网改版,设计稿里有一堆页面切换的过渡动画:列表页点进去,缩略图要平滑放大过渡到详情页的大图;切 Tab 时内容区要有滑动效果;从首页进入产品页,导航栏要有一个淡入淡出的衔接。
我第一反应很自然------掏出 framer-motion,配合 React Router 的 AnimatePresence,一顿操作猛如虎。写完一看:动画组件 + 状态管理 + 各种 layoutId + DOM 测量逻辑 + 边界处理......光动画相关的代码就写了将近 800 行。
然后问题来了:
- 页面多了以后,
AnimatePresence的exit动画和新页面的enter动画时间线打架,有时候页面内容已经变了,动画还在播上一个页面的残影 - 那个「缩略图放大到详情图」的共享元素动画,我用了
layoutId,结果因为两个页面 DOM 结构不一样,framer-motion的自动测量算出来的偏移量歪到姥姥家去了 - 最致命的是------多页面应用(MPA)完全没法做过渡动画。我们站有一些页面是服务端渲染的静态页,点个链接页面白屏一闪,体验瞬间回到 2010 年
我一度想放弃,跟设计师说「这个做不了」。直到某天在 Chrome 的 What's New 里看到了 View Transitions API。
二、View Transitions API 是什么?
一句话解释:浏览器现在内置了一个「截屏→变 DOM→补间动画」的动画引擎。
它的工作流程特别简单,就四步:
- 浏览器给当前页面拍一张「快照」
- 你更新 DOM(切页面、换内容随你)
- 浏览器再拍一张「新快照」
- 浏览器自动在两个快照之间做补间动画
以前这四步你得自己写:手动 clone DOM、用 getBoundingClientRect() 算位置、管理 requestAnimationFrame 的时间线、处理各种边缘情况。现在一行代码就够:
javascript
// 就这一行,浏览器帮你搞定所有过渡动画
document.startViewTransition(() => updateTheDOM());
实战一:多页面应用(MPA)的跨页面过渡
这才是 View Transitions API 最让我兴奋的地方。以前多页面应用想做过渡动画?做梦。要么你用 Service Worker 搞一套复杂的预加载 + 缓存策略,要么你干脆把所有页面改成 SPA。
现在呢?在 CSS 里加一行就完事了:
css
/* 在全局 CSS 里加这行 */
@view-transition {
navigation: auto;
}
加上这行之后,用户点击站内链接跳转时,浏览器会自动在旧页面和新页面之间做一个淡入淡出的过渡。白屏闪烁?不存在了。
但光淡入淡出还不够------我们真正需要的是共享元素动画。
实战二:共享元素过渡(这是真正的魔法)
还记得我说的那个痛点吗------列表页缩略图点击后平滑过渡到详情页大图。View Transitions API 的做法简直像变魔术:
列表页(list.html):
css
.product-thumb {
view-transition-name: product-hero;
}
详情页(detail.html):
css
.product-detail-img {
view-transition-name: product-hero;
}
就这。两个页面的元素只要设置了相同的 view-transition-name,浏览器会自动追踪它们的位置、大小、甚至圆角,然后生成丝滑的补间动画。不用算坐标,不用管 DOM 结构,不用处理异步加载的时序------浏览器全包了。
说实话,我第一次看到效果的时候沉默了大概 5 秒。我之前写的那 800 行代码像个笑话。
实战三:自定义过渡动画风格
默认的淡入淡出虽然够用,但有时你想要更个性化的效果------比如「旧页面向左滑出,新页面从右滑入」。View Transitions API 暴露了伪元素让你自定义:
css
/* 旧页面滑出 */
::view-transition-old(root) {
animation: 0.3s ease both slide-to-left;
}
/* 新页面滑入 */
::view-transition-new(root) {
animation: 0.3s ease both slide-from-right;
}
@keyframes slide-to-left {
to { transform: translateX(-30px); opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); opacity: 0; }
}
而且你可以对不同类型的过渡定义不同的动画效果------前进是右滑入,后退是左滑入,类原生 App 的导航体验一下子就有了:
javascript
// 通过设置过渡类型,配合不同的 CSS 动画
document.startViewTransition(() => {
navigate(direction === 'forward' ? '/next' : '/prev');
});
三、不只是 View Transitions------2026 年这些 CSS 也在帮你删 JS 代码
View Transitions API 解决的是页面过渡的问题。但 2026 年的 CSS 远不止这一个惊喜。下面这三个特性,每一个都能帮你删掉一大坨 JavaScript。
1. 滚动驱动动画(Scroll-Driven Animations)
以前做「滚动到某个位置触发动画」,套路都是 window.addEventListener('scroll', ...) + 一堆 getBoundingClientRect() 计算。主线程一忙动画就掉帧,用户体验烂得一批。
现在 CSS 原生支持了:
css
.fade-in-section {
opacity: 0;
transform: translateY(30px);
animation: fade-in linear forwards;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes fade-in {
to { opacity: 1; transform: translateY(0); }
}
这个动画跑在浏览器的合成器线程 上,完全不占用主线程。你滚动得再快也不会掉帧。我之前那个滚动动画的 IntersectionObserver + requestAnimationFrame 方案,删了,全删了。
2. 容器查询(Container Queries)
十几年来我们一直用 @media (max-width: 768px) 做响应式,但这个思路有个本质缺陷:它关心的是视口宽度,不是组件所在容器的宽度。
同一个卡片组件,放在 1200px 宽的主内容区是两列布局,拖到 300px 宽的侧边栏就炸了。以前我们靠「给组件传 variant prop + 条件渲染 class」来解决。现在 CSS 自己能感知容器大小了:
css
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}
@container (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
组件放进任何容器都能自适应,不用再写一堆 variant="compact" 这种 props。这才是真正的「一次编写,到处适用」。
3. :has() 选择器------CSS 终于能「向上」选择了
前端人吐槽了二十年的「CSS 只能向下选,不能根据子元素状态选父元素」,终于被 :has() 终结了:
css
/* 表单字段包含无效 input 时,整行变红 */
.form-field:has(input:invalid) {
border-color: #ef4444;
background: #fef2f2;
}
/* 购物车有商品时隐藏空状态 */
.cart:has(.cart-item) .empty-state {
display: none;
}
/* 卡片里包含视频时换深色背景 */
.article-card:has(video) {
background: #1a1a2e;
color: #fff;
}
以前这些逻辑都得写在 JavaScript 里------监听表单验证状态、判断数组长度、检查 DOM 结构......现在一个选择器搞定,页面加载时就已经是正确的状态,不存在「先渲染默认状态,JS 跑完了再闪一下」的闪烁问题。
四、效果对比
| 场景 | 传统方案 | 2026 CSS 方案 | 代码量变化 |
|---|---|---|---|
| 页面过渡动画 | framer-motion + React Router + 状态管理 | document.startViewTransition() + CSS |
800行 → ~50行 |
| MPA 跨页面过渡 | 无法实现(或需 Service Worker 黑科技) | @view-transition { navigation: auto } |
0行 → 1行CSS |
| 共享元素动画 | 手动测量 DOM + 计算偏移 + 管理生命周期 | view-transition-name: xxx |
~200行 → 1行CSS |
| 滚动触发动画 | IntersectionObserver + JS 动画 |
animation-timeline: view() |
~100行 → 5行CSS |
| 组件级响应式 | @media + JS props 传 variant |
@container 容器查询 |
~50行 → 5行CSS |
| 子元素影响父样式 | JS 监听 + class 切换 | :has() 伪类选择器 |
~30行 → 1行CSS |
不是说 JavaScript 没用了------而是该让 CSS 干的活,就让 CSS 去干。CSS 跑在浏览器的样式引擎里,天然比 JS 操作 DOM 快一个数量级。
五、总结
核心收获:2026 年了,别再习惯性地用 JavaScript 解决一切问题。CSS 已经进化成了一个强大的「声明式交互引擎」------View Transitions API 管页面过渡,Container Queries 管响应式布局,Scroll-Driven Animations 管滚动交互,:has() 管条件样式。让每个语言做它最擅长的事,你的代码会少很多,性能会好很多。
给读者的三条建议:
- View Transitions API 现在就可以用。Chrome 已全功能支持(包括 MPA 跨页面过渡),Safari 和 Firefox 正在追赶。用渐进增强的方式------支持的就用原生动画,不支持的就退化到无动画,不影响功能。
- 先审视你的 JS 动画代码 。打开项目搜一下
addEventListener('scroll'和IntersectionObserver,看看有多少可以用 Scroll-Driven Animations 替换。每替换一处,你的页面 FPS 就稳定一点。 - 不要把
view-transition-name当 class 用 。每个页面内view-transition-name必须是唯一的,只给关键的视觉引导元素(比如主图、标题)设置。