多页面与单页面视图过渡动画(View Transitions API)

1,070 阅读7分钟

多页面与单页面视图过渡动画(View Transitions API)

今天依旧给大家整个活,简单学习了一下,顺便来蹭个帖子。 由于新api兼容性不太好,所以当预习了。

多页面和单页面过渡demo

主题切换demo

原理

视图过渡过程

1.当调用 document.startViewTransition() 时,API 会截取当前页面的屏幕截图。

2.接下来,调用传递给 startViewTransition() 的回调函数,即 displayNewImage,这会导致 DOM 发生更改。 当回调函数成功运行时,ViewTransition.updateCallbackDone Promise 兑现,允许你响应 DOM 更新。

3.API 会捕获页面的新状态并实时展示

4.API 构造了一个具有以下结构的伪元素树:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

当过渡动画即将运行时,ViewTransition.ready Promise 兑现,你可以响应它进行一些操作,例如运行自定义的 JavaScript 动画,而不是默认的动画。

5.旧页面视图的 opacity 从 1 过渡到 0,而新视图从 0 过渡到 1,这就是默认的交叉淡入淡出效果。

6.当过渡动画结束时,ViewTransition.finished Promise 兑现,你可以响应它进行一些操作。

为不同的元素使用不同的过渡效果

目前,当 DOM 更新时发生变化的不同元素都使用相同的过渡动画。如果你想让不同的元素以不同于默认的“root”动画的方式进行过渡,你可以使用 view-transition-name 属性将它们分开。例如:

figcaption {
  view-transition-name: figure-caption;
}

view-transition-name 的值可以是除 none 之外的任何值——none 值表示该元素不参与视图过渡。

注:view-transition-name 必须是唯一的。如果两个渲染元素同时具有相同的 view-transition-nameViewTransition.ready 将拒绝并跳过过渡。

SPA同一文档视图转换

从 Chrome 111 开始,Chrome 支持同一份文档的视图转换。在使用之前先看一下浏览器兼容性把

image.png 嗯,不咋地。

默认

3.gif

以图片中内容为案例,默认视图转换是淡入淡出。

  //添加判断过渡动画并添加
  function viewTransition(fun) {
    if ('startViewTransition' in document) {
      let transition = document.startViewTransition(() => {
        fun()
      })
      // ready 只读属性是一个 Promise。会在伪元素树被创建且过渡动画即将开始时兑现。
      transition.ready.then(() => {})
    } else {
      fun()
    }
  }
//删除某个元素
function removeLi(e) {
    viewTransition(() => {
      document.querySelector(`.itembox`).removeChild(e.target)
    })
  }
//添加一个元素
  function addLi(val) {
    viewTransition(() => {
      document.querySelector(`.itembox`).appendChild(val)
    })
  }

自定义转场效果

2.gif

所有视图过渡伪元素都可以使用 CSS 来定位,并且由于动画是使用 CSS 定义的,因此您可以使用现有的 CSS 动画属性对其进行修改。例如:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 2s;
}

完成这项更改后,淡出现在变得很慢。

也可以通过js添加animate动画修改转场效果,例如这种

5.gif

document.querySelector('#buttonbg').onclick = function (e) {
        var root = document.documentElement
    
    //判断是否支持
        if (document.startViewTransition) {
    //给哪些效果设置过渡
          var transition = document.startViewTransition(() => {
            if (root.style.getPropertyValue('--bgColor') === '#000') {
              root.style.setProperty('--bgColor', '#fff')
              root.style.setProperty('--color', '#000')
            } else {
              root.style.setProperty('--bgColor', '#000')
              root.style.setProperty('--color', '#fff')
            }
          })
        //过渡开始时设置一个动画
          transition.ready.then(() => {
            document.documentElement.animate(
              {
                clipPath: [
                    //效果一
                  /* `polygon(50% 50%, 0% 100%, 50% 50%, 50% 0%, 50% 50%, 100% 100%)`,
                  `polygon(50% 100%, 0% 100%, 0% 0%, 50% 0%, 100% 0%, 100% 100%)` */
    
                    //效果二
                  `circle(0% at ${e.clientX}px ${e.clientY}px)`,
                  `circle(200% at ${e.clientX}px ${e.clientY}px)`
                ]
              },
              {
                duration: 500,
    //为新快照设置过渡效果
                pseudoElement: '::view-transition-new(root)'
              }
            )
          })
        } else {
          if (root.style.getPropertyValue('--bgColor') === '#000') {
            root.style.setProperty('--bgColor', '#fff')
            root.style.setProperty('--color', '#000')
          } else {
            root.style.setProperty('--bgColor', '#000')
            root.style.setProperty('--color', '#fff')
          }
        }
      }

6.gif

这里因为默认的一些样式导致,包括原有的淡出效果,还有混合模式

所以我们把默认效果置空

::view-transition-old(root) ,
::view-transition-new(root) {
 animation: none;
}

这样我们的主题切换效果就完成了

转换多个元素

单独为其添加动画效果。可通过为元素分配 view-transition-name 来实现。

//view-transition-name 的值可以是您所需的任何值(none 除外,这意味着没有转换名称)。它用于唯一标识整个过渡过程中的元素。
.main-header {
  view-transition-name: main-header;
}

注:view-transition-name 必须是唯一的。如果两个渲染元素同时具有相同的 view-transition-name,系统会跳过过渡。

