Skip to content

View Transitions API 学习笔记:实现圆形扩散主题切换效果

这份笔记旨在提炼 View Transitions API 的核心概念,并通过一个实战案例——实现一个从鼠标点击位置开始的圆形扩散主题切换动画,来帮助理解其工作原理和自定义方法。

1. 核心概念:什么是 View Transitions API?

View Transitions API 提供了一种强大的机制,用于在两次不同的 DOM 状态之间创建平滑的动画过渡效果。

其核心工作流程可以概括为:

  1. 截图当前状态:当你调用 document.startViewTransition() 时,API 会立即对当前页面的可见部分进行截图。
  2. 更新 DOM:API 接收一个回调函数,你需要在这个函数中同步地完成对 DOM 的所有更新(例如,切换一个 class 来改变主题)。
  3. 截图新状态:DOM 更新后,API 会对页面的新状态再进行一次截图。
  4. 创建过渡动画:API 在一个独立的层中,处理从旧截图(::view-transition-old)到新截图(::view-transition-new)的动画。默认效果是淡入淡出(cross-fade)。

通过操作这两个截图伪元素,我们可以实现高度自定义的过渡动画。

2. 基础用法与默认效果

View Transitions API 的入门非常简单。只需将改变 DOM 的代码包裹在 document.startViewTransition() 的回调中即可。

示例代码:

javascript
const themeToggleButton = document.getElementById('theme-toggle');

themeToggleButton.addEventListener('click', () => {
  // 判断浏览器是否支持
  if (!document.startViewTransition) {
    // 不支持则直接切换,无动画
    toggleTheme();
    return;
  }

  // 使用 View Transitions
  document.startViewTransition(() => {
    toggleTheme(); // 这是我们实际更新 DOM 的函数
  });
});

function toggleTheme() {
  document.documentElement.classList.toggle('dark');
}

仅仅这样,我们就能获得一个平滑的、默认的淡入淡出过渡效果。

3. 自定义动画:实现圆形扩散

虽然默认效果不错,但此 API 的真正魅力在于自定义。我们将实现一个从鼠标点击位置开始的圆形扩散效果。

第 1 步:禁用默认动画

为了不受默认动画的干扰,我们需要先通过 CSS 将其禁用。

css
/* 禁用默认的淡入淡出动画 */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
}

第 2 步:捕获点击坐标并启动过渡

我们需要知道鼠标点击的精确位置,以便将它作为动画的起始原点。

javascript
document.addEventListener('click', (event) => {
  const { clientX: x, clientY: y } = event; // 获取点击坐标

  const transition = document.startViewTransition(() => {
    // ... DOM 更新逻辑
  });

  // 后续动画逻辑...
});

第 3 步:计算扩散半径

这是实现此效果最关键、也最复杂的一步。

目标:圆形的半径必须足够大,以确保无论用户点击屏幕上的哪个位置,最终的圆形都能完全覆盖整个视口。

思路:正确的半径应该是从点击点到视口四个角中最远那个角的距离

计算方法

  1. 获取点击点 (x, y)
  2. 计算点击点到视口左右边缘的最大水平距离 max_x
  3. 计算点击点到视口上下边缘的最大垂直距离 max_y
  4. 使用勾股定理 Math.hypot() 计算出最远的对角线距离,即为我们需要的最终半径。

代码实现

javascript
// 计算点击点到视口最远角的距离
const endRadius = Math.hypot(
  Math.max(x, window.innerWidth - x),
  Math.max(y, window.innerHeight - y)
);

第 4 步:使用 Web Animations API 创建动画

我们利用 transition.ready 这个 Promise,在过渡动画准备好时,使用 Web Animations API (element.animate) 来动态地为新视图(::view-transition-new)添加一个 clip-path 裁剪动画。

最终整合代码

以下是结合了以上所有步骤的完整 JavaScript 代码:

javascript
const themeToggleButton = document.getElementById('theme-toggle');

// 点击按钮时切换主题
themeToggleButton.addEventListener('click', (event) => {
  // 判断浏览器是否支持
  if (!document.startViewTransition) {
    toggleTheme();
    return;
  }
  
  // 1. 获取点击坐标
  const { clientX: x, clientY: y } = event;

  // 2. 计算到最远角的距离作为最终半径
  const endRadius = Math.hypot(
    Math.max(x, window.innerWidth - x),
    Math.max(y, window.innerHeight - y)
  );

  // 3. 启动视图过渡
  const transition = document.startViewTransition(() => {
    toggleTheme();
  });

  // 4. 在过渡准备好后,执行自定义动画
  transition.ready.then(() => {
    // 定义动画关键帧
    const clipPath = [
      `circle(0px at ${x}px ${y}px)`, // 初始状态:半径为0的圆
      `circle(${endRadius}px at ${x}px ${y}px)` // 结束状态:半径足以覆盖屏幕的圆
    ];

    // 为新视图应用动画
    document.documentElement.animate(
      { clipPath },
      {
        duration: 500, // 动画时长
        easing: 'ease-in', // 缓动函数
        // 指定动画的伪元素
        pseudoElement: '::view-transition-new(root)',
      }
    );
  });
});

function toggleTheme() {
    document.documentElement.classList.toggle('dark');
}

对应的 CSS (别忘了禁用默认动画):

css
/* 禁用默认的淡入淡出动画 */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
}

/* 确保混合模式正确,避免在动画过程中出现颜色闪烁 */
::view-transition-new(root) {
  mix-blend-mode: normal;
}
::view-transition-old(root) {
  z-index: 1;
}
::view-transition-new(root) {
  z-index: 999;
}


/* 简单的深色主题示例 */
:root {
    --bg-color: #fff;
    --text-color: #000;
}
.dark {
    --bg-color: #222;
    --text-color: #fff;
}
body {
    background-color: var(--bg-color);
    color: var(--text-color);
}

4. 总结

View Transitions API 极大地简化了页面状态切换动画的实现。通过以下几个关键点,我们可以构建出富有创意和吸引力的用户体验:

  • 核心函数document.startViewTransition(updateCallback)
  • 关键伪元素::view-transition-old(root)::view-transition-new(root) 是我们自定义动画的主要目标。
  • 自定义流程:先禁用默认动画,然后通过 CSS 或 JS (Web Animations API) 为伪元素添加新动画。
  • 发挥创意clip-path 只是其中一种玩法,你可以利用 transformopacity 等任何 CSS 属性来创造独特的过渡效果。