利用 View Transitions API 实现简单流畅的过渡

Jake Archibald
Jake Archibald

浏览器支持

  • 111
  • 111
  • x
  • x

来源

利用 View Transition API,您可以在一个步骤中轻松更改 DOM,同时创建两种状态之间的动画转换。Chrome 111 及更高版本支持此功能。

使用 View Transition API 创建的转场效果。试用演示版网站 - 需要安装 Chrome 111 或更高版本。

为什么需要此功能?

页面过渡效果不仅美观,还可以传达用户流向,并明确哪些元素在各个页面之间是相关的。它们甚至可能在数据提取期间发生,这样可以更快地感知性能。

但是,我们已经拥有了网络上已有的动画工具,例如 CSS 过渡CSS 动画Web Animation API,那么为什么我们需要用新工具来移动元素呢?

事实上,即使我们已经有工具,状态转换也非常困难。

即使是像简单的淡入淡出之类的内容,也要求两种状态同时存在。这就带来了易用性方面的挑战,例如处理传出元素上的其他交互。此外,对于辅助设备的用户,在一段时间内,之前状态和之后状态会同时在 DOM 中,内容可能会在树周围以一种非常直观的方式移动,但很容易导致阅读位置和焦点丢失。

如果两种状态的滚动位置不同,处理状态变化尤为困难。而且,如果某个元素从一个容器移动到另一个容器,您可能会在 overflow: hidden 和其他形式的裁剪方面遇到困难,这意味着您必须重新构建您的 CSS 以获得想要的效果。

这并不是不可能,只是非常困难

视图转换提供了一种更简单的方法,它允许您在没有状态重叠的情况下进行 DOM 更改,但可以使用已截取快照的视图创建状态之间的转换动画。

此外,虽然当前的实现针对的是单页应用 (SPA),但该功能将扩展为支持在加载整个页面之间进行转换,而这目前是不可能的。

标准化状态

该功能正在 W3C CSS 工作组中作为草稿规范进行开发。

对 API 设计感到满意后,我们将启动相关流程和检查,将此功能顺利发布至稳定版。

开发者的反馈非常重要,请在 GitHub 上提交问题,并附上相关建议和问题。

最简单的过渡:淡入淡出

默认的视图转换是淡入淡出,因此它是对 API 的实用介绍:

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // With a transition:
  document.startViewTransition(() => updateTheDOMSomehow(data));
}

其中 updateTheDOMSomehow 用于将 DOM 更改为新状态。您可以随意地执行此操作:添加/移除元素、更改类名称、更改样式...并不重要。

就像这样,页面淡入淡出:

默认的淡入淡出。最少演示来源

好吧,淡入淡出效果没那么好幸运的是,过渡可以自定义,但在进行这方面的自定义之前,我们需要了解基本的淡入淡出的工作原理。

这些过渡期的运作方式

以上面的代码示例为例:

document.startViewTransition(() => updateTheDOMSomehow(data));

当调用 .startViewTransition() 时,该 API 会捕获页面的当前状态。这包括截取屏幕截图。

完成后,系统会调用传递给 .startViewTransition() 的回调。这就是 DOM 发生变化的地方。然后,该 API 会捕获网页的新状态。

捕获状态后,API 会构造一个伪元素树,如下所示:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

::view-transition 位于叠加层中,位于页面上所有其他内容之上。如果您想设置过渡的背景颜色,便可使用此属性。

::view-transition-old(root) 是旧视图的屏幕截图,::view-transition-new(root) 是新视图的实时表示。两者均呈现为 CSS“替换内容”(例如 <img>)。

旧视图的动画效果从 opacity: 1opacity: 0,而新视图则从 opacity: 0 过渡到 opacity: 1,从而形成淡入淡出。

所有的动画都是使用 CSS 动画执行的,因此可以使用 CSS 进行自定义。

轻松定制

上述所有伪元素均可使用 CSS 来定位,而且由于动画是使用 CSS 定义的,因此您可以使用现有的 CSS 动画属性对其进行修改。例如:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 5s;
}

