实现效果

实现过程
前提需要引入好 element-plus,并导入element的黑色主题CSS
示例,再 main.js 中引入
| 12
 3
 4
 5
 6
 7
 8
 
 | import ElementPlus from 'element-plus'import 'element-plus/dist/index.css'
 import 'element-plus/theme-chalk/dark/css-vars.css'
 
 app.use(ElementPlus, {
 locale: locale,
 size: Cookies.get('size') || 'default'
 })
 
 | 
接着编写下面的基础代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | <template><div>
 <el-switch v-model='value1' class='switch' @change='changeSwitch'>
 <template #active-action>
 <el-icon>
 <Moon />
 </el-icon>
 </template>
 <template #inactive-action>
 <el-icon>
 <Sunny />
 </el-icon>
 </template>
 </el-switch>
 </div>
 </template>
 
 <script setup>
 let value1 = ref(false)
 
 function changeSwitch() {
 document.documentElement.classList.toggle('dark')
 }
 </script>
 <style scoped lang='scss'>
 .switch {
 position: absolute;
 left: 200px;
 top: 200px;
 }
 </style>
 
 | 
现在当切换 switch 时,背景颜色会直接进行黑白切换

从动画可以看到,现在是没有任何过渡效果的
我们可以使用 document.startViewTransition 很简单的就可以加上过渡效果,document.startViewTransition 接收一个函数参数,再函数内部写上操作DOM的逻辑代码即可实现一个默认的过度效果
| 12
 3
 4
 5
 
 | function changeSwitch() {document.startViewTransition(() => {
 document.documentElement.classList.toggle('dark')
 })
 }
 
 | 
现在就有了一个默认的过渡效果

为什么使用了 document.startViewTransition 就会有一个动画效果呢
MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/startViewTransition
 
通过调用 API,让浏览器为新旧两种不同视图分别捕获并建立了快照 (即 ::view-transition-old(root)旧快照 和 ::view-transition-new(root) 新快照),而后新旧两快照在 ::view-transition-image-pair(root) 容器中完成转场动画的过渡。动画结束后则删除其相关伪元素 (快照和容器)。
 
我们也可以控制默认的动画时长
| 12
 3
 4
 
 | ::view-transition-old(root), /* 旧视图*/::view-transition-new(root) { /* 新视图*/
 animation-duration: 2s;
 }
 
 | 
了解了这些,现在想点击按钮,实现一个扩散圆来实现主题切换效果,只需要根据按钮位置,计算圆的半径,然后设置 ::view-transition-new(root) 这个伪元素的动画效果,让圆从 0% 到 100%即可
完整代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 
 | <template><div>
 <el-switch v-model='isDark' class='switch' ref='switchRef' @change='changeSwitch'>
 <template #active-action>
 <el-icon>
 <Moon />
 </el-icon>
 </template>
 <template #inactive-action>
 <el-icon>
 <Sunny />
 </el-icon>
 </template>
 </el-switch>
 </div>
 </template>
 
 <script setup>
 import { ref } from 'vue'
 
 let isDark = ref(false)
 let switchRef = ref(null)
 
 
 function changeSwitch() {
 
 const transition = document.startViewTransition(() => {
 
 document.documentElement.classList.toggle('dark')
 })
 
 
 transition.ready.then(() => {
 
 const switchElement = switchRef.value?.$el
 
 const rect = switchElement.getBoundingClientRect()
 const x = rect.left + rect.width / 2
 const y = rect.top + rect.height / 2
 
 const endRadius = Math.hypot(
 Math.max(x, innerWidth - x),
 Math.max(y, innerHeight - y),
 )
 
 const clipPath = [
 `circle(0 at ${x}px ${y}px)`,
 `circle(${endRadius}px at ${x}px ${y}px)`,
 ]
 
 document.documentElement.animate(
 {
 clipPath: isDark.value ? clipPath.reverse() : clipPath,
 },
 {
 duration: 400,
 pseudoElement: isDark.value
 ? '::view-transition-old(root)'
 : '::view-transition-new(root)',
 },
 )
 })
 }
 </script>
 
 <style>
 ::view-transition-new(root),
 ::view-transition-old(root) {
 
 animation: none !important;
 }
 
 
 .dark::view-transition-old(root) {
 z-index: 100;
 }
 </style>
 
 <style scoped>
 .switch {
 position: absolute;
 left: 50%;
 top: 50%;
 transform: translate(-50%, -50%);
 }
 </style>
 
 | 
circle 表示画一个圆形,更多图像可参考
https://developer.mozilla.org/zh-CN/docs/Web/CSS/clip-path#%E5%BD%A2%E5%BC%8F%E8%AF%AD%E6%B3%95
关于更多的 document.documentElement.animate 知识可参考
https://developer.mozilla.org/zh-CN/docs/Web/API/Element/animate