你没有用过的船新版本——跨页面共享元素动画

  • 本文主要参考 GitHub shared-element-transitions
  • 无法访问 GitHub 可以使用 Gitee 镜像
  • 共享元素动画使用要求,版本 101 后,开启 chrome://flags/#document-transition
  • 过渡接口不一定稳定,可能有变

效果示例

视频来源于 GitHub,存在无法播放的情况

原理浅析

跨页面共享元素动画实现的方法其实并非真的操作 DOM 移动,而是先把需要移动的页面或者元素存为图片,然后再通过对图片添加动画实现跨页面“共享元素”动画。未来还会更新加强版:computed style + 图片,可以更精细地控制 CSS 样式而不是只操作一张图片。也正因为动的是截图,整个画面在过渡时不会响应任何点击事件,一定程度上减少了极限操作造成的 bug。

值得注意的是,基于跨页面过渡的原理,我们可以推测,在动画执行的时候,目标页面其实已经渲染好了(才会有目标图片),所以使用跨页面过渡动画有一个缺点,那就是有一定延迟。

<top-layer>
  <container(root)>
    <image-wrapper(root)>
      <outgoing-image(root) />
      <incoming-image(root) />
    </image-wrapper(root)>
  </container(root)>
</top-layer>

可以想象过渡过程中,在画面顶层会有这么一组元素覆盖着原本的元素,所以你无法点击原来画面上的元素。默认情况下 outgoing-imageincoming-image 的过渡是一个淡入淡出动画,我们可以通过下面这些 CSS 选择器控制这两个图片的动画:

container(root) - ::page-transition-container(root)
image-wrapper(root) - ::page-transition-image-wrapper(root)
outgoing-image(root) - ::page-transition-outgoing-image(root)
incoming-image(root) - ::page-transition-incoming-image(root)

例如把动画延长到 5 秒:

::page-transition-outgoing-image(root),
::page-transition-incoming-image(root) {
  animation-duration: 5s;
}

再例如把淡入淡出改为其他动画:

@keyframes slide-to-left {
  to {
    transform: translateX(-100%);
  }
}

@keyframes slide-from-right {
  from {
    transform: translateX(100%);
  }
}

::page-transition-outgoing-image(root) {
  animation: 500ms ease-out both slide-to-left;
}

::page-transition-incoming-image(root) {
  animation: 500ms ease-out both slide-from-right;
}

多元素参与过渡

上面的代码仅仅是对整个页面(root)添加过渡动画,然而更常见的需求肯定是对某个元素的过渡。为此我们需要把某个元素指定为过渡使用元素:

.site-header {
  page-transition-tag: side-header;
  /* Paint containment is required */
  contain: paint;
}

P.S. break-inside: avoid;contain: paint; 是实现跨页过渡的重要属性

设置后,过渡用的“图片”结构会像这样:

<top-layer>
  <container(root)>
    <image-wrapper(root)>
      <outgoing-image(root) />
      <incoming-image(root) />
    </image-wrapper(root)>
  </container(root)>

  <container(site-header)>
    <image-wrapper(site-header)>
      <outgoing-image(site-header) />
      <incoming-image(site-header) />
    </image-wrapper(site-header)>
  </container(site-header)>
</top-layer>

对过渡动画的操作与 root 别无二致,仅仅是把 root 换成对应的 tag 就可以了:::page-transition-outgoing-image(site-header)

调用过渡 API

仅配置 CSS 无法实现过渡,还需要调用 document.createDocumentTransition 告诉浏览器执行过渡操作:

async function spaNavigate(data) {
  // Fallback
  if (!document.createDocumentTransition) {
    await updateTheDOMSomehow(data)
    return
  }

  // With a transition
  const transition = document.createDocumentTransition()
  await transition.start(async () => {
    // Once this callback has called, the browser has captured the page similar to a screenshot.
    // This screenshot is now being displayed rather than the real DOM.
    // Any animated content on the page (e.g. CSS animations, videos, GIFs) will now appear frozen.
    await updateTheDOMSomehow()
    // The DOM has now updated, but the user is still looking at the captured state.
    // Once this async function returns, the transition will begin.
  })
  // The transition is now complete, and the captured state is removed to reveal
  // the real DOM underneath.
}

这个过程步骤如下:

  • start 的 callback 运行时,浏览器已经获取当前截图,并且整个页面被截图覆盖,所以从用户角度看,整个画面都是静止无响应的
  • 在 callback 中对 DOM 进行操作,因为当前画面仍是截图,所以即使 DOM 操作完成,用户也依然看到旧画面
  • callback 运行结束后,拿到新的页面截图,过渡才正式开始
  • await start 后,过渡动画完成,移除过渡用的截图,新页面可操作

还可以通过修改 el.style.pageTransitionTag 临时调整过渡方式:

async function animate(direction) {
  // Fallback
  if (!document.createDocumentTransition) {
    await mutate()
    return
  }

  // With a transition
  const transition = document.createDocumentTransition()
  document.querySelector('#title').style.pageTransitionTag =
    'site-title-' + direction
  await transition.start(() => mutate())
  document.querySelector('#title').style.pageTransitionTag = ''
  console.log('Transition complete!')
}

多页面过渡

上述操作仅限单页面应用,虽然这个 API 的目标是使多页面应用也能进行共享元素过渡,但是浏览器暂时未支持,只能通过代码来预习一下,基本概念不变,跨页面的重点是在页面隐藏和新页面展示前对元素进行操作:

document.addEventListener("pagehide", (event) => {
  if (!event.transition) return;
  document.getElementById("foo").style.pageTransitionTag = "foo";
  event.transition.setData({});
});
document.addEventListener('beforepageshow', async (event) => {
  if (!event.transition) return
  document.querySelector('.header').style.pageTransitionTag = 'header'

  await event.transition.ready

  // The pseudo-elements are now accessible and can be animated:
  document.documentElement.animate(keyframes, {
    ...animationOptions,
    pseudoElement: '::page-transition-container(header)',
  })
})

来试试

初步了解跨页面过渡接口和 CSS 配置后,应该可以理解这个简单的例子啦!大家赶紧打开 chrome://flags/#document-transition ,试试通过修改这个例子更深入理解共享元素动画吧!

https://codepen.io/ssshooter/pen/GRxjGXJ


暂时没有留言,要抢沙发吗?
留言
© 2018- SSShooter • theme usubeni • powered by Gatsbyjs
一转眼 已是