View Transitions API 学习笔记:实现圆形扩散主题切换效果
这份笔记旨在提炼 View Transitions API 的核心概念,并通过一个实战案例——实现一个从鼠标点击位置开始的圆形扩散主题切换动画,来帮助理解其工作原理和自定义方法。
1. 核心概念:什么是 View Transitions API?
View Transitions API 提供了一种强大的机制,用于在两次不同的 DOM 状态之间创建平滑的动画过渡效果。
其核心工作流程可以概括为:
- 截图当前状态:当你调用
document.startViewTransition()时,API 会立即对当前页面的可见部分进行截图。 - 更新 DOM:API 接收一个回调函数,你需要在这个函数中同步地完成对 DOM 的所有更新(例如,切换一个
class来改变主题)。 - 截图新状态:DOM 更新后,API 会对页面的新状态再进行一次截图。
- 创建过渡动画:API 在一个独立的层中,处理从旧截图(
::view-transition-old)到新截图(::view-transition-new)的动画。默认效果是淡入淡出(cross-fade)。
通过操作这两个截图伪元素,我们可以实现高度自定义的过渡动画。
2. 基础用法与默认效果
View Transitions API 的入门非常简单。只需将改变 DOM 的代码包裹在 document.startViewTransition() 的回调中即可。
示例代码:
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 将其禁用。
/* 禁用默认的淡入淡出动画 */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}第 2 步:捕获点击坐标并启动过渡
我们需要知道鼠标点击的精确位置,以便将它作为动画的起始原点。
document.addEventListener('click', (event) => {
const { clientX: x, clientY: y } = event; // 获取点击坐标
const transition = document.startViewTransition(() => {
// ... DOM 更新逻辑
});
// 后续动画逻辑...
});第 3 步:计算扩散半径
这是实现此效果最关键、也最复杂的一步。
目标:圆形的半径必须足够大,以确保无论用户点击屏幕上的哪个位置,最终的圆形都能完全覆盖整个视口。
思路:正确的半径应该是从点击点到视口四个角中最远那个角的距离。
计算方法:
- 获取点击点
(x, y)。 - 计算点击点到视口左右边缘的最大水平距离
max_x。 - 计算点击点到视口上下边缘的最大垂直距离
max_y。 - 使用勾股定理
Math.hypot()计算出最远的对角线距离,即为我们需要的最终半径。
代码实现:
// 计算点击点到视口最远角的距离
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 代码:
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 (别忘了禁用默认动画):
/* 禁用默认的淡入淡出动画 */
::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只是其中一种玩法,你可以利用transform、opacity等任何 CSS 属性来创造独特的过渡效果。