View Transition API —— 给 Web 动效锦上添花

? 转载请注明出处。

欢迎来到新玩具乐园,今天我们来上手把玩 View Transition API。

一、Why View Transition

Transition 指的是 UI 层面的状态过渡,恰当的过渡效果能带给用户舒适感,提升产品的品质。

pasted image 0.gif

一般来说,我们有三种方式可以实现过渡动效:

  • CSS Transition
  • CSS Animation
  • Web Animation API

那么 View Transition API 的用武之地在哪?

我们先来看一个示例

movie.gif

当点击列表页的影片封面切换到详情页时,影片封面图在切换过程中是逐渐放大进入详情页的。

假如让你来操刀,你会怎么实现?

经典思路也许是:专门设置一个 DOM 元素,用来模拟过渡状态中的封面图,配合 绝对定位、CSS 动画和 JavaScript 计算来实现。

确实行得通,但总感觉稍欠优雅,略显狼狈。

这其实暴露了传统实现方式的短板:应对过渡中间态比较吃力。

我们的确可以增加额外的 DOM 元素来模拟中间态,但冗余的环节让代码复杂度升高,交互过程更不可控。所谓节外多生枝,夜长易梦多。

在这样的成本面前,跨页面过渡动效更是不敢想。

而能让我们轻松应对中间态的,正是 View Transition API。

? View Transition API,曾用名 Shared Element Transition、Page Transition,目前是 W3C 标准的工作组草稿,目前仅在 Chrome/Edge 111+ 版本中支持

二、认识 API

View Transition API 带来的新知识并不多:

  • JavaScript 方法与属性
    • startViewTransition()
    • updateCallbackDone
    • ready
    • finished
    • skipTransition()
  • CSS 规则和伪元素
    • view-transition-name
    • ::view-transition
    • ::view-transition-group()
    • ::view-transition-image-pair()
    • ::view-transition-old()
    • ::view-transition-new()

下面我们来逐一领略。

startViewTransition

开启 View Transition 动效,只需要一行代码:

document.startViewTransition(updateCallback);

startViewTransition 方法接收一个回调函数作为参数,在回调函数 updateCallback 里,我们执行变更 DOM 状态的逻辑,比如切换页面路由、更新页面内容等。

来看一个最小示例:

basic.gif

