跳转到内容

主题过渡动画

刀刀
0字
0分钟
2026/8/16

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

CSS 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)

CSS 代码实现

CSS 基础实现

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

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>

CSS 过渡实现

使用 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;

CSS 优化细节

还有几点细节需调整:

  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',
      },
    )
  })
})

贡献者

The avatar of contributor named as duyidao duyidao
The avatar of contributor named as 刀刀 刀刀
The avatar of contributor named as v_duyilin v_duyilin

页面历史

刀刀博客累计访客 人;文档累计访问量共