mcpField
W
web-animation-view-transitions

web-animation-view-transitions

github.com/agents-inc设计与 UI26101 次安装2 天前更新
代码仓库

关于 web-animation-view-transitions

用于实现浏览器 View Transitions 的实践指南,涵盖单页更新和页面导航。内容包括功能检测、共享元素、CSS 动画以及减少动态效果偏好的处理。

主要功能

  1. 01同文档与跨文档转场
  2. 02共享元素命名模式
  3. 03使用 CSS 定制转场动画
  4. 04减少动态效果与不支持浏览器的回退
  5. 05转场时序与 Promise 指引

适用场景

  1. 01为路由或视图变化添加动画
  2. 02让共享主视觉元素在页面间移动
  3. 03添加具有方向感的导航动画
  4. 04遵循减少动态效果偏好

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-animation-view-transitions

安装后在 Claude Code 中输入/web-animation-view-transitions确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
core.md
shared-elements.md
spa.md

详细介绍

View Transitions API 模式

**快速指南:**浏览器会截取旧状态的屏幕画面,让新状态保持实时,并将它们作为一棵伪元素树进行动画过渡。document.startViewTransition() 用于驱动同一文档内的变化;在两个页面上设置 @view-transition { navigation: auto },则可驱动跨文档过渡。view-transition-name 会将元素从根快照中分离出来,使其可以独立移动;整个动画通过 CSS 而非脚本进行自定义。

详细资源:

  • examples/core.md — 功能检测、状态过渡、三个 Promise、跳过过渡、CSS 覆盖
  • examples/spa.md — 主题揭示、表单步骤、标签面板、手风琴、列表重排序、减少动态效果
  • examples/shared-elements.md — 主视觉动画、多元素卡片、跨文档配对、模态框
  • reference.md — 命名规则、伪元素树、浏览器支持、API 表格

适用场景

  • 客户端路由或页面内状态由脚本更新 — startViewTransition() 接收更新回调,并在更新前后进行捕获;请参阅 examples/spa.md。
  • 服务端渲染的页面之间进行导航 — 完全无需脚本:两个文档都通过 @view-transition { navigation: auto } 启用此功能,并通过 pageswap 和 pagereveal 事件设置名称;请参阅 examples/shared-elements.md。
  • 某个元素需要在两个状态之间移动,而不是与其他所有元素一起交叉淡入淡出 — 该元素需要在两侧设置匹配的 view-transition-name;请参阅 examples/shared-elements.md。

<critical_requirements>

编写 View Transitions 代码前

先检查是否支持 startViewTransition,再调用;无论是否支持,都要执行更新。 API 未实现时,调用会直接缺失,而不是空操作,因此不加检查就调用会抛出异常,DOM 更改也完全不会发生。

为每个参与过渡的元素指定一个其他可见元素都没有的名称。 名称用于标识一个快照;如果两个可见元素使用同一个名称,整个过渡都会中止,而不只是它们各自的部分。

在 transition.finished 解析后,清除动态分配的 view-transition-name。 元素上的名称如果没有清除,就会与下一次为元素分配的名称冲突,因此通常是第二次导航时才出现故障,而不是第一次。

为每个自定义过渡提供 prefers-reduced-motion 分支。 页面级过渡会移动整个视口,而这正是设置该偏好的目的;应缩短持续时间,或跳过过渡并直接应用更新。

</critical_requirements>


自动检测: startViewTransition、view-transition-name、view-transition-class、@view-transition、::view-transition-old、::view-transition-new、::view-transition-group、::view-transition-image-pair、:active-view-transition-type、pageswap、pagereveal、updateCallbackDone、skipTransition、match-element

适用于:

  • 状态变化幅度足够大,需要替换整个视图或其中的大块区域
  • 服务端渲染网站中的页面间导航
  • 一个元素在两个视图中都存在的主视觉动画
  • 方向感知导航,即前进和后退需要相反的移动效果
  • 元素重新排序,即元素发生移动而非变化

其他地方处理:

  • 单个控件的悬停、焦点和按下反馈——没有任何内容被替换,因此不存在可捕获的前后快照对
  • 速度会跨越中断延续的动效,例如在手势中途接续的弹簧动画
  • 帧级时间线编排,即交付物是一条可拖动播放进度的时间线

