返回首页
🎨 前端 / Web

View Transitions API 实战 2026:用浏览器原生能力做流畅的页面过渡

从列表点击进入详情,图片像被一只无形的手放大、平移、落位;返回时它又缩回列表原位。这种过去要依赖重量级动画库、还要手动对齐位置的共享元素过渡,如今浏览器原生就能完成。2026 年,View Transitions API 已在所有主流浏览器全面可用,跨文档过渡更被纳入 Interop 2026 互操作性计划。本文系统实战 View Transitions:单页应用同文档过渡的基本原理、view-transition-name 命名与共享元素、自定义关键帧动画、列表到详情的经典场景、多页应用跨文档过渡与 pageswap/pagereveal 事件、React/Vue 框架集成、无障碍与降级,以及性能边界。

View Transitions · CSS · 前端动画 · SPA · MPA · Web API · 用户体验 · React · 共享元素 · 浏览器
��

今日技术简讯

📰 技术简讯 · 2026-10-12

今日聚合 6 条热门技术内容(中文素材优先)。

🤖 AI / LLM

1. 谷歌 Gemini 4「Argon」即将公开发布,内部测试更强版本「Carbon」

  • 链接:http://m.toutiao.com/group/7694804779515625999/
  • 来源:IT之家
  • 摘要:据商业内幕报道,谷歌即将正式发布新一代旗舰 Gemini 4「Argon」,同时内部已在测试代码能力更强的后续版本「Carbon」,早期员工反馈其编程表现对标竞品顶级代码模型;旗舰模型竞争进入长周期软件工程的正面较量阶段。

2. Google DeepMind 开源多模态嵌入模型 EmbeddingGemma 2

🎨 前端 / Web

3. Ant Design 6.5 发布:用 DESIGN.md 适配 AI 设计工具,包体积大幅瘦身

  • 链接:https://github.com/ant-design/ant-design/releases
  • 来源:Ant Design
  • 摘要:Ant Design 6.5 引入定义视觉语言的 DESIGN.md 以适配 AI 设计工具,压缩后包体积显著下降,并为多个主流 AI 与社交平台新增内置图标;组件库的交付物正在从"组件代码"扩展到"机器可读的设计规范"。

4. Linear 完成超千个 PR 的迁移:从 styled-components 全面转向 StyleX

  • 链接:https://www.infoq.com/React/news/
  • 来源:InfoQ
  • 摘要:Linear 完成 React 应用从 styled-components 到 Meta StyleX 的迁移,涉及上千个 PR,核心动机是性能与更严格的组件边界;运行时 CSS-in-JS 的主线程开销被编译期确定的样式取代,导航流畅度获得改善。

⚙️ 后端 / 架构

5. Redis 开源版 8.10:紧凑哈希让同构数据只存一次字段名

🚀 独立开发 / OPC

6. 两天做出的 AI 小游戏上线一个月吸引数十万玩家

  • 链接:https://indiefront.cn/articles/cmuwhonxl000e9zqc118wc8n3/
  • 来源:独立开发前线
  • 摘要:独立开发者用 AI 在两天内完成小红书小游戏「小喵钓鱼」,上线一个月吸引大量玩家、日新增数千。案例的启示并非"做游戏很容易",而是 AI 把原型到上架的周期压缩到极致后,独立开发者的核心竞争力转向选题、平台理解与快速试错的节奏。

数据来源:掘金 / InfoQ 中文 / 即刻 / 少数派 / HN 采集日期:2026-10-12 (UTC+8)

��

今日深度文

View Transitions API 实战 2026:用浏览器原生能力做流畅的页面过渡

优秀的界面动画从来不是装饰,而是空间关系的说明。点击列表里的一张卡片进入详情,如果详情页是"唰"地一下整体替换,用户需要重新定位自己在哪里;如果卡片上的图片平滑放大、移动并落位到详情页的大图位置,用户一眼就能理解"我从哪里来、这个页面和刚才是什么关系"。这就是共享元素过渡(shared element transition)的价值。在很长一段时间里,实现它需要 FLIP 技术、手动测量位置、引入动画库;而现在,浏览器把这件事做成了原生能力——View Transitions API。2026 年它已在所有主流浏览器全面可用,连传统多页站点也能通过跨文档过渡获得同样的体验。


一、它到底解决了什么:从"整页替换"到"连续空间"

没有过渡时,DOM 更新是瞬间发生的:旧状态一帧消失,新状态下一帧出现,两者之间没有任何视觉关联。

无过渡的状态切换:
  第 N 帧:列表(卡片是小图)
  第 N+1 帧:详情(大图、正文)   ← 用户大脑需要重新建立空间认知