完成这项更改后,淡出现在变得很慢:

长淡入淡出。最少演示来源

好吧,那还是没印象了。我们改为实现 Material Design 的共享轴转场

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes slide-from-right {
  from { transform: translateX(30px); }
}

@keyframes slide-to-left {
  to { transform: translateX(-30px); }
}

::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

结果如下:

共享轴转场。最少演示来源

转换多个元素

在上一个演示中,整个页面都涉及了共享轴过渡。这适用于页面的大部分内容,但似乎不太适合标题,因为标题就会滑出,只是为了重新滑回。

为避免出现这种情况,您可以从页面的其余部分提取页眉,以便单独为其添加动画效果。可通过为元素分配 view-transition-name 来实现。

.main-header {
  view-transition-name: main-header;
}

view-transition-name 的值可以是您所需的任何值(none 除外,这意味着没有转换名称)。它用于唯一标识整个过渡过程中的元素。

结果:

具有固定标题的共享轴转场。最少演示来源

现在,标题会留在原处并淡入淡出。

该 CSS 声明导致伪元素树发生变化:

::view-transition
├─ ::view-transition-group(root)
│  └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
   └─ ::view-transition-image-pair(main-header)
      ├─ ::view-transition-old(main-header)
      └─ ::view-transition-new(main-header)

现在有两个转换组。一个用于标题,另一个用于其余部分。这些元素可以通过 CSS 单独定位,并且采用不同的过渡。不过,在本例中,main-header 保留了默认过渡,即淡入淡出。

