跳至主要內容

使用 startViewTransition 实现 Element Plus 主题切换效果

望间代码JavaScript大约 3 分钟

使用 startViewTransition 实现 Element Plus 主题切换效果

效果分析

  • 点击主题按钮,切换到白天(light)模式,主题效果从点击位置由一个小圆点逐渐放大,直到覆盖整个页面。
  • 点击主题按钮,切换到黑夜(dark)模式,主题效果从整个页面以点击位置为圆心,由一个大圆逐渐收缩成小圆点,直至消失。

实现原理

比较容易想到的方式,就是将黑夜模式页面和白天模式页面完全叠在一起,在切换主题的时候,对原主题页面进行裁切,直到原主题页面完全消失,新主题页面完全显示。

实现步骤

既然是圆形裁切,那么就需要确定圆心坐标和半径。

主题切换由按钮的点击事件触发,那么圆心坐标就是鼠标点击的坐标

<button onclick="changeTheme">change</button>
// 获取按钮
let button = document.querySelector("button");

/**
 * 点击按钮切换主题事件
 */
function changeTheme(event) {
  const { clientX: x, clientY: y } = event;
}

知道了圆心的坐标,那么圆形的半径就可以求出,下面是圆形求半径的公式。

r=x2+y2 r = \sqrt{x^2 + y^2}

因为这个圆形需要覆盖整个页面,为了保险,所以这里取了页面的最大值。

const r = Math.hypot(Math.max(x, innerWidth), Math.max(y, innerHeight));

裁切可以使用 cilp-path 属性实现

clip-path: circle(r at x y) ,其中 r 表示圆的半径;x 表示圆心 x 轴坐标;y 表示圆心 y 轴坐标。

详细用法请参考 MDN-clip-pathopen in new window

  • 当由 light 主题切换成 dark 主题时,clip-path: circle(r at x y) => circle(0 at x y)

  • 当由 dark 主题切换成 light 主题时,clip-path: circle(0 at x y) => circle(r at x y)

既然有了裁切的方式,那么裁切目标呢?上面我们分析的是裁切主题页面,那么怎么获取到这两个主题页面呢?

这里我们需要引入两个伪元素 ::view-transition-newopen in new window::view-transition-oldopen in new window,分别代表新旧主题视图,这里我们可以理解成我们需要进行裁切的页面。

如果需要详细了解它们原理,请参考 W3C 标准文档open in new window

  • 当由 light 主题切换成 dark 主题时,::view-transition-old 被裁切,::view-transition-new 不会被裁切。

    表示旧主题视图由半径为 r 的圆形逐渐变化成半径为 0 的圆形。

  • 当由 dark 主题切换成 light 主题时,::view-transition-new 被裁切,::view-transition-old 不会被裁切。

    表示新主题视图由半径为 0 的圆形逐渐变化成半径为 r 的圆形。


解决了裁切的功能点,接下来就是如何实现动画效果了。

View Transitions APIopen in new window 可以轻松地创建不同 DOM 状态之间的动画过渡,刚好派上用场。

使用 startViewTransition 很简单

const transition = document.startViewTransition(callback);

startViewTransition 有个实例属性 ready,过渡动画即将开始时触发并放回 Promise 实例。

我们可以在这里面将圆形裁切的动画效果实现。

最终代码

HTML
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <button onclick="toggleTheme(event)">change</button>
  </body>
</html>
上次编辑于:
贡献者: ViewRoom