View Transition 做的事:
  第 N 帧:对旧状态截图(old snapshot)
  中间帧:浏览器自动生成从旧位置到新位置的动画
  第 N+1 帧:新状态(new snapshot)就位
  → 旧视图"变形"为新视图,元素的对应关系被视觉传达

浏览器在过渡期间做了三件开发者过去要手动完成的苦活:

① 快照:分别捕获过渡前、后的页面视觉状态。
② 命名配对:按 view-transition-name 找到"旧状态的 A 元素"
   与"新状态的 A 元素",判定为同一元素的两个形态。
③ 自动补间:对配对元素的位置、尺寸、内容自动生成过渡,
   开发者只需用 CSS 描述"想要什么动画"。

关键认知:**你操作的仍然是普通 DOM,View Transitions 只负责"状态 A 到状态 B 怎么演",不改变任何渲染逻辑。**这也是它比动画库方案轻得多的根本原因。


二、两种模式:同文档过渡与跨文档过渡

View Transitions 有两个适用场景,API 形态不同,先建立全局认知:

模式一:同文档过渡(same-document,SPA 经典场景)
  页面不刷新,由 JS 改变 DOM(标签切换、模态框、路由切换)。
  入口:document.startViewTransition(() => { 更新DOM })
  支持:全主流浏览器。

模式二:跨文档过渡(cross-document,MPA 经典场景)
  真实的页面跳转(a 标签、表单提交、前进后退),
  旧文档与新文档是两份独立 HTML。
  入口:CSS @view-transition 声明启用 +
        pageswap / pagereveal 事件做精细控制。
  状态:全主流浏览器已支持,互操作性持续打磨中。

跨文档过渡有几个硬性前提,先记在心里,后面细说:只作用于同源导航、导航过程不能包含跨域重定向、新旧两个文档都要通过 CSS 主动选择启用。


三、最小可用例子:一个标签切换

先看同文档模式的完整最小例子。HTML 是两个普通的面板与切换按钮:

<button id="toggle">切换视图</button>
<div id="panelA" class="panel">面板 A:概览数据</div>
<div id="panelB" class="panel" hidden>面板 B:详细列表</div>

JS 只做一件事:把 DOM 更新包进 startViewTransition。注意回调必须同步反映新状态——浏览器在回调执行后捕获新快照。

const panelA = document.querySelector("#panelA");
const panelB = document.querySelector("#panelB");

document.querySelector("#toggle").addEventListener("click", () => {
  // 把普通的 DOM 切换交给过渡 API 包裹
  document.startViewTransition(() => {
    panelA.hidden = !panelA.hidden;
    panelB.hidden = !panelB.hidden;
  });
});

不加任何 CSS,浏览器会给整个页面一个默认的淡入淡出。想自定义,就操作过渡期间自动生成的伪元素树:

过渡期间浏览器构建的伪元素层级:

  ::view-transition                    ← 覆盖层根
    └─ ::view-transition-group(root)   ← 布局/尺寸动画的容器
         ├─ ::view-transition-old(root) ← 旧状态快照(淡出)
         └─ ::view-transition-new(root) ← 新状态快照(淡入)

  root 是默认名称;设置了 view-transition-name 的元素
  会各自生成一组 group/old/new。

自定义动画就是给这些伪元素写普通的 CSS 动画:

/* 让整页默认过渡变成轻微上移 + 淡入,更克制更快 */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.25s;
  animation-timing-function: ease;
}

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}

::view-transition-new(root) {
  animation-name: fade-up;
}

到这里你已经拥有了一个零依赖、由合成线程驱动的页面切换动画。


四、共享元素:用 view-transition-name 给元素"配对"

整页淡入淡出只是开胃菜,真正强大的是共享元素。给过渡前后"同一个东西"赋予相同的 view-transition-name,浏览器就会让它从旧形态平滑变形到新形态。

经典场景:列表卡片缩略图 → 详情页主图。

/* 列表中的卡片图片 */
.card-cover {
  view-transition-name: hero-photo;
}

/* 详情页的大图:同名,即建立配对 */
.detail-hero {
  view-transition-name: hero-photo;
}

配合 JS 的状态更新(这里以客户端渲染列表/详情为例):

/**
 * 从列表进入详情:更新 DOM 的动作包进过渡
 * 同名元素 hero-photo 会从卡片位置放大到主图位置
 */
function openDetail(postId) {
  document.startViewTransition(() => {
    renderDetail(postId); // 同步切换到详情视图
  });
}

