parent
b9f7340c25
commit
b36ebadcb8
@ -711,10 +711,11 @@ interface SidebarItem {
|
||||
*/
|
||||
postList?: boolean
|
||||
/**
|
||||
* 是否启用 深色/浅色 模式切换过渡动画
|
||||
* @default true
|
||||
* 是否启用 深色/浅色 模式切换过渡动画,
|
||||
* 或配置过渡动画类型
|
||||
* @default 'fade'
|
||||
*/
|
||||
appearance?: boolean
|
||||
appearance?: boolean | 'fade' | 'circle-clip' | 'horizontal-clip' | 'vertical-clip' | 'skew-clip'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -1,13 +1,79 @@
|
||||
<script lang="ts" setup>
|
||||
import VPSwitch from '@theme/VPSwitch.vue'
|
||||
import { inject, ref, watchPostEffect } from 'vue'
|
||||
import { useData } from '../composables/index.js'
|
||||
import { computed, inject, nextTick, ref, watchPostEffect } from 'vue'
|
||||
import { enableTransitions, useData } from '../composables/index.js'
|
||||
|
||||
const checked = ref(false)
|
||||
const { theme, isDark } = useData()
|
||||
|
||||
const toggleAppearance = inject('toggle-appearance', () => {
|
||||
isDark.value = !isDark.value
|
||||
const transitionMode = computed(() => {
|
||||
const transition = theme.value.transition
|
||||
const options = typeof transition === 'object' ? transition : {}
|
||||
if (transition === false || options.appearance === false)
|
||||
return false
|
||||
|
||||
return typeof options.appearance === 'string' ? options.appearance : 'fade'
|
||||
})
|
||||
|
||||
const toggleAppearance = inject('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
|
||||
if (!enableTransitions() || transitionMode.value === false) {
|
||||
isDark.value = !isDark.value
|
||||
return
|
||||
}
|
||||
|
||||
await document.startViewTransition(async () => {
|
||||
isDark.value = !isDark.value
|
||||
await nextTick()
|
||||
}).ready
|
||||
|
||||
const keyframes: PropertyIndexedKeyframes = {}
|
||||
const mode = transitionMode.value
|
||||
let duration = 400
|
||||
|
||||
if (mode === 'circle-clip') {
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y),
|
||||
)}px at ${x}px ${y}px)`,
|
||||
]
|
||||
keyframes.clipPath = isDark.value ? clipPath.reverse() : clipPath
|
||||
}
|
||||
else if (mode === 'horizontal-clip') {
|
||||
const clipPath = [
|
||||
`inset(0px ${innerWidth}px 0px 0px)`,
|
||||
`inset(0px 0px 0px 0px)`,
|
||||
]
|
||||
keyframes.clipPath = isDark.value ? clipPath.reverse() : clipPath
|
||||
}
|
||||
else if (mode === 'vertical-clip') {
|
||||
const clipPath = [
|
||||
`inset(0px 0px ${innerHeight}px 0px)`,
|
||||
`inset(0px 0px 0px 0px)`,
|
||||
]
|
||||
keyframes.clipPath = isDark.value ? clipPath.reverse() : clipPath
|
||||
}
|
||||
else if (mode === 'skew-clip') {
|
||||
const clipPath = [
|
||||
'polygon(0px 0px, 0px 0px, 0px 0px)',
|
||||
`polygon(0px 0px, ${innerWidth * 2}px 0px, 0px ${innerHeight * 2}px)`,
|
||||
]
|
||||
keyframes.clipPath = isDark.value ? clipPath.reverse() : clipPath
|
||||
}
|
||||
else {
|
||||
keyframes.opacity = isDark.value ? [1, 0] : [0, 1]
|
||||
duration = 300
|
||||
}
|
||||
|
||||
document.documentElement.animate(
|
||||
keyframes,
|
||||
{
|
||||
duration,
|
||||
easing: 'ease-in',
|
||||
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
const switchTitle = ref('')
|
||||
@ -52,3 +118,21 @@ watchPostEffect(() => {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
mix-blend-mode: normal;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
[data-theme="dark"]::view-transition-new(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::view-transition-new(root),
|
||||
[data-theme="dark"]::view-transition-old(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -9,13 +9,18 @@ export const darkModeSymbol: InjectionKey<DarkModeRef> = Symbol(
|
||||
__VUEPRESS_DEV__ ? 'darkMode' : '',
|
||||
)
|
||||
|
||||
export function enableTransitions() {
|
||||
return 'startViewTransition' in document
|
||||
&& window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||
}
|
||||
|
||||
export function setupDarkMode(app: App): void {
|
||||
const theme = useThemeData()
|
||||
|
||||
const transition = theme.value.transition
|
||||
const disableTransition = typeof transition === 'object'
|
||||
const disableTransition = enableTransitions() || (typeof transition === 'object'
|
||||
? transition.appearance === false
|
||||
: transition === false
|
||||
: transition === false)
|
||||
|
||||
const appearance = theme.value.appearance
|
||||
const isDark
|
||||
|
||||
@ -63,7 +63,7 @@ export interface ThemeTransition {
|
||||
postList?: boolean
|
||||
/**
|
||||
* 是否启用 深色/浅色 模式切换过渡动画
|
||||
* @default true
|
||||
* @default 'fade'
|
||||
*/
|
||||
appearance?: boolean
|
||||
appearance?: boolean | 'fade' | 'circle-clip' | 'horizontal-clip' | 'vertical-clip' | 'skew-clip'
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user