视图过渡是一个截图与实时视图相互对照并进行动画处理。所有行为都由此而来:旧的一侧是静止像素,因此其中的任何移动都会冻结;为元素命名会将其提取为独立的快照对,并拥有自己的动画;整个树都通过 CSS 设置样式,因为它是一棵伪元素树,而不是由脚本驱动的动画。

DOM 更新本身仍然是常规操作。startViewTransition 包装的是无论如何都会发生的更改,因此对于不支持该功能的浏览器,回退方式就是直接进行更改。


<decision_framework>

元素所属的快照

Does an element exist on both sides and represent the same thing?
├─ YES -> give it one view-transition-name on both sides; it gets its own group and travels
└─ NO  -> leave it in the root snapshot; it cross-fades with everything else around it

每个命名元素都会在树中增加一个组、一对图像和两个快照,因此给所有元素命名的代价大于收益——只为读者会用眼睛追随的元素命名。

默认动画还是自定义动画

Cross-fade at a different duration    -> set animation-duration on ::view-transition-old/new(root)
Slide, scale, wipe                    -> keyframes on the old and new pseudo-elements
Direction-dependent motion            -> a transition type, selected with :active-view-transition-type()
Geometry computed at the moment of the transition (a reveal from a click point)
                                      -> await transition.ready, then animate the pseudo-element

在何处设置名称

对于页面上唯一的元素,CSS 是合适的选择,例如页眉、主视觉区域或单个面板。对于出现多次的元素,脚本才是合适的选择,因为对列表应用静态规则会让每一行都使用相同的名称,从而破坏过渡;应在过渡前为每个元素单独分配名称,并在过渡后清除,或者在支持的情况下使用 view-transition-name: match-element。

</decision_framework>


核心模式

模式 1:功能检测与回退
const SUPPORTS_VIEW_TRANSITIONS =
  typeof document !== "undefined" && "startViewTransition" in document;

function updateWithTransition(updateFn: () => void | Promise<void>): void {
  if (!SUPPORTS_VIEW_TRANSITIONS) {
    updateFn();
    return;
  }
  document.startViewTransition(() => updateFn());
}

在服务端渲染期间没有 document 可供检查,因此 typeof document 检查很重要。

完整代码:examples/core.md


### 模式 2:同一文档内的过渡

回调执行 DOM 更新;浏览器会在调用回调前捕获一次,并在回调完成后再捕获一次。

const transition = document.startViewTransition(async () => {
  await updateFn();
});

// Options form: classify the transition for CSS to select on
const transition = document.startViewTransition({
  update: () => updateDOM(),
  types: ["slide-forward"],
});

await transition.finished;
Promise在何时完成
transition.updateCallbackDoneDOM 更新回调执行完毕
transition.ready伪元素树已创建,动画即将开始
transition.finished动画结束,新视图已生效

async 回调会让页面冻结,直到回调完成,因此其中任何耗时操作都会造成明显卡顿——应先更新为加载状态,而不是等待网络往返。

完整代码:examples/core.md


模式 3:跨文档过渡
/* on both the source and the destination document */
@view-transition {
  navigation: auto;
}

无需脚本,也不涉及路由器;它适用于同一源内的 push、replace 和遍历导航。只有一侧启用时,不会发生任何过渡。

完整代码:examples/shared-elements.md


模式 4:共享元素过渡

变更两侧具有匹配名称的元素会彼此过渡,而不是交叉淡入淡出。

.product-thumbnail {
  view-transition-name: product-hero;
}
.product-image {
  view-transition-name: product-hero;
}

::view-transition-group(product-hero) {
  animation-duration: var(--hero-duration);
  animation-timing-function: var(--hero-easing);
}

组会为位置和尺寸设置动画;图像对则会在组内交叉淡入淡出两个快照。如果两侧的宽高比不同,可在旧、新伪元素上设置 object-fit,以避免快照变形。

完整代码:examples/shared-elements.md


模式 5:自定义 CSS 动画

默认的交叉淡入淡出是旧、新伪元素上的 UA 样式表动画;覆盖它只需使用常规 CSS。

::view-transition-old(root) {
  animation: slide-out-left var(--transition-duration) var(--transition-easing);
}
::view-transition-new(root) {
  animation: slide-in-right var(--transition-duration) var(--transition-easing);
}

