main
vue 123 lines 2.55 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
78 :deep() {
79 .n-icon {
80 position: absolute;
81 top: 0;
82 left: 0;
83
84 & > svg {
85 position: absolute;
86 top: 0;
87 left: 0;
88 transition: opacity 0.35s;
89
90 &.hover {
91 opacity: 0;
92 }
93 &:not(.hover) {
94 opacity: 1;
95 }
96 }
97
98 &:hover {
99 & > svg {
100 &.hover {
101 opacity: 1;
102 }
103 &:not(.hover) {
104 opacity: 0;
105 }
106 }
107 }
108 }
109 }
110 }
111 .rotate-enter-active,
112 .rotate-leave-active {
113 transition: all 0.5s ease-out;
114 }
115 .rotate-enter-from {
116 opacity: 0;
117 transform: rotate(45deg);
118 }
119 .rotate-leave-to {
120 opacity: 0;
121 transform: rotate(-45deg);
122 }
123 </style>