feat(next): soft appearance toggle animation (#238)
parent
3e8db3be9d
commit
ed3588f658
|
@ -7,6 +7,7 @@ export {}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
AppearanceToggle: typeof import('./theme/components/AppearanceToggle.vue')['default']
|
||||||
Changelog: typeof import('./theme/components/Changelog.vue')['default']
|
Changelog: typeof import('./theme/components/Changelog.vue')['default']
|
||||||
HomeContent: typeof import('./theme/components/HomeContent.vue')['default']
|
HomeContent: typeof import('./theme/components/HomeContent.vue')['default']
|
||||||
PageInfo: typeof import('./theme/components/PageInfo.vue')['default']
|
PageInfo: typeof import('./theme/components/PageInfo.vue')['default']
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import { nextTick, provide, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const { isDark } = useData()
|
||||||
|
|
||||||
|
const enableTransitions = () =>
|
||||||
|
'startViewTransition' in document &&
|
||||||
|
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||||
|
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
|
||||||
|
if (!enableTransitions()) {
|
||||||
|
isDark.value = !isDark.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (document as any).startViewTransition !== 'function') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.documentElement.classList.contains('VPSwitchAppearance-ViewTransition')) {
|
||||||
|
document.documentElement.classList.add('VPSwitchAppearance-ViewTransition')
|
||||||
|
}
|
||||||
|
|
||||||
|
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)`
|
||||||
|
]
|
||||||
|
|
||||||
|
await (document as any).startViewTransition(async () => {
|
||||||
|
isDark.value = !isDark.value
|
||||||
|
await nextTick()
|
||||||
|
}).ready
|
||||||
|
|
||||||
|
document.documentElement.animate(
|
||||||
|
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
||||||
|
{
|
||||||
|
duration: 500,
|
||||||
|
easing: 'ease-in',
|
||||||
|
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
::view-transition-old(root),
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation: none;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(root),
|
||||||
|
.dark::view-transition-new(root) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(root),
|
||||||
|
.dark::view-transition-old(root) {
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPSwitchAppearance-ViewTransition .VPSwitchAppearance .check {
|
||||||
|
transition: transform 350ms 0ms !important;
|
||||||
|
}
|
||||||
|
.VPSwitchAppearance-ViewTransition.dark .VPSwitchAppearance .check {
|
||||||
|
transition: transform 350ms 500ms !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,6 +2,7 @@
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import type { Theme } from 'vitepress'
|
import type { Theme } from 'vitepress'
|
||||||
import DefaultTheme from 'vitepress/theme'
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import AppearanceToggle from './components/AppearanceToggle.vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NolebaseEnhancedReadabilitiesPlugin,
|
NolebaseEnhancedReadabilitiesPlugin,
|
||||||
|
@ -22,17 +23,19 @@ import 'uno.css'
|
||||||
export default {
|
export default {
|
||||||
extends: DefaultTheme,
|
extends: DefaultTheme,
|
||||||
Layout: () => {
|
Layout: () => {
|
||||||
return h(DefaultTheme.Layout, null, {
|
return h(AppearanceToggle, null, {
|
||||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
'default': () => h(DefaultTheme.Layout, null, {
|
||||||
'doc-top': () => [
|
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||||
h(NolebaseHighlightTargetedHeading),
|
'doc-top': () => [
|
||||||
],
|
h(NolebaseHighlightTargetedHeading),
|
||||||
'nav-bar-content-after': () => [
|
],
|
||||||
h(NolebaseEnhancedReadabilitiesMenu),
|
'nav-bar-content-after': () => [
|
||||||
],
|
h(NolebaseEnhancedReadabilitiesMenu),
|
||||||
'nav-screen-content-after': () => [
|
],
|
||||||
h(NolebaseEnhancedReadabilitiesScreenMenu),
|
'nav-screen-content-after': () => [
|
||||||
],
|
h(NolebaseEnhancedReadabilitiesScreenMenu),
|
||||||
|
],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
enhanceApp({ app }) {
|
enhanceApp({ app }) {
|
||||||
|
|
Loading…
Reference in New Issue