完整代码:examples/core.md


模式 6:方向感知过渡

过渡类型是 CSS 可据此选择样式的标签,因此前进和后退可以共用一组关键帧,只需应用不同的伪元素对。

html:active-view-transition-type(forwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-left;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-right;
  }
}

html:active-view-transition-type(backwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-right;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-left;
  }
}
document.startViewTransition({
  update: () => navigateForward(),
  types: ["forwards"],
});

对于只能在更新开始后才知道方向的情况,transition.types 是现有过渡上的可变集合。

完整代码:examples/spa.md


模式 7:减少动态效果
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01ms !important;
  }
}
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";

function shouldEnableTransitions(): boolean {
  if (window.matchMedia(REDUCED_MOTION_QUERY).matches) return false;
  return "startViewTransition" in document;
}

CSS 方式会缩短动画,同时保留过渡机制;对于交叉淡入淡出,这就足够了。脚本方式会完全跳过过渡,对于全视口滑动或揭示效果,这是更诚实的做法。两者之间还有第三种方式:取消 ::view-transition-group(*) 上的动画——该伪元素负责承载尺寸和位置变化——同时保留旧、新伪元素的交叉淡入淡出。这样既去掉了移动效果,又保留了反馈。

完整代码:examples/spa.md


模式 8:在过渡时计算几何信息

如果动画依赖的内容只有在点击时才知道,请等待 transition.ready,然后直接为伪元素设置动画。

const transition = document.startViewTransition(updateFn);
await transition.ready;

document.documentElement.animate(
  {
    clipPath: [`circle(0 at ${x}px ${y}px)`, `circle(${r}px at ${x}px ${y}px)`],
  },
  {
    duration: REVEAL_DURATION_MS,
    easing: REVEAL_EASING,
    pseudoElement: "::view-transition-new(root)",
  },
);

由于伪元素本身没有可供寻址的节点,因此动画会通过 pseudoElement 选项以文档元素为目标。

完整代码:examples/spa.md


<red_flags>

危险信号

运行时会出错:

  • 未检查支持情况就调用 document.startViewTransition(...) — 在缺少此 API 的环境中会抛出错误,导致 DOM 更新和动画一并丢失 — 根据 "startViewTransition" in document 进行分支判断
  • 两个可见元素使用相同的 view-transition-name — 整个过渡都会被放弃,包括原本正确的部分 — 通过脚本为每个元素分别分配名称,或使用 match-element
  • 在列表选择器的 CSS 中设置 view-transition-name — 每一行都会声明相同的名称 — 在过渡开始前通过脚本设置,并在过渡后清除
  • transition.finished 后仍保留已分配的名称 — 下次过渡时会发生冲突,因此故障会在原因出现后的下一次导航时才显现 — 在 finished 处理程序中清除名称
  • @view-transition 只写在两个文档中的一个 — 跨文档过渡需要两端都选择启用 — 也在目标文档中添加该 at-rule
  • 在 transition.ready 解析之前就为伪元素添加动画 — 此时树尚不存在,动画会静默地作用于空对象 — 先等待该 Promise
  • 没有 prefers-reduced-motion 分支 — 全视口滑动动画会呈现给不希望看到动画的用户 — 添加媒体查询,或在脚本中跳过过渡
  • <meta name="view-transition" content="same-origin"> — 已过时且会被忽略 — 改用 @view-transition at-rule

容易产生意外的行为:

  • 旧页面一侧是截图,因此其中的视频、GIF 和正在运行的动画都会冻结;新页面一侧则是实时画面,会继续播放
  • 名称在整个文档范围内全局生效,因此即使两个无关的区域相距很远,使用相同名称也会冲突
  • 过渡期间页面处于不可交互状态,因此较长的动画会直接增加交互延迟 — 导航流程中的动画应控制在 300ms 以下
  • 跨文档过渡要求导航发生在同源页面之间
  • match-element 需要 Chrome 137+ 或 Safari 18.4+,且 Firefox 不支持
  • 延迟注册的 pagereveal 处理程序会错过该事件 — 将其放在 <head> 中,或为脚本添加 blocking="render"
  • auto、inherit、none 和 unset 是 CSS 全局关键字,而非自定义标识符,因此不能用作名称

评论

支持 Markdown