这个组件包含一个按钮,用户可以通过点击它来切换应用程序的主题。通过使用 CSS 动画,我们能够在主题切换的时候,提供视觉反馈,提升用户体验。
<template>
<div class="header-theme" @click="onHandler">
<IconSunFill v-show="AppTheme === 'dark'" />
<IconMoonFill v-show="AppTheme !== 'dark'" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { IconSunFill, IconMoonFill } from '@arco-design/web-vue/es/icon'
import { AppTheme } from '@/layout/stores/settings'
// interface PropsType {}
// const props = defineProps<PropsType>()
// const isDark = ref<boolean>(false)
const onHandler = async (event: MouseEvent) => {
const x = event.clientX
const y = event.clientY
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
)
AppTheme.value = AppTheme.value === 'dark' ? 'light' : 'dark'
const isDark = AppTheme.value === 'dark'
const transition = document.startViewTransition(() => {
const root = document.documentElement
root.classList.remove(isDark ? 'dark' : 'light')
root.classList.add(isDark ? 'light' : 'dark')
document.body.setAttribute('arco-theme', isDark ? 'dark' : 'light')
})
transition.ready.then(() => {
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
]
document.documentElement.animate(
{
clipPath: isDark ? [...clipPath].reverse() : [...clipPath],
},
{
duration: 500,
easing: 'ease-in',
pseudoElement: isDark
? '::view-transition-old(root)'
: '::view-transition-new(root)',
}
)
})
}
onMounted(() => {
const isDark = AppTheme.value === 'dark'
document.body.setAttribute('arco-theme', isDark ? 'dark' : 'light')
})
</script>
<style lang="scss" scoped>
.header-theme {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
overflow: hidden;
font-size: 16px;
color: var(--color-text-1);
cursor: pointer;
background-color: var(--color-fill-2);
border-radius: 50%;
&:hover {
background-color: var(--color-fill-3);
}
}
</style>
<style>
.dark::view-transition-old(root) {
z-index: 1;
}
.dark::view-transition-new(root) {
z-index: 999;
}
::view-transition-old(root) {
z-index: 999;
mix-blend-mode: normal;
animation: none;
}
::view-transition-new(root) {
z-index: 1;
mix-blend-mode: normal;
animation: none;
}
</style>