好的,默认过渡不只是淡入淡出,::view-transition-group 还会过渡:

  • 定位和转换(通过 transform
  • 宽度
  • 身高

到目前为止,这无关紧要,因为 DOM 更改的两侧标题大小和位置都相同。但我们也可以提取标题中的文本:

.main-header-text {
  view-transition-name: main-header-text;
  width: fit-content;
}

使用 fit-content,使元素按照文本的大小调整,而不是拉伸至剩余宽度。否则,返回箭头会减小标题文本元素的大小,而我们希望它在两个页面中的大小相同。

现在,我们要处理以下三个部分:

::view-transition
├─ ::view-transition-group(root)
│  └─ …
├─ ::view-transition-group(main-header)
│  └─ …
└─ ::view-transition-group(main-header-text)
   └─ …

同样,只需使用默认值即可:

滑动标题文本。最少演示来源

现在,标题文本有一点令人满意的滑行,为返回按钮留出空间。

调试转换

由于视图过渡是在 CSS 动画的基础上构建的,因此 Chrome 开发者工具中的 Animations 面板非常适合调试过渡。

使用动画面板,您可以暂停下一个动画,然后在该动画中前后拖动。在此期间,可以在元素面板中找到过渡伪元素。

使用 Chrome 开发者工具调试视图转换。

过渡元素不必是同一个 DOM 元素

到目前为止,我们已使用 view-transition-name 为标题以及标题中的文本创建了单独的过渡元素。从概念上来讲,这些元素在 DOM 更改之前和之后是相同的,但您可以在其中创建过渡效果。

例如,可以为主视频嵌入提供 view-transition-name

.full-embed {
  view-transition-name: full-embed;
}

然后,在用户点击缩略图时,可以为其赋予相同的 view-transition-name,只是在过渡期间会保持不变:

thumbnail.onclick = async () => {
  thumbnail.style.viewTransitionName = 'full-embed';

  document.startViewTransition(() => {
    thumbnail.style.viewTransitionName = '';
    updateTheDOMSomehow();
  });
};

结果:

一个元素正在转换为另一个元素。最少演示来源

现在,缩略图将过渡到主图片。虽然它们在概念上(和字面上)是不同的元素,但 Transition API 将它们视为相同的元素,因为它们共享相同的 view-transition-name

实际代码比上述简单示例稍微复杂一点,因为它还处理了转换回缩略图页面的事宜。如需了解完整实现,请查看源代码

自定义进入和退出过渡

看看下面这个示例:

进入和退出边栏。最少演示来源

边栏是转换的一部分:

.sidebar {
  view-transition-name: sidebar;
}

但与前面示例中的标题不同,边栏并非所有页面都显示。如果两种状态都有边栏,过渡伪元素将如下所示:

::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
   └─ ::view-transition-image-pair(sidebar)
      ├─ ::view-transition-old(sidebar)
      └─ ::view-transition-new(sidebar)

不过,如果边栏仅位于新页面上,则 ::view-transition-old(sidebar) 伪元素不会存在。由于边栏没有“旧”图片,因此图片对只有 ::view-transition-new(sidebar)。同样,如果边栏仅位于旧页面上,则图片对将只有一个 ::view-transition-old(sidebar)

在上面的演示中,根据边栏是处于进入、退出还是同时显示这两种状态,其转换效果会有所不同。它通过从右侧滑入并淡入进入,而通过向右滑动并淡出退出;在两种状态同时显示时,它都会留在原处。

若要创建特定的进入和退出过渡,您可以使用 :only-child 伪类,在旧/新伪元素是图片对中的唯一子元素时定位到它:

/* Entry transition */
::view-transition-new(sidebar):only-child {
  animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Exit transition */
::view-transition-old(sidebar):only-child {
  animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}

在这种情况下,边栏在两种状态下都没有特定的转换,因为默认设置是完美的。

异步 DOM 更新和等待内容

传递给 .startViewTransition() 的回调可以返回一个 promise,以允许异步 DOM 更新并等待重要内容准备就绪。

document.startViewTransition(async () => {
  await something;
  await updateTheDOMSomehow();
  await somethingElse;
});

在 promise 执行之前,转换不会开始。在此期间,页面会冻结,因此应尽可能缩短延迟时间。具体来说,网络提取应在调用 .startViewTransition() 之前完成,同时网页仍具有完全可交互性,而不是作为 .startViewTransition() 回调的一部分执行。

如果您决定等待图片或字体准备就绪,请务必使用激进型超时:

const wait = ms => new Promise(r => setTimeout(r, ms));

document.startViewTransition(async () => {
  updateTheDOMSomehow();

  // Pause for up to 100ms for fonts to be ready:
  await Promise.race([document.fonts.ready, wait(100)]);
});

但在某些情况下,最好完全避免延迟,并使用您已有的内容。

充分利用现有的内容

如果缩略图过渡到较大的图片:

默认过渡为淡入淡出,这意味着缩略图可以与尚未加载的完整图片进行淡入淡出。

处理此问题的一种方法是在开始转换之前等待整个图片加载完毕。理想情况下,此操作应在调用 .startViewTransition() 之前完成,以便页面保持互动状态,并且可显示一个旋转图标以向用户表明内容正在加载。但在这种情况下,有一种更好的方法:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
}

现在,缩略图不会消失,只是位于完整图片下方。这意味着,如果新视图尚未加载,则缩略图会在整个过渡过程中一直显示。这意味着转换可以直接开始,并且整个图片可以在自己的时间内完成加载。

如果新视图具有透明度,则不起作用,但在本例中,我们知道没有透明度,因此我们可以进行此优化。

处理宽高比的变化

方便的是,目前为止所有转换都指向具有相同宽高比的元素,但情况并非总是如此。如果缩略图为 1:1,而主图片为 16:9,该怎么办?

一个元素转换为另一个元素,宽高比发生变化。最少演示来源

在默认过渡中,该组的动画效果会从之前的尺寸过渡到之后的尺寸。新旧视图均采用组为 100% 的宽度并会自动采用高度,这意味着无论组大小如何,它们都会保持宽高比不变。

这是一个很好的默认设置,但在本例中并不是我们想要的。因此:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
  /* Make the height the same as the group,
  meaning the view size might not match its aspect-ratio. */
  height: 100%;
  /* Clip any overflow of the view */
  overflow: clip;
}

/* The old view is the thumbnail */
::view-transition-old(full-embed) {
  /* Maintain the aspect ratio of the view,
  by shrinking it to fit within the bounds of the element */
  object-fit: contain;
}