左列直接新增列表项,而右侧的新增逻辑用 startViewTransition 包装。(完整例子见 view-transition-demos/01-basic.html

三个状态

调用 startViewTransition 后,我们会得到一个实例对象:

const vt = document.startViewTransition(updateCallback);

实例对象具有三个属性,分别代表过渡过程中的三个状态节点:

  • updateCallbackDone:回调函数执行完毕
  • ready:准备就绪,即将开始播放动效
  • finished:动效播放完毕

这三个属性值都是 Promise 对象,这就方便了我们精细掌控动效过程。

await vt.updateCallbackDone;
// Do whatever you want
await vt.ready;

// Do whatever you want
await vt.finished;
// Do whatever you want

skipTransition

实例对象还具有一个方法,可以跳过动效过程。

vt.skipTransition();

需要指出的是,调用 skipTransition 只会跳过动效,但 updateCallback 仍会正常执行。这正是 View Transition API 的设计理念:锦上添花,不喧宾夺主。

另外,在不同的状态节点调用 skipTransition,效果也不同。这里卖个关子,热切邀请大家运行一下示例代码(view-transition-demos/02-api.html),看看到底有什么不同之处。

view-transition-name

API 在 CSS 规则中新增了一个属性 view-transition-name,该属性可以应用于任何元素,属性值也可以是任何不重复的字符串名称。

.box {

  view-transition-name: box;
}

对于设置了 view-transition-name 的元素,浏览器会将其识别为需要应用过渡动效的元素。

回顾一下本文的第一个最小示例(view-transition-demos/01-basic.html),在实现代码里,我们并未给任何元素设置 view-transition-name,但仍然产生了过渡动效。这是因为在缺省情况下,浏览器会给根节点 —— html 节点 —— 隐式地设置为 view-transition-name: root;

伪元素树

当浏览器识别到了动效的目标元素,就会针对目标生成一系列伪元素,构建成树形结构,插入到 html 第一个子节点的位置上。

image.png

伪元素树的 z-index 层级是最高的,覆盖在所有其他元素之上。这确保了在过渡过程中,页面是不可交互的。

如果页面中有多个元素都设置了 view-transition-name,那么在树根 ::view-transition 之下,就会出现多个 ::view-transition-group ,与 root group 平级。

三、原理 & 生命周期

View Transition API 的设计足够简洁,因此其实现原理也并不艰深。我们将聚焦于过渡动效从始至终的全过程,也就是着眼于其整个生命周期,来探究它的动效到底是如何实现的。

1.startViewTransition

一段生命周期的起始,源自于我们调用 startViewTransition

2. 抓拍旧状态快照

在 DOM 状态变化前,浏览器抓拍一张旧状态的快照。我们可以理解为,浏览器帮我们截了一张图。

3. 暂停渲染

此时,浏览器会暂停页面渲染,updateCallback 引发的页面变化,要等到动效执行时才会显现出来。

4. 执行 updateCallback

到这里,我们的回调函数会执行,更新 DOM 状态。当然从用户的视角,是看不见变化的。

5. updateCallbackDone

回调执行完毕,实例对象的 updateCallbackDone 状态达成。

6. 抓拍新状态快照

浏览器抓拍新 DOM 状态的快照,同样地,也可以理解为截一张图,但这张图是直接对照新状态绘制的,并不是页面显现的内容。

7. 构建伪元素树

利用刚刚生成的两张快照,浏览器创建一系列伪元素,插入到 html 节点中。旧、新快照,对应着上文提到的两个伪元素:::view-transition-old::view-transition-new

8. 恢复渲染

伪元素应有尽有,就可以恢复页面渲染、将伪元素显示在页面上了。

9. ready

万事俱备,Transition 的 ready 状态达成。

10. 播放动效

浏览器安排新旧快照两个“演员”登台,导演一场从旧状态到新状态的过渡效果。

11. 移除伪元素

动效演出落幕,“演员”退场,浏览器把伪元素移除。

12、 finished 状态达成

W3C 标准文档中,有一个步进式 Demo,可以帮我们更直观地了解生命周期的每个阶段。

life.gif

四、最佳实践

由于 View Transition API 还是新鲜出炉的功能,所以在使用过程中,还是有一些需要注意之处。

1. 平稳降级

当前只有 Chrome/Edge 111+ 支持 View Transition API,所以我们需要兼顾浏览器差异。

if (!document.startViewTransition) {
    updateCallback();
    return;
}
document.startViewTransition(updateCallback);

2. 精简 updateCallback

在过渡过程中,由于页面不可交互,所以这一阶段的时长要可控。

// Bad ❌

document.startViewTransition(async () => {
    await fetchLargeFile();
    await changeDOM();
    await doSomething();
});

在上面的代码示例中,updateCallback 要等到大文件加载完才能执行。结合上文提及的生命周期阶段可知,此时页面停止渲染,用户将会看到一个卡死的页面。

因此,我们要将有可能阻塞过渡过程的逻辑,移到 updateCallback 之外。

3. 防止重名

在页面中,如果有多个元素的 view-transition-name 的值相同,浏览器会自动跳过这些元素的过渡动效,因此要保证同一时刻下,DOM 中每个 view-transition-name 仅对应唯一的元素

4. 防止状态中断

view-transition-name 重名导致动效被跳过,因此 Transition 的 ready 状态也就永远无法达到。

这种情况下,我们预期在 ready 状态后执行的一些逻辑,就不能得以执行,比如:

.box {

    view-transition-name: box;
}

.spot {
    view-transition-name: box;
}
// Bad ❌

const vt = document.startViewTransition(updateCallback);

await vt.ready;

doSomething(); // Never run.

为了避免逻辑异常,我们需要用 try...catch 等手段捕获异常,保持逻辑通顺。

// Good ✅
const vt = document.startViewTransition(updateCallback);

try {
    await vt.ready;
}

doSomething();

5. 仅限 SPA

在单页应用(SPA)中,我们可以用 View Transition API 去处理路由切换的过渡,但在跨页面应用(MPA)的路由切换中,由于新状态承载于新页面,也就是新的 document 对象,所以过渡效果无法跨过页面之间的阻隔。

不过,在 View Transition API 的最初设想中,作者们是有计划支持 MPA 的:

image.png

所以,让我们拭目以待吧。

总结

在了解和使用 View Transition API 的过程中,我能切实感受到,它在帮助提升开发者的开发体验,也能在更复杂的场景中提升用户体验,实在是一把简单却足够强大的利器。

但其 W3C 标准还处于草稿阶段,功能完善度、浏览器支持面还需要长足发展。

我想,View Transition API 的设计初衷,就是对体验提升的不懈追求。正是这种精神,让 Web 开发不断萌新芽、开新花,这片土地,总是生机勃勃,总是日新月异。


如果你想调试更多 Demo,请访问 github.com/Baddyo/view…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYjMbQJX' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片