新的 View Transitions API 提供了一种更简单的方式来在两个 DOM 状态之间进行动画转换 ------ 即使是在页面加载之间也可以实现。这是一项渐进式的增强功能,可以在今天的浏览器上使用。
过去十年,CSS过渡和动画已经彻底改变了网页效果。但并非所有事情都很容易实现。例如,考虑一个包含10张图片及标题的元素列表,我们想要通过交叉淡化的效果过渡到一个新的元素列表。通常的实现方式是:
- 保留旧的 DOM 元素
- 构建新的 DOM 元素,并将其追加到页面中,确保它们处在适当的位置
- 淡出旧元素集合,同时淡入新元素集合,然后
- (可选)用新DOM元素替换旧DOM元素
但是在 View Transitions API 中,我们只需要更新 DOM 即可 ------ 这在以前是不可能做到的!
View Transitions API 的工作流程如下:
- API 对当前页面状态进行快照
- 我们更新 DOM,添加或删除元素
- API 对新的页面状态进行快照
- API 在这两个状态之间进行动画转换,默认为淡入淡出,也可以定义自己的 CSS 动画
我们只需要像以前一样更新 DOM。加上几行代码就可以在 View Transitions API 可用时渐进增强页面,实现类似 PPT 的转换效果。
该 API 目前还在实验阶段,但最新版的 Chromium 内核浏览器都已支持页面内的、单页面文档的DOM转换效果。
针对页面导航的 viewTransition API 在 Chrome 115+ 中也可用,可以实现单页面应用跳转时的动画效果,比如典型的 WordPress 网站。这个用起来更简单,不需要任何 JavaScript。
Firefox 和 Safari 尚未透露会何时支持该 API。 对于不支持 View Transitions API 的浏览器,页面会按正常非动画方式加载,所以可以放心地在今天的网站中添加相关效果。
过时的新技术
对于我们这些见过互联网发展历史的老程序员来说,可能会有一种熟悉的既视感。微软在 IE 4.0(1997年发布)和 IE 5.5(2000年发布)中添加了元素级别和整页的过渡效果。我们可以通过 <meta>
标签添加 PowerPoint式的方框、圆形、翻页、溶解、百叶窗、滑动、条形和螺旋效果:
html
<meta http-equiv="Page-Enter" content="progid:DXImageTransform.Microsoft.Iris(Motion='in', IrisStyle='circle')">
<meta http-equiv="Page-Exit" content="progid:DXImageTransform.Microsoft.Iris(Motion='out', IrisStyle='circle')">
奇怪的是,这种技术从未被广泛采用。它不是 Web 标准,但当时 W3C 还在襁褓期 ------ 开发者也确实乐于使用许多其他 IE 专有的技术!
为什么过了25年才出现一个替代方案呢?!
创建页面内的过渡效果
在 Chrome 中查看以下 CodePen 示例,并点击头部的导航以查看两种状态之间的1秒淡入淡出效果。
页面 HTML 包含两个文章元素,ID 为 article1 和 article2:
html
<main>
<div id="articleroot">
<article id="article1">
<!-- 文章1内容 -->
</article>
<article id="article2">
<!-- 文章2内容 -->
</article>
</div>
</main>
switchArticle() 函数处理所有 DOM 更新。它通过添加或删除 hidden 属性来显示或隐藏每个文章。在页面加载时,从页面 URL 的 location.hash 确定激活的文章,如果 hash 不存在,则默认为第一个 <article>
元素:
js
// 获取页面所有文章
const articles = document.getElementsByTagName('article');
// 页面加载时显示一个文章
switchArticle();
// 显示激活文章
function switchArticle(e) {
const hash = e?.target?.hash?.slice(1) || location?.hash?.slice(1);
Array.from(articles).forEach(article => {
if (article.id === hash || (!hash && !i)) {
article.removeAttribute('hidden');
}
else {
article.setAttribute('hidden', '');
}
});
}
事件处理函数监听所有页面点击事件,当用户点击包含 #hash 的链接时,调用 switchArticle():
js
// 导航点击事件处理
document.body.addEventListener('click', e => {
if (!e?.target?.hash) return;
switchArticle(e);
});
我们现在可以更新这个事件处理器以使用 View Transitions,先判断该 API 是否可用:
js
document.body.addEventListener('click', e => {
if (!e?.target?.hash) return;
if (document.startViewTransition) {
// 使用 View Transition 动画效果
document.startViewTransition(() => switchArticle(e));
} else {
// View Transition 不可用时正常切换
switchArticle(e);
}
});
document.startViewTransition() 会拍摄初始状态快照、运行 switchArticle() 更新 DOM、拍摄结束状态快照,最后在两者之间应用默认0.5秒的淡入淡出过渡动画。
我们可以在 CSS 中使用以下选择器针对旧状态和新状态应用样式:
css
::view-transition-old(root) {
/* 动画出效果 */
}
::view-transition-new(root) {
/* 动画入效果 */
}
创建更精细的过渡效果
下面的 CodePen 示例通过 ::view-transition-old(root) 和 ::view-transition-new(root) 选择器添加了更好的动画效果。
CSS 中定义了淡入淡出和旋转的 transition-out 和 transition-in 动画:
css
::view-transition-old(root) {
animation: 1s transition-out 0s ease;
}
::view-transition-new(root) {
animation: 1s transition-in 0s ease;
}
@keyframes transition-out {
from {
opacity: 1;
translate: 0;
rotate: 0;
}
to {
opacity: 0;
translate: -3rem -5rem;
rotate: -10deg;
}
}
@keyframes transition-in {
from {
opacity: 0;
translate: 3rem 5rem;
rotate: -10deg;
}
to {
opacity: 1;
translate: 0;
rotate: 0;
}
}
这些动画会应用到整个页面,包括 <header>
元素,看起来不太正常。我们可以通过设置 view-transition-name 为特定值的方式给不同元素应用动画效果:
css
header {
view-transition-name: header;
}
然后可以针对该元素应用不同的动画:
css
::view-transition-old(header) {
/* ... */
}
::view-transition-new(header) {
/* ... */
}
在这个例子中,我们不想 header 有任何动画效果,所以不需要定义任何样式。::view-transition-old(root) 和 ::view-transition-new(root) 选择器现在仅应用于不包含 header 的其他元素。
因为我们在 CSS 中定义了动画效果,可以在开发者工具的动画面板中更详细地检查和调试动画。
使用 Web Animations API
使用 Web Animations API 可以在 JavaScript 中进行更细致的动画效果和时间控制。
document.startViewTransition() 返回一个对象,其ready属性是一个Promise,在过渡的旧状态和新状态伪元素可用时resolve:
js
// 使用 Web Animations API
const transition = document.startViewTransition(doDOMUpdate);
transition.ready.then(() => {
document.documentElement.animate(
[
{ rotate: '0deg' },
{ rotate: '360deg' }
],
{
duration: 1000,
easing: 'ease',
pseudoElement: '::view-transition-new(root)'
}
);
});
创建多页面导航过渡效果
我们也可以在多页面应用(比如典型的WordPress网站)的页面加载之间使用 View Transitions。这被称为 viewTransition API for navigations,需要在 Chrome 115(当前开发者版Canary)中启用 chrome://flags/ 实验标志来开启。
启用只需要在 HTML head 中添加一个 meta 标签:
html
<meta name="view-transition" content="same-origin" />
然后我们可以定义与页面内过渡相同的 ::view-transition-old 和 ::view-transition-new 选择器。不需要任何 JavaScript,除非想要使用 Web Animations API。
该导航 API 是否默认启用还不确定。但我们今天就可以使用该技术,因为不支持的浏览器会退化为正常的非动画页面加载。
禁用动画
对于部分遭受运动障碍的用户来说,动画可能会引起不适。大多数操作系统提供禁用效果的用户偏好设置。我们可以使用 CSS 的 prefers-reduced-motion 媒体查询检测这一设置并相应关闭动画:
css
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
总结
View Transitions API简化了改变元素状态时的动画转换,无论是页面内还是页面之间的导航。这之前也可以实现类似的过渡效果,但需要大量 JavaScript 代码,还需要注意不要影响浏览器的正常导航操作如后退按钮。
该 API 还非常新。不能保证它会保持不变,成为 W3C 标准,或在 Firefox 和 Safari 中实现。但是我们今天就可以使用该 API,因为它是渐进增强的。在不支持该 API 的浏览器上,页面还是可以正常工作,只是没有动画效果。API 可能会有变动的风险,但即使需要维护,旧代码也不应该对网站造成影响,更新工作应该也很小。
该 API 的一个缺点是可能会在 Web 上出现大量令人讨厌的冗长和夸张的动画效果,因为站长认为这很符合自己的"品牌形象"!理想情况下,动画应该快速和微妙地突出 UI 的变化。少即是多。
参考资料:
- Chrome Developers: Smooth and simple transitions with the View Transitions API: lots of examples, suggestions, and techniques
- MDN: View Transitions API
- W3C: CSS View Transitions Module Level 1
- 参考翻译原文