浅谈 View Transitions API

新的 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 的变化。少即是多。

参考资料:

相关推荐
EricWang13583 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning3 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人13 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂35 分钟前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石43 分钟前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程44 分钟前
【前端基础】CSS基础
前端·css
嚣张农民1 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
周亚鑫1 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
Justinc.2 小时前
CSS3新增边框属性(五)
前端·css·css3
neter.asia2 小时前
vue中如何关闭eslint检测?
前端·javascript·vue.js