main
vue 124 lines 2.57 KB
Raw
1 <template>
2 <button class="theme-switch" alt="theme-switch" aria-label="theme-switch" @click="toggleTheme">
3 <Transition name="rotate">
4 <Icon v-if="isThemeDark" :size="20">
5 <Iconify :icon="Sunny" class="hover" />
6 <Iconify :icon="SunnyOutline" />
7 </Icon>
8 <Icon v-else :size="20">
9 <Iconify :icon="Moon" class="hover" />
10 <Iconify :icon="MoonOutline" />
11 </Icon>
12 </Transition>
13 </button>
14 </template>
15
16 <script lang="ts" setup>
17 import { Icon as Iconify } from "@iconify/vue"
18 import { computed, nextTick } from "vue"
19 import Icon from "@/components/common/Icon.vue"
20 import { useThemeStore } from "@/stores/theme"
21
22 const Sunny = "ion:sunny"
23 const Moon = "ion:moon"
24 const SunnyOutline = "ion:sunny-outline"
25 const MoonOutline = "ion:moon-outline"
26 const themeStore = useThemeStore()
27 const isThemeDark = computed<boolean>(() => themeStore.isThemeDark)
28
29 function toggleTheme(event?: MouseEvent) {
30 const isAppearanceTransition =
31 typeof document !== "undefined" &&
32 document.startViewTransition &&
33 !window.matchMedia("(prefers-reduced-motion: reduce)").matches
34
35 if (!isAppearanceTransition || !event) {
36 themeStore.toggleTheme()
37 return
38 }
39
40 if (document?.startViewTransition) {
41 const x = event.clientX ?? innerWidth / 2
42 const y = event.clientY ?? innerHeight / 2
43 const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))
44
45 const transition = document.startViewTransition(async () => {
46 themeStore.toggleTheme()
47 await nextTick()
48 })
49
50 transition.ready.then(() => {
51 const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`]
52 // const clipPath = [`inset(50%)`, `inset(0)`]
53
54 document.documentElement.animate(
55 {
56 clipPath
57 },
58 {
59 duration: 300,
60 easing: "ease-in",
61 pseudoElement: "::view-transition-new(root)"
62 }
63 )
64 })
65 }
66 }
67 </script>
68
69 <style scoped lang="scss">
70 .theme-switch {
71 position: relative;
72 width: 20px;
73 height: 20px;
74 overflow: hidden;
75 outline: none;
76 border: none;
77 cursor: pointer;
78
79 :deep() {
80 .n-icon {
81 position: absolute;
82 top: 0;
83 left: 0;
84
85 & > svg {
86 position: absolute;
87 top: 0;
88 left: 0;
89 transition: opacity 0.35s;
90
91 &.hover {
92 opacity: 0;
93 }
94 &:not(.hover) {
95 opacity: 1;
96 }
97 }
98
99 &:hover {
100 & > svg {
101 &.hover {
102 opacity: 1;
103 }
104 &:not(.hover) {
105 opacity: 0;
106 }
107 }
108 }
109 }
110 }
111 }
112 .rotate-enter-active,
113 .rotate-leave-active {
114 transition: all 0.5s ease-out;
115 }
116 .rotate-enter-from {
117 opacity: 0;
118 transform: rotate(45deg);
119 }
120 .rotate-leave-to {
121 opacity: 0;
122 transform: rotate(-45deg);
123 }
124 </style>