/**
 * 从详情返回列表:同名元素自动"缩回"原位
 */
function backToList() {
  document.startViewTransition(() => {
    renderList();
  });
}

浏览器自动补间了位置(translate)、尺寸(width/height)和宽高比变化,开发者完全不需要 getBoundingClientRect 手算。这正是过去 FLIP 库最繁琐的部分。

**命名有一条铁律:同一时刻,一个 view-transition-name 在页面中只能对应一个被渲染的元素。**名字重复会导致过渡被跳过,因为浏览器无法判断该配对谁:

常见翻车点:
  × 给列表所有卡片图片都写死 view-transition-name: item-photo
    → 一屏几十个同名元素,过渡直接失效。
  √ 只给"被点击的那张卡片"动态设置名字,
    其余卡片保持 none,详情页大图用同一个名字。

列表场景的正确做法是点击时才命名:

/**
 * 点击卡片:仅为被点击卡片指定共享名,保证全局唯一
 */
function onCardClick(card, postId) {
  card.querySelector(".card-cover").style.viewTransitionName = "hero-photo";
  document.startViewTransition(() => renderDetail(postId));
}

返回列表时也要清理,避免下一屏残留同名元素:

/**
 * 返回后清除共享名,恢复列表多卡片共存状态
 */
function backToList() {
  document.startViewTransition(() => renderList());
  // 新状态已无详情大图;列表图片统一不设共享名,避免重名
}

五、自定义过渡手感:分组、旧层、新层分别控制

默认动画之外,可以分别控制容器(位置/尺寸)与新旧两层(内容交叉淡入)。一个带轻微缩放、内容交叉淡化的详情打开动画:

