跳转到内容

主题过渡动画

刀刀

7/8/2025

0 字

0 分钟

Element UI 切换主题时有一个很炫酷的过渡动画,这主要是通过 view transition 实现的。

view transition

view transition 是一个 CSS 规范,用于在页面切换时添加过渡动画。它包含两个部分:

  1. view transition 规则:用于定义过渡动画的属性,如 transition-propertytransition-duration 等。
  2. view transition 事件:用于监听过渡动画的开始和结束,从而执行相应的操作。

它的工作原理如下:

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

  2. 接着调用回调函数,在回调函数中修改 DOM 结构

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

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

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

代码实现

基础实现

先写一个能够切换主题色的代码:

html
<style>
  :root {
    --background-color: #fff;
    background-color: var(--background-color);
  }

  :root.dark {
    --background-color: #000;
  }
</style>

<body>
  <button id="btn">切换主题</button>

  <script>
    const btn = document.getElementById('btn')
    btn.addEventListener('click', () => {
      document.documentElement.classList.toggle('dark')
    })
  </script>
</body>

过渡实现

接下来,我们使用 view transition 实现过渡动画,步骤为:

  1. document.startViewTransition() 方法开始过渡动画,接收一个函数作为参数,该函数用于修改 DOM 结构(即上方代码修改 className 部分。得到一个参数,命名为 transition
  2. transition.ready 动画捕获时,添加过渡动画的样式。由于这是一个异步方法,因此需要在 .then 回调中执行
  3. 调用 document.documentElement.animate 方法设置动画,参数一是一个对象,属性名是需要实现的动画的,值是一个数组,第一项是起始样式,第二项是结束样式;参数二是一个对象,包含 duration 过渡时间、pseudoElement 过渡元素等属性

而动画过渡本案例采用的是 clip-path 属性,它用于裁剪元素,使元素的部分内容不可见。这里我们设置一个圆形,从 0% 到 100% 的变化,从而实现一个圆形的放大效果。

html
<style>
  :root {
    --background-color: #fff;
    background-color: var(--background-color);
  }

  :root.dark {
    --background-color: #000;
  }

  ::view-transition-new(root), 
  ::view-transition-old(root) { 
    animation: none !important; 
  } 
</style>

<body>
  <button id="btn">切换主题</button>

  <script>
    const btn = document.getElementById('btn')
    btn.addEventListener('click', () => {
      const transition = document.startViewTransition(() => { 
        document.documentElement.classList.toggle('dark')
      }) 
      transition.ready.then(() => { 
        document.documentElement.animate({ 
          clipPath: [`circle(0% at 50% 50%)`, `circle(100% at 50% 50%)`], 
        }, { 
          duration: 500, 
          pseudoElement: '::view-transition-new'
        }) 
      }) 
    })
  </script>
</body>

现在运行代码,发现实现了从中心圆形扩散的过渡动画了。后面去调整一下细节。

注意

如果没有动画效果,可能是原来的默认动画样式覆盖了设置的动画样式,需要在 ::view-transition-new::view-transition-old 中添加 animation: none !important;

优化细节

还有几点细节需要调整一下:

  1. 修改中心点为鼠标点击的位置
  2. clip-pathcircle 半径要计算修改,不然如果中心点在四个角时,会因为半径不够,导致后续没有动画
js
const btn = document.getElementById('btn')
btn.addEventListener('click', (e) => { 
  const transition = document.startViewTransition(() => {
    document.documentElement.classList.toggle('dark')
  })
  const x = e.clientX 
  const y = e.clientY 
  const targetRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y)) 
  transition.ready.then(() => {
    document.documentElement.animate({
      clipPath: [`circle(0% at ${x}px ${y}px)`, `circle(${targetRadius}px at ${x}px ${y}px)`], 
    }, {
      duration: 500,
      pseudoElement: '::view-transition-new'
    })
  })
})