为多个元素单独添加效果

1.gif 如果想案例中需要给多个li分别添加动画效果可以使用js单独为其添加view-transition-name,或者使用css变量

js

//新增item事件
var index = 3
    document.querySelector(`.addbotton`).onclick = function () {
      let childHTML = document.createElement('li')
      var r = Math.floor(Math.random() * 256)
      var g = Math.floor(Math.random() * 256)
      var b = Math.floor(Math.random() * 256)
      childHTML.style.backgroundColor = 'rgb(' + r + ',' + g + ',' + b + ')'
      childHTML.innerHTML = index
      //模拟唯一标识,
      childHTML.style.viewTransitionName = 'd' + index
      index++
      childHTML.onclick = removeLi
      addLi(childHTML)
    }

css

<div class="list" id="list">
  <div class="item" style="--i: a1">1</div>
  <div class="item" style="--i: a2">2</div>
  <div class="item" style="--i: a3">3</div>
  <div class="item" style="--i: a4">4</div>
  <div class="item" style="--i: a5">5</div>
  <div class="item" style="--i: a6">6</div>
  <div class="item" style="--i: a7">7</div>
  <div class="item" style="--i: a8">8</div>
  <div class="item" style="--i: a9">9</div>
  <div class="item" style="--i: a10">10</div>
</div>
​
.item{
  view-transition-name: var(--i);
}

MPA多页面应用的跨文档试图过渡

注:Chrome 126 开始,Chrome 支持跨文档视图转换。

跨文档视图转换依赖于与同一文档视图转换完全相同的构建块和原则,这是非常有意为之:

  1. 浏览器会截取新旧页面上具有唯一 view-transition-name 的元素快照。
  2. DOM 会在渲染被禁用时进行更新。
  3. 最后,过渡要借助 CSS 动画。

跨文档视图转换仅限于同源导航

跨文档视图转换仅限于同源导航。如果两个参与的网页的来源相同,则相应导航会被视为同源。

网页的来源是所用架构、主机名和端口的组合,详见 web.dev

注:在 Chrome 126 中,跨文档视图转换仅限于主框架导航。在未来的版本中,还将为在 iframe 中进行的导航启用跨文档视图转换。

老规矩学习之前先看一下兼容性。

@view-transition:

image.png gg了

pageswap与pagereveal兼容性相同

pageswap: image.png pageswap.view-transition:

image.png

因为是实验属性,我试了一下需要把上图中的实验api打开。@view-transition需要打开此api才能使用 image.png

可选择启用跨文档视图转换

@view-transition at-rule 中,将 navigation 描述符设置为 auto,以便为跨文档、同源导航启用视图转换

css

@view-transition {
  navigation: auto;
}

pageswappagereveal 事件

为了能够自定义跨文档视图转换,HTML 规范包含两个新事件:pageswappagereveal

  • pageswap 事件在网页最后一帧呈现之前触发。可以使用此功能在旧版快照获取之前,在传出页面上进行一些最后时刻的更改。

  • pagereveal 事件会在网页上完成初始化或重新启用后、首次呈现机会之前触发。利用它,可以在截取新快照之前自定义新页面。

4.gif

one页面

  /* // 旧页面逻辑 */
  window.addEventListener('pageswap', (e) => {
    /* 判断是否支持 */
    if (e.viewTransition) {
        /* //添加视图转换名称 */
      document.querySelector(`#one img`).style.viewTransitionName = 'avatar1'
/* 完成后删除对应名称 */
      e.viewTransition.finished.then(() => {
        document.querySelector(`#one img`).style.viewTransitionName = 'none'
      })
    }
  })
​
  /* // 新页面逻辑 */
  window.addEventListener('pagereveal', (e) => {
    /* 判断是否支持 */
    if (e.viewTransition) {
        /* //添加视图转换名称 */
      document.querySelector(`#one img`).style.viewTransitionName = 'avatar2'
/* 完成后删除对应名称 */
      e.viewTransition.ready.then(() => {
        document.querySelector(`#one img`).style.viewTransitionName = 'none'
      })
    }
  })

two页面

    /* // 旧页面逻辑 */
    window.addEventListener('pageswap', (e) => {
      /* 判断是否支持 */
      if (e.viewTransition) {
        /* //添加视图转换名称 */
        document.querySelector(`#two img`).style.viewTransitionName = 'avatar2'
        /* 完成后删除对应名称 */
        e.viewTransition.finished.then(() => {
          document.querySelector(`#two img`).style.viewTransitionName = 'none'
        })
      }
    })
​
    // 新页面逻辑
    window.addEventListener('pagereveal', (e) => {
      /* 判断是否支持 */
      if (e.viewTransition) {
        /* //添加视图转换名称 */
        document.querySelector(`#two img`).style.viewTransitionName = 'avatar1'
        /* 完成后删除对应名称 */
        e.viewTransition.ready.then(() => {
          document.querySelector(`#two img`).style.viewTransitionName = 'none'
        })
      }
    })

参考资料

1.适用于单页应用的同文档视图转换  |  View Transitions  |  Chrome for Developers

2.来实现一下 element-plus 中的主题切换动画 - 掘金 (juejin.cn)

3.太丝滑了!了解一下原生的视图转换动画 View Transitions API-CSDN博客

4.View Transitions API - Web API | MDN (mozilla.org)