feat(components): add theme switching animation

This commit is contained in:
wang 2024-07-27 22:46:14 +08:00
parent a9dce21878
commit 4a2dbbf1d9
4 changed files with 58 additions and 25 deletions

View File

@ -1,13 +1,12 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, nextTick } from 'vue';
import type { PopoverPlacement } from 'naive-ui';
import { $t } from '@/locales';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({ name: 'ThemeSchemaSwitch' });
interface Props {
/** Theme schema */
themeSchema: UnionKey.ThemeScheme;
/** Show tooltip */
showTooltip?: boolean;
/** Tooltip placement */
@ -19,15 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
tooltipPlacement: 'bottom'
});
interface Emits {
(e: 'switch'): void;
}
const emit = defineEmits<Emits>();
function handleSwitch() {
emit('switch');
}
const themeStore = useThemeStore();
const icons: Record<UnionKey.ThemeScheme, string> = {
light: 'material-symbols:sunny',
@ -35,13 +26,46 @@ const icons: Record<UnionKey.ThemeScheme, string> = {
auto: 'material-symbols:hdr-auto'
};
const icon = computed(() => icons[props.themeSchema]);
const icon = computed(() => icons[themeStore.themeScheme]);
const tooltipContent = computed(() => {
if (!props.showTooltip) return '';
return $t('icon.themeSchema');
});
function toggleDark(event: MouseEvent) {
const isAppearanceTransition =
document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!isAppearanceTransition) {
themeStore.toggleThemeScheme();
return;
}
const x = event.clientX;
const y = event.clientY;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
// @ts-expect-error: Transition API
const transition = document.startViewTransition(async () => {
themeStore.toggleThemeScheme();
await nextTick();
});
transition.ready.then(() => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: themeStore.darkMode ? [...clipPath].reverse() : clipPath
},
{
duration: 400,
easing: 'ease-out',
pseudoElement: themeStore.darkMode ? '::view-transition-old(root)' : '::view-transition-new(root)'
}
);
});
}
</script>
<template>
@ -49,7 +73,7 @@ const tooltipContent = computed(() => {
:icon="icon"
:tooltip-content="tooltipContent"
:tooltip-placement="tooltipPlacement"
@click="handleSwitch"
@click="toggleDark"
/>
</template>

View File

@ -41,11 +41,7 @@ const { isFullscreen, toggle } = useFullscreen();
<GlobalSearch />
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
<ThemeSchemaSwitch
:theme-schema="themeStore.themeScheme"
:is-dark="themeStore.darkMode"
@switch="themeStore.toggleThemeScheme"
/>
<ThemeSchemaSwitch />
<ThemeButton />
<UserAvatar />
</div>

View File

@ -15,3 +15,21 @@ html {
html.grayscale {
filter: grayscale(100%);
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-old(root) {
z-index: 1;
}
::view-transition-new(root) {
z-index: 9999;
}
.dark::view-transition-old(root) {
z-index: 9999;
}
.dark::view-transition-new(root) {
z-index: 1;
}

View File

@ -59,12 +59,7 @@ const bgColor = computed(() => {
<SystemLogo class="text-64px text-primary lt-sm:text-48px" />
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
<div class="i-flex-col">
<ThemeSchemaSwitch
:theme-schema="themeStore.themeScheme"
:show-tooltip="false"
class="text-20px lt-sm:text-18px"
@switch="themeStore.toggleThemeScheme"
/>
<ThemeSchemaSwitch :show-tooltip="false" class="text-20px lt-sm:text-18px" />
<LangSwitch
:lang="appStore.locale"
:lang-options="appStore.localeOptions"