实现一个丝滑的暗黑模式切换动画

体验:https://ganto.me

参考内容:
参考文章:https://juejin.cn/post/7269388083342082107
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/View_Transitions_API https://developer.mozilla.org/zh-CN/docs/Web/CSS/::view-transition-new

实现方式就是利用vueuse将html元素动态添加class类名dark

利用伪元素::view-transition-old以及::view-transition-new,如需了解这两个伪元素,请移步文章开头参考内容

可以这样理解,调用document.startViewTransition(() => { /* 修改html元素类名 */ })方法时,在该方法的回调函数中,添加修改页面变化的代码;
这样伪元素::view-transition-old记录的就是修改之前的页面'截图',而伪元素::view-transition-new是修改页面变化后的页面,然后是这两个伪元素进行的动画过渡

如果你想快速在项目中添加该动画,请添加以下代码
*css

html {
  --g-tabbar-border: #c9c9c9;
  --g-tabbar-item-after: #595959;
  --g-about-link-border: #c9c9c9;
  --g-about-link-hover: #374151;
  --g-blog-bg-color: #d4d4d4;
}
html.dark {
  --g-tabbar-border: #595959;
  --g-tabbar-item-after: #fff;
  --g-about-link-border: #374151;
  --g-about-link-hover: #fff;
  --g-blog-bg-color: #8a8a8a;
}

::view-transition-old(*) {
  animation: none;
}

::view-transition-new(*) {
  animation: clip .5s ease-in;
}

::view-transition-old(root) {
  z-index: 1;
}

::view-transition-new(root) {
  z-index: 9999;
}

html.dark::view-transition-old(*) {
  animation: clip .5s ease-in reverse;
}

html.dark::view-transition-new(*) {
  animation: none;
}

html.dark::view-transition-old(root) {
  z-index: 9999;
}

html.dark::view-transition-new(root) {
  z-index: 1;
}

@keyframes clip {
  from {
    clip-path: circle(0% at var(--x) var(--y));
  }
  to{
    clip-path: circle(var(--r) at var(--x) var(--y));
  }
}

clip-path css样式中的 circle 函数,第一个参数就是圆的半径,第二、三个参数是圆心坐标

*js

const changeBtn = (func, $eve) => {
  const x = $eve.clientX
  const y = $eve.clientY
  // 计算鼠标点击位置距离视窗的最大圆半径
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y),
  )
  document.documentElement.style.setProperty('--x', x + 'px')
  document.documentElement.style.setProperty('--y', y + 'px')
  document.documentElement.style.setProperty('--r', endRadius + 'px')
  // 判断浏览器是否支持document.startViewTransition
  if (document.startViewTransition) {
    // 如果支持就使用document.startViewTransition方法
    document.startViewTransition(() => {
      func.call() // 这里的函数是切换主题的函数,调用changeBtn函数时进行传入
    })
  } else {
    // 如果不支持,就使用最原始的方式,切换主题
    func.call()
  }
}

动画会用到鼠标点击按钮时的点击坐标

如果你想快速测试,在添加css代码后,直接在浏览器控制台Console中运行以下代码测试

*测试

document.querySelector('html').setAttribute('style', '--r: 100px; --x: 100px; --y: 100px;')
document.startViewTransition(() => {
    document.querySelector('html').classList.toggle('dark')
})

完整案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      html {
        --bg-color: #000;
        --text-color: #fff;
      }
      html.dark {
        --bg-color: #fff;
        --text-color: #000;
      }

      ::view-transition-old(*) {
        animation: none;
      }

      ::view-transition-new(*) {
        animation: clip 0.5s ease-in;
      }

      ::view-transition-old(root) {
        z-index: 1;
      }

      ::view-transition-new(root) {
        z-index: 9999;
      }

      html.dark::view-transition-old(*) {
        animation: clip 0.5s ease-in reverse;
      }

      html.dark::view-transition-new(*) {
        animation: none;
      }

      html.dark::view-transition-old(root) {
        z-index: 9999;
      }

      html.dark::view-transition-new(root) {
        z-index: 1;
      }

      @keyframes clip {
        from {
          clip-path: circle(0% at var(--x) var(--y));
        }
        to {
          clip-path: circle(var(--r) at var(--x) var(--y));
        }
      }

      /* 样式写法例子,使用css变量,可以实现动态切换主题 */
      body {
        background-color: var(--bg-color);
        color: var(--text-color);
      }
    </style>
  </head>
  <body>
    <h1>
      test text
    </h1>
    <button id="btn">changeTheme</button>

    <script>
      // 主题切换函数
      const changeTheme = () => {
        document.querySelector('html').classList.toggle('dark')
      }
      const changeBtn = (func, $eve) => {
        const x = $eve.clientX
        const y = $eve.clientY
        // 计算鼠标点击位置距离视窗的最大圆半径
        const endRadius = Math.hypot(
          Math.max(x, innerWidth - x),
          Math.max(y, innerHeight - y),
        )
        document.documentElement.style.setProperty('--x', x + 'px')
        document.documentElement.style.setProperty('--y', y + 'px')
        document.documentElement.style.setProperty('--r', endRadius + 'px')
        // 判断浏览器是否支持document.startViewTransition
        if (document.startViewTransition) {
          // 如果支持就使用document.startViewTransition方法
          document.startViewTransition(() => {
            func.call() // 这里的函数是切换主题的函数,调用changeBtn函数时进行传入
          })
        } else {
          // 如果不支持,就使用最原始的方式,切换主题
          func.call()
        }
      }
      document.getElementById('btn').addEventListener('click', (e) => {
        changeBtn(changeTheme, e)
      })
    </script>
  </body>
</html>
posted @ 2024-01-02 21:40  干徒  阅读(586)  评论(1编辑  收藏  举报