/* 容器层:负责位置与尺寸的平滑移动 */
::view-transition-group(hero-photo) {
  animation-duration: 0.32s;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

/* 旧内容快速淡出,新内容稍晚淡入,形成柔和交叉 */
::view-transition-old(hero-photo) {
  animation: hero-out 0.32s ease both;
}
::view-transition-new(hero-photo) {
  animation: hero-in 0.32s ease 0.06s both;
}

@keyframes hero-out {
  to { opacity: 0; }
}
@keyframes hero-in {
  from { opacity: 0; }
}

除了共享的主图,让其余内容(正文、标题)分别有节奏地进场,可以给它们也起不同名字:

.detail-title { view-transition-name: detail-title; }
.detail-body  { view-transition-name: detail-body; }

/* 正文做轻微上浮,与主图移动错开几十毫秒 */
::view-transition-new(detail-body) {
  animation: body-in 0.4s ease 0.08s both;
}
@keyframes body-in {
  from { opacity: 0; transform: translateY(10px); }
}

设计上的建议:**过渡时长控制在两三百毫秒、位移幅度克制、缓动用 ease-out 类曲线。**动画是在帮用户理解空间,不是在表演——过长、过炫的过渡会让高频操作显得拖沓。


六、跨文档过渡:多页站点也能拥有原生动画

对传统 MPA(包括 Next.js 静态导出、Astro、纯静态博客这类每次跳转都是真实 HTML 文档的站点),跨文档过渡是 2026 年最值得关注的能力。启用方式意外地简单——新旧两个页面都加上同一段 CSS 即表示"我同意参与过渡":

/* 列表页与详情页都要写:对同源导航启用跨文档过渡 */
@view-transition {
  navigation: auto;
}

仅此一行,浏览器就会在页面跳转时复用 View Transition 的快照机制:默认给整页一个淡入淡出。要做共享元素,依然是老办法——两个文档里的对应元素使用同一个 view-transition-name:

/* 列表页 index.html */
.card-cover { view-transition-name: hero-photo; }

/* 详情页 post.html —— 同名即配对,跨文档同样有效 */
.detail-hero { view-transition-name: hero-photo; }

跨文档的唯一命名问题更微妙:列表页有几十张卡片,而详情页只有一张大图。解决方法是只让"当前被点击卡片"携带名字——点击跳转前用 JS 动态设置:

/**
 * 列表页:点击卡片时,给该卡片写入共享名,
 * 随后让浏览器正常跳转到详情页
 */
document.querySelectorAll(".card").forEach((card) => {
  card.addEventListener("click", () => {
    document.querySelectorAll(".card-cover").forEach((img) => {
      img.style.viewTransitionName = "none";
    });
    card.querySelector(".card-cover").style.viewTransitionName = "hero-photo";
  });
});

需要在导航发生时做更精细的控制(按前进/后退方向给不同动画),使用两个生命周期事件:

/**
 * 旧文档即将离开:可通过 event.viewTransition 定制
 */
window.addEventListener("pageswap", (event) => {
  if (!event.viewTransition) return;
  // navigation.activationType 可区分 traverse(前进/后退)等类型
});

/**
 * 新文档即将呈现:在这里给不同进入方向的过渡加类名
 */
window.addEventListener("pagereveal", (event) => {
  if (!event.viewTransition) return;
  const isBack =
    navigation.activationType === "traverse-backward";
  document.documentElement.classList.toggle("is-back", isBack);
});
/* 后退时用更快、更收敛的动画,与"深入详情"形成方向感差异 */
.is-back::view-transition-group(hero-photo) {
  animation-duration: 0.2s;
}

跨文档过渡的边界条件要清楚,满足不了时浏览器会静默回退为普通导航:

跨文档过渡生效条件:
  ① 仅限同源导航(跨域直接不启用)。
  ② 导航链路上不能有跨源重定向。
  ③ 新旧文档都必须用 @view-transition 选择启用。
  ④ 地址栏直接输入 URL 这类非页面内触发的导航不包含在内。

七、在 React 等框架中使用:小心异步状态更新

React 集成的坑在于:startViewTransition 的回调期望 DOM 在回调返回时就完成更新,而 React 的状态更新默认是批量异步调度的。如果直接在回调里 setState,浏览器截到的新快照可能还是旧 DOM。解决工具是 flushSync:

import { flushSync } from "react-dom";
import { useState } from "react";

/**
 * React 中的同文档过渡
 * 用 flushSync 强制状态更新同步刷新到 DOM,再让浏览器截图
 */
export function ViewSwitcher() {
  const [showDetail, setShowDetail] = useState(false);

  /** 切换到详情 */
  const goDetail = () => {
    document.startViewTransition(() => {
      flushSync(() => setShowDetail(true));
    });
  };

  /** 返回列表 */
  const goBack = () => {
    document.startViewTransition(() => {
      flushSync(() => setShowDetail(false));
    });
  };

  return showDetail ? <Detail onBack={goBack} /> : <List onOpen={goDetail} />;
}

使用 Suspense、流式数据或路由库时,如果新视图的数据未就绪,可以先在过渡期间显示占位,数据到达后再做第二次过渡,避免截到半成品页面。各大元框架也在把这套机制封装进自己的路由导航 API,接入前先查当前版本的官方文档,避免与框架内建过渡重复触发。

Vue 同理:把 DOM 变更放进 startViewTransition 回调,并确保 DOM 已更新(可在 nextTick 之后让快照捕获,或等待框架提供的过渡钩子)。核心原则跨框架一致:先保证新 DOM 真实存在,再交还控制权给浏览器。


八、无障碍:尊重"减少动态效果"的系统设置

过渡动画会引发部分用户的前庭不适,必须响应用户的系统级偏好设置 prefers-reduced-motion。最省心的做法是在用户偏好减少动画时关闭自定义动画(浏览器会退化为即时切换,功能完全不受影响):

/* 用户在系统中开启"减少动态效果"时,不施加自定义动画 */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

也可以在 JS 层直接跳过过渡、只做状态更新:

/**
 * 根据系统偏好决定是否用过渡包裹 DOM 更新
 * 偏好减少动画时直接更新,不创建过渡
 */
function updateView(updateDom) {
  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (reduceMotion || !document.startViewTransition) {
    updateDom();
    return;
  }
  document.startViewTransition(updateDom);
}

九、降级:不支持时,体验退化为"正常跳转"

View Transitions 的降级极其优雅:**它本质上只是一层视觉增强,去掉它,DOM 更新与页面跳转照常工作。**因此特性检测就足够了:

/**
 * 安全调用同文档过渡
 * 旧浏览器直接执行更新,行为与加动画前完全一致
 */
function safeTransition(updateDom) {
  if (typeof document.startViewTransition === "function") {
    document.startViewTransition(updateDom);
  } else {
    updateDom();
  }
}

跨文档模式无需写 JS 降级:不支持的浏览器会忽略 @view-transition 规则,页面按最普通的方式跳转。view-transition-name 这个 CSS 属性在旧浏览器里也只是被忽略,不影响布局。

降级策略总结:
  - 同文档:特性检测 document.startViewTransition,有则增强、无则直改。
  - 跨文档:CSS 渐进增强,不支持即普通导航,零副作用。
  - 永远不要把核心功能(路由、数据)依赖在过渡是否执行上。

十、性能与使用边界

View Transitions 的动画跑在合成线程上,正常使用比 JS 驱动的属性动画更省主线程,但仍有边界:

性能注意事项:
  ① 快照有成本:超大区域、复杂滤镜、超大图片的快照会增加开销,
     给过渡元素合理命名、避免整页巨量内容同时动画。
  ② 动画只用合成属性思路:浏览器内部已优化位置/尺寸补间,
     自定义时优先 opacity/transform,避免在关键帧里触发
     layout 的属性。
  ③ 别在过渡期间做重活:startViewTransition 回调里只更新 DOM,
     数据请求、复杂计算应在过渡外完成。
  ④ 列表唯一命名:动态命名 + 及时清理,养成习惯,
     重名是最常见的"动画莫名失效"原因。

适合使用的场景:列表↔详情、标签页切换、设置项展开、图片灯箱、主题切换、播放器迷你条↔全屏。不适合滥用的场景:高频连续交互(拖拽跟手、输入联想)、需要逐帧精确控制的复杂编排——那些仍应交给专门的动画方案。


十一、落地清单

上手阶段
  □ 用 startViewTransition 包一个标签切换,体验默认淡入淡出
  □ 给一对共享元素设置相同 view-transition-name,观察自动补间
  □ 加 prefers-reduced-motion 与特性检测降级

列表/详情阶段
  □ 点击时动态命名被点击卡片,保证同名全局唯一
  □ 自定义 group/old/new 的时长、缓动与交叉淡化
  □ React 中用 flushSync 保证快照捕获新 DOM

MPA 阶段
  □ 新旧文档都加 @view-transition { navigation: auto }
  □ 用 pageswap/pagereveal 区分前进/后退动画
  □ 核对同源、无跨源重定向等生效前提

结语:动画的归宿是成为浏览器的基础设施

前端动画的发展脉络,是一条"把高频通用的东西从业务代码下沉到平台"的历史:布局交给 CSS、手势交给 Pointer Events、字体加载交给 Font Loading API。共享元素过渡曾经是衡量一个前端"会不会做动画"的高难度动作,如今它也走完了这条路——开发者只需要声明"这两个元素是同一个东西",剩下的测量、配对、补间全部由浏览器完成。

当动画的边际成本趋近于零,真正稀缺的就变成了判断力:**哪里值得动、动多快、如何不打扰用户、如何尊重不想要动画的人。**工具会越来越强,而克制与同理心,始终是体验设计师无法外包给浏览器的那部分工作。

�� 同主题文章

🎨前端 / Web·

前端状态管理完整实战 2026:从 Redux 到 Signals 再到 Server State

前端状态管理是 React 生态永恒的话题。从 Class 时代的 Redux 到 Hooks 时代的 Context + useReducer,再到 2024 年 Signals 的兴起和 2025 年 Server State 的深度整合,状态管理的范式在持续演进。核心矛盾从未改变:状态在哪里放、怎么同步、组件怎么订阅。本文完整实战 2026 年前端状态管理:状态分类(UI / 服务端 / 全局)、Redux 到 Zustand 的简化路线、Signals 细粒度响应式的原理与迁移、Server State 与本地状态的边界、缓存策略与乐观更新、以及独立开发者的选型决策树。

前端状态管理ReactRedux
🎨前端 / Web·

Web 性能优化完整实战 2026:INP 时代的 Core Web Vitals 与渲染性能

2024 年 INP 取代 FID 成为 Core Web Vitals 的正式指标后,前端性能优化的重心从「加载快不快」转向「用起来顺不顺」。LCP 管首屏、INP 管交互、CLS 管稳定,三个指标共同决定用户对网站的真实体感,也直接影响搜索排名与转化率。本文完整实战 2026 年的 Web 性能优化:三大指标的测量与阈值、LCP 优化链路、INP 的三阶段拆解与优化、CLS 稳定性治理、长任务与主线程释放、资源加载策略、真实用户监控 RUM,以及独立开发者投入产出比最高的优化清单。

Web 性能Core Web VitalsINP
🎨前端 / Web·

React Server Components 完整实战 2026:服务端渲染、流式与数据获取的架构重组

React Server Components(RSC)是 React 诞生以来最深刻的架构变革:组件被分为「服务端组件」与「客户端组件」两种,前者只在服务端运行、不占用浏览器 JavaScript 体积、可直接访问数据库,后者保留交互能力。它不是 SSR 的升级版,而是对 React 渲染模型的重新设计。本文完整实战 RSC:服务端组件与客户端组件的分工、数据获取的革命、流式渲染与 Suspense、缓存策略、与客户端状态管理的边界、迁移路径与常见误区。

ReactServer ComponentsRSC

🎨 前端 / Web 分类更多