/* The new view is the full image */
::view-transition-new(full-embed) {
  /* Maintain the aspect ratio of the view,
  by growing it to cover the bounds of the element */
  object-fit: cover;
}

这意味着,当宽度增加时,缩略图始终位于元素的中心,但当从 1:1 过渡到 16:9 时,整个图片会“未剪裁”。

根据设备状态更改过渡效果

您可能需要在移动设备和桌面设备上使用不同的转场效果,如下例所示,在移动设备上,从一侧开始完整滑动,而在桌面设备上则进行更为细微的滑动:

一个元素正在转换为另一个元素。最少演示来源

这可以通过常规媒体查询来实现:

/* Transitions for mobile */
::view-transition-old(root) {
  animation: 300ms ease-out both full-slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out both full-slide-from-right;
}

@media (min-width: 500px) {
  /* Overrides for larger displays.
  This is the shared axis transition from earlier in the article. */
  ::view-transition-old(root) {
    animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
  }

  ::view-transition-new(root) {
    animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
  }
}

您可能还需要根据匹配的媒体查询更改为其分配 view-transition-name 的元素。

对“减少动作”偏好设置做出响应

用户可以通过其操作系统指明他们更喜欢减少动作。该偏好设置会通过 CSS 公开

您可以选择阻止这些用户进行转换:

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

不过,偏好“减少动作”并不意味着用户不需要“没有动作”。除了上述方法,您可以选择更细微的动画,但动画仍能表达元素和数据流之间的关系。

根据导航类型更改过渡效果

有时,在从一个特定类型的页面导航到另一个页面时,应该进行专门定制的转换。或者,“返回”导航应该不同于“前进”导航。

返回“返回”时的过渡效果不同。最少演示来源

处理这些情况的最佳方法是在 <html> 上设置类名称(也称为文档元素):

if (isBackNavigation) {
  document.documentElement.classList.add('back-transition');
}

const transition = document.startViewTransition(() =>
  updateTheDOMSomehow(data)
);

try {
  await transition.finished;
} finally {
  document.documentElement.classList.remove('back-transition');
}

此示例使用了 transition.finished,这是一个在转换达到其结束状态后解析的 promise。API 参考文档介绍了此对象的其他属性。

现在,您可以在 CSS 中使用该类名称来更改过渡:

/* 'Forward' transitions */
::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
      cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
  animation-name: fade-out, slide-to-right;
}

.back-transition::view-transition-new(root) {
  animation-name: fade-in, slide-from-left;
}

与媒体查询一样,这些类的存在也可用于更改获取 view-transition-name 的元素。

在不冻结其他动画的情况下进行转换

请看下面的视频转换位置演示:

视频过渡。最少演示来源

您发现它有什么问题吗?如果您没有这样做,也不用担心。在这里,它的播放速度会变慢:

视频过渡,较慢。最少演示来源

在过渡过程中,视频看起来会冻结,然后视频的播放版本会淡入。这是因为 ::view-transition-old(video) 是旧视图的屏幕截图,而 ::view-transition-new(video) 是新视图的实时图片。

您可以解决这个问题,但首先要问问自己是否值得修正。如果您在以正常速度播放过渡时没有发现“问题”,我就不愿更改它。

如果您确实想要解决此问题,请不要显示 ::view-transition-old(video),直接切换到 ::view-transition-new(video)。您可以通过替换默认样式和动画来实现此目的:

::view-transition-old(video) {
  /* Don't show the frozen old view */
  display: none;
}

::view-transition-new(video) {
  /* Don't fade the new view in */
  animation: none;
}

这样就大功告成了!

视频过渡,较慢。最少演示来源

现在,视频会在整个过渡过程中播放。

使用 JavaScript 添加动画效果

到目前为止,所有转换都是使用 CSS 定义的,但有时 CSS 是不够的:

圆形过渡。最少演示来源

此过渡的以下几个部分是单靠 CSS 无法实现的:

  • 动画会从点击位置开始。
  • 动画的结束位置是一个圆形,并有距离最远角的半径。不过,有希望将来 CSS 能够实现这一点。

