77 lines
1.6 KiB
Vue
77 lines
1.6 KiB
Vue
<script setup lang="ts">
|
|
const colorMode = useColorMode()
|
|
|
|
const nextTheme = computed(() => (colorMode.value === 'dark' ? 'light' : 'dark'))
|
|
|
|
const switchTheme = () => {
|
|
colorMode.preference = nextTheme.value
|
|
}
|
|
|
|
const startViewTransition = (event: MouseEvent) => {
|
|
if (!document.startViewTransition) {
|
|
switchTheme()
|
|
return
|
|
}
|
|
|
|
const x = event.clientX
|
|
const y = event.clientY
|
|
const endRadius = Math.hypot(
|
|
Math.max(x, window.innerWidth - x),
|
|
Math.max(y, window.innerHeight - y)
|
|
)
|
|
|
|
const transition = document.startViewTransition(() => {
|
|
switchTheme()
|
|
})
|
|
|
|
transition.ready.then(() => {
|
|
const duration = 600
|
|
document.documentElement.animate(
|
|
{
|
|
clipPath: [
|
|
`circle(0px at ${x}px ${y}px)`,
|
|
`circle(${endRadius}px at ${x}px ${y}px)`
|
|
]
|
|
},
|
|
{
|
|
duration: duration,
|
|
easing: 'cubic-bezier(.76,.32,.29,.99)',
|
|
pseudoElement: '::view-transition-new(root)'
|
|
}
|
|
)
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ClientOnly>
|
|
<UButton
|
|
:aria-label="`Switch to ${nextTheme} mode`"
|
|
:icon="`i-lucide-${nextTheme === 'dark' ? 'sun' : 'moon'}`"
|
|
color="neutral"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="rounded-full"
|
|
@click="startViewTransition"
|
|
/>
|
|
<template #fallback>
|
|
<div class="size-4" />
|
|
</template>
|
|
</ClientOnly>
|
|
</template>
|
|
|
|
<style>
|
|
::view-transition-old(root),
|
|
::view-transition-new(root) {
|
|
animation: none;
|
|
mix-blend-mode: normal;
|
|
}
|
|
|
|
::view-transition-new(root) {
|
|
z-index: 9999;
|
|
}
|
|
::view-transition-old(root) {
|
|
z-index: 1;
|
|
}
|
|
</style>
|