幸运的是,您可以使用 Web Animation API 创建转场效果!

let lastClick;
addEventListener('click', event => (lastClick = event));

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // Get the click position, or fallback to the middle of the screen
  const x = lastClick?.clientX ?? innerWidth / 2;
  const y = lastClick?.clientY ?? innerHeight / 2;
  // Get the distance to the furthest corner
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  );

  // With a transition:
  const transition = document.startViewTransition(() => {
    updateTheDOMSomehow(data);
  });

  // Wait for the pseudo-elements to be created:
  transition.ready.then(() => {
    // Animate the root's new view
    document.documentElement.animate(
      {
        clipPath: [
          `circle(0 at ${x}px ${y}px)`,
          `circle(${endRadius}px at ${x}px ${y}px)`,
        ],
      },
      {
        duration: 500,
        easing: 'ease-in',
        // Specify which pseudo-element to animate
        pseudoElement: '::view-transition-new(root)',
      }
    );
  });
}

此示例使用了 transition.ready,该 promise 会在成功创建过渡伪元素后进行解析。API 参考文档介绍了此对象的其他属性。

使用过渡效果作为增强功能

View Transition API 旨在“封装”DOM 更改并为其创建过渡。不过,应将过渡视为增强功能,因为,如果 DOM 更改成功但过渡失败,您的应用不应进入“错误”状态。理想情况下,过渡不应该失败,但如果失败,它不应该破坏其余的用户体验。

为了将转换视为增强功能,在使用 Transition Promise 时,避免在转换失败时导致应用抛出。

错误做法
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

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

  await transition.ready;

  document.documentElement.animate(
    {
      clipPath: [`inset(50%)`, `inset(0)`],
    },
    {
      duration: 500,
      easing: 'ease-in',
      pseudoElement: '::view-transition-new(root)',
    }
  );
}

该示例的问题在于,如果过渡无法达到 ready 状态,switchView() 将拒绝,但这并不意味着视图无法切换。DOM 可能已成功更新,但存在重复的 view-transition-name,因此跳过了转换。

请改为执行以下操作:

正确做法
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

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

  animateFromMiddle(transition);

  await transition.updateCallbackDone;
}

async function animateFromMiddle(transition) {
  try {
    await transition.ready;

    document.documentElement.animate(
      {
        clipPath: [`inset(50%)`, `inset(0)`],
      },
      {
        duration: 500,
        easing: 'ease-in',
        pseudoElement: '::view-transition-new(root)',
      }
    );
  } catch (err) {
    // You might want to log this error, but it shouldn't break the app
  }
}

此示例使用 transition.updateCallbackDone 等待 DOM 更新,并在更新失败时拒绝更新。如果过渡失败,switchView 不再拒绝;它会在 DOM 更新完成时进行解析,并在过渡失败时拒绝。

如果您希望 switchView 在新视图“稳定”后解析(例如,任何动画转场已完成或跳至结尾),请将 transition.updateCallbackDone 替换为 transition.finished

不是 polyfill,不过...

我认为这个特征无法以任何有用的方式进行 polyfill,但我很高兴被证明是错误的!

不过,在不支持视图转换的浏览器中,此辅助函数会大大简化操作:

function transitionHelper({
  skipTransition = false,
  classNames = [],
  updateDOM,
}) {
  if (skipTransition || !document.startViewTransition) {
    const updateCallbackDone = Promise.resolve(updateDOM()).then(() => {});

    return {
      ready: Promise.reject(Error('View transitions unsupported')),
      updateCallbackDone,
      finished: updateCallbackDone,
      skipTransition: () => {},
    };
  }

  document.documentElement.classList.add(...classNames);

  const transition = document.startViewTransition(updateDOM);

  transition.finished.finally(() =>
    document.documentElement.classList.remove(...classNames)
  );

  return transition;
}

其使用方式如下:

function spaNavigate(data) {
  const classNames = isBackNavigation ? ['back-transition'] : [];

  const transition = transitionHelper({
    classNames,
    updateDOM() {
      updateTheDOMSomehow(data);
    },
  });

  // …
}

在不支持 View Transitions 的浏览器中,系统仍会调用 updateDOM,但不会有动画过渡效果。

您还可以提供一些 classNames,以便在转场期间添加到 <html>,从而更轻松地根据导航类型更改转场效果

如果您不想使用动画,也可以将 true 传递给 skipTransition,即使在支持视图转换的浏览器中也是如此。如果网站的用户偏好设置是停用过渡,此功能会非常有用。

使用框架

如果您使用的是将 DOM 更改抽象化的库或框架,比较棘手的部分是了解 DOM 更改何时完成。以下是一组在不同框架中使用上面的帮助程序的示例。

  • React - 这里的键是 flushSync,它会同步应用一组状态更改。是的,使用该 API 会收到一条重要警告,但 Dan Abramov 向我保证,该 API 适用于这种情况。与往常的 React 和异步代码一样,在使用 startViewTransition 返回的各种 promise 时,请注意您的代码是否以正确的状态运行。
  • Vue.js - 这里的键是 nextTick,它会在 DOM 更新后执行。
  • Svelte - 与 Vue 非常相似,但等待下一次更改的方法是 tick
  • Lit - 这里的键是组件中的 this.updateComplete promise,在 DOM 更新后执行。
  • Angular - 这里的关键是 applicationRef.tick,它会清空待处理的 DOM 更改。从 Angular 17 版开始,您可以使用 @angular/router 附带的 withViewTransitions

API 参考文档

const viewTransition = document.startViewTransition(updateCallback)

启动一个新的 ViewTransition

捕获文档的当前状态后,系统会调用 updateCallback

然后,当 updateCallback 返回的 promise 执行时,转换将从下一帧开始。如果 updateCallback 返回的 promise 拒绝,则会放弃转换。

ViewTransition 的实例成员:

viewTransition.updateCallbackDone

updateCallback 返回的 promise 执行时执行或在拒绝时拒绝的 promise。

View Transition API 用于封装 DOM 更改并创建转场。不过,有时您并不关心过渡动画是否成功,您只想知道 DOM 是否发生更改以及何时发生。updateCallbackDone 适用于该用例。

viewTransition.ready

一个 promise,在过渡的伪元素创建完毕后,并且动画即将开始播放后执行。

如果转换无法开始,则会拒绝。这可能是因为配置错误,例如重复的 view-transition-name,或者 updateCallback 返回被拒绝的 promise。

这对于使用 JavaScript 为过渡伪元素添加动画效果非常有用。

viewTransition.finished

一个 promise,在最终状态对用户完全可见且可互动时执行的。

仅当 updateCallback 返回被拒绝的 promise 时,才会拒绝,因为这表示未创建最终状态。

否则,如果转换无法开始,或在转换过程中被跳过,则仍会达到结束状态,因此 finished 会执行。

viewTransition.skipTransition()

跳过过渡的动画部分。

这不会跳过对 updateCallback 的调用,因为 DOM 更改与转换是分开的。

默认样式和转换参考

::view-transition
根伪元素,用于填充视口并包含每个 ::view-transition-group
::view-transition-group

已确定无误位置。

在“之前”和“之后”状态之间转换 widthheight

在“之前”和“之后”视口空间四边形之间转换 transform

::view-transition-image-pair

完全可以填满整个群组。

具有 isolation: isolate,用于限制 plus-lighter 混合模式对旧视图和新视图的影响。

::view-transition-new::view-transition-old

该位置绝对位于封装容器的左上角。

填满整个组宽度,但具有自动高度,因此它会保持宽高比,而不是填充组。

具有 mix-blend-mode: plus-lighter 以允许实现真正的淡入淡出。

旧视图从 opacity: 1 过渡到 opacity: 0。新的视图从 opacity: 0 过渡到 opacity: 1

反馈

在此阶段,开发者的反馈非常重要,欢迎在 GitHub 上提交问题,并附上相关建议和问题。