feat(ui-transitions): more transitions

Signed-off-by: Neko Ayaka <neko@ayaka.moe>
This commit is contained in:
Neko Ayaka
2025-03-06 18:28:28 +08:00
parent b371c5fdbe
commit bfd6f297cc
15 changed files with 159 additions and 116 deletions
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'slide',
stageTransition: {
name: 'slide',
},
},
})
</script>
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'slope-slide',
stageTransition: {
name: 'slope-slide',
},
},
})
</script>
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'arrow',
stageTransition: {
name: 'arrow',
},
},
})
</script>
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'multiple-blocks-reveal',
stageTransition: {
name: 'multiple-blocks-reveal',
},
},
})
</script>
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'fantasy-fall',
stageTransition: {
name: 'fantasy-fall',
},
},
})
</script>
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'rectangles-rotate',
stageTransition: {
name: 'rectangles-rotate',
},
},
})
</script>
@@ -5,7 +5,9 @@ import Buttons from '../components/Buttons.vue'
definePage({
meta: {
stageTransition: 'bubble-wave-out',
stageTransition: {
name: 'bubble-wave-out',
},
},
})
</script>
@@ -2,15 +2,20 @@
import { computed, onMounted } from 'vue'
const props = withDefaults(defineProps<{
primaryColor?: string
secondaryColor?: string
stageTransition?: {
primaryColor?: string
secondaryColor?: string
}
}>(), {
primaryColor: '#666',
secondaryColor: '#ccc',
stageTransition: () => ({
primaryColor: '#666',
secondaryColor: '#ccc',
}),
})
const overlayColor1 = computed(() => props.primaryColor || '#666')
const overlayColor2 = computed(() => props.secondaryColor || '#ccc')
const stageTransition = computed(() => props.stageTransition)
const overlayColor1 = computed(() => stageTransition.value.primaryColor || '#666')
const overlayColor2 = computed(() => stageTransition.value.secondaryColor || '#ccc')
onMounted(() => {
document.documentElement.style.setProperty('--stage-transition-3-overlay-color-1', overlayColor1.value)
@@ -1,20 +1,20 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { computed, onMounted } from 'vue'
const props = withDefaults(defineProps<{
colors?: string[]
delay?: number
duration?: number
}>(), {
colors: () => ['#eee', '#ebcb8b', '#c56370', '#3f3b52'],
delay: 0,
duration: 0.4,
})
const props = defineProps<{
stageTransition?: {
colors?: string[]
delay?: number
duration?: number
}
}>()
const colors = computed(() => props.stageTransition.colors || ['#eee', '#ebcb8b', '#c56370', '#3f3b52'])
onMounted(() => {
document.documentElement.style.setProperty('--circle-expansion-delay', `${props.delay}s`)
document.documentElement.style.setProperty('--circle-expansion-duration', `${props.duration}s`)
props.colors.forEach((color, index) => {
document.documentElement.style.setProperty('--circle-expansion-delay', `${props.stageTransition.delay || 0}s`)
document.documentElement.style.setProperty('--circle-expansion-duration', `${props.stageTransition.duration || 0.4}s`)
colors.value.forEach((color, index) => {
document.documentElement.style.setProperty(`--circle-expansion-color-${index + 1}`, color)
})
})
@@ -22,7 +22,7 @@ onMounted(() => {
<template>
<div class="circle-expansion-transition">
<div v-for="(_, index) in props.colors" :key="index" />
<div v-for="(_, index) in colors" :key="index" />
</div>
</template>
@@ -1,33 +1,26 @@
<script setup lang="ts">
import { onMounted } from 'vue'
const props = withDefaults(defineProps<{
primaryColor?: string
duration?: number
delay?: number
borderRadius?: {
sm?: string
md?: string
lg?: string
const props = defineProps <{
stageTransition?: {
primaryColor?: string
duration?: number
delay?: number
borderRadius?: {
sm?: string
md?: string
lg?: string
}
}
}>(), {
primaryColor: '#eee',
duration: 0.6,
delay: 0,
borderRadius: () => ({
sm: '14rem',
md: '14rem',
lg: '50%',
}),
})
}> ()
onMounted(() => {
document.documentElement.style.setProperty('--fantasy-fall-color', props.primaryColor)
document.documentElement.style.setProperty('--fantasy-fall-duration', `${props.duration}s`)
document.documentElement.style.setProperty('--fantasy-fall-delay', `${props.delay}s`)
document.documentElement.style.setProperty('--fantasy-fall-radius-sm', `${props.borderRadius.sm}`)
document.documentElement.style.setProperty('--fantasy-fall-radius-md', `${props.borderRadius.md}`)
document.documentElement.style.setProperty('--fantasy-fall-radius-lg', `${props.borderRadius.lg}`)
document.documentElement.style.setProperty('--fantasy-fall-color', props.stageTransition.primaryColor || '#eee')
document.documentElement.style.setProperty('--fantasy-fall-duration', `${props.stageTransition.duration || 0.6}s`)
document.documentElement.style.setProperty('--fantasy-fall-delay', `${props.stageTransition.delay || 0}s`)
document.documentElement.style.setProperty('--fantasy-fall-radius-sm', `${props.stageTransition.borderRadius?.sm || '14rem'}`)
document.documentElement.style.setProperty('--fantasy-fall-radius-md', `${props.stageTransition.borderRadius?.md || '14rem'}`)
document.documentElement.style.setProperty('--fantasy-fall-radius-lg', `${props.stageTransition.borderRadius?.lg || '50%'}`)
})
</script>
@@ -2,15 +2,20 @@
import { computed, onMounted } from 'vue'
const props = withDefaults(defineProps<{
primaryColor?: string
secondaryColor?: string
stageTransition?: {
primaryColor?: string
secondaryColor?: string
}
}>(), {
primaryColor: '#666',
secondaryColor: '#ccc',
stageTransition: () => ({
primaryColor: '#666',
secondaryColor: '#ccc',
}),
})
const overlayColor1 = computed(() => props.primaryColor || '#666')
const overlayColor2 = computed(() => props.secondaryColor || '#ccc')
const stageTransition = computed(() => props.stageTransition)
const overlayColor1 = computed(() => stageTransition.value.primaryColor || '#666')
const overlayColor2 = computed(() => stageTransition.value.secondaryColor || '#ccc')
onMounted(() => {
document.documentElement.style.setProperty('--stage-transition-4-overlay-color-1', overlayColor1.value)
@@ -1,33 +1,27 @@
<script setup lang="ts">
import { onMounted } from 'vue'
const props = withDefaults(defineProps<{
primaryColor?: string
secondaryColor?: string
tertiaryColor?: string
duration?: number
delay?: number
rotation?: number
staggerDelay?: number
}>(), {
primaryColor: '#ebcb8b', // First circle (top-left)
secondaryColor: '#c56370', // Second circle (bottom-right)
tertiaryColor: '#43445b', // Third circle (center)
duration: 0.6,
delay: 0,
rotation: 270,
staggerDelay: 0.1, // Delay between each circle animation
})
const props = defineProps<{
stageTransition?: {
primaryColor?: string
secondaryColor?: string
tertiaryColor?: string
duration?: number
delay?: number
rotation?: number
staggerDelay?: number
}
}>()
onMounted(() => {
// Set CSS variables for all three circles
document.documentElement.style.setProperty('--rectangle-rotate-1-color', props.primaryColor)
document.documentElement.style.setProperty('--rectangle-rotate-2-color', props.secondaryColor)
document.documentElement.style.setProperty('--rectangle-rotate-3-color', props.tertiaryColor)
document.documentElement.style.setProperty('--rectangle-rotate-duration', `${props.duration}s`)
document.documentElement.style.setProperty('--rectangle-rotate-delay', `${props.delay}s`)
document.documentElement.style.setProperty('--rectangle-rotate-stagger', `${props.staggerDelay}s`)
document.documentElement.style.setProperty('--rectangle-rotate-rotation', `${props.rotation}deg`)
document.documentElement.style.setProperty('--rectangle-rotate-1-color', props.stageTransition.primaryColor || '#ebcb8b')
document.documentElement.style.setProperty('--rectangle-rotate-2-color', props.stageTransition.secondaryColor || '#c56370')
document.documentElement.style.setProperty('--rectangle-rotate-3-color', props.stageTransition.tertiaryColor || '#43445b')
document.documentElement.style.setProperty('--rectangle-rotate-duration', `${props.stageTransition.duration || 0.6}s`)
document.documentElement.style.setProperty('--rectangle-rotate-delay', `${props.stageTransition.delay || 0}s`)
document.documentElement.style.setProperty('--rectangle-rotate-stagger', `${props.stageTransition.staggerDelay || 0.1}s`)
document.documentElement.style.setProperty('--rectangle-rotate-rotation', `${props.stageTransition.rotation || 270}deg`)
})
</script>
@@ -2,15 +2,20 @@
import { computed, onMounted } from 'vue'
const props = withDefaults(defineProps<{
primaryColor?: string
secondaryColor?: string
stageTransition?: {
primaryColor?: string
secondaryColor?: string
}
}>(), {
primaryColor: '#666',
secondaryColor: '#ccc',
stageTransition: () => ({
primaryColor: '#666',
secondaryColor: '#ccc',
}),
})
const overlayColor1 = computed(() => props.primaryColor || '#666')
const overlayColor2 = computed(() => props.secondaryColor || '#ccc')
const stageTransition = computed(() => props.stageTransition)
const overlayColor1 = computed(() => stageTransition.value.primaryColor || '#666')
const overlayColor2 = computed(() => stageTransition.value.secondaryColor || '#ccc')
onMounted(() => {
document.documentElement.style.setProperty('--stage-transition-1-overlay-color-1', overlayColor1.value)
@@ -2,15 +2,20 @@
import { computed, onMounted } from 'vue'
const props = withDefaults(defineProps<{
primaryColor?: string
secondaryColor?: string
stageTransition?: {
primaryColor?: string
secondaryColor?: string
}
}>(), {
primaryColor: '#666',
secondaryColor: '#ccc',
stageTransition: () => ({
primaryColor: '#666',
secondaryColor: '#ccc',
}),
})
const overlayColor1 = computed(() => props.primaryColor || '#666')
const overlayColor2 = computed(() => props.secondaryColor || '#ccc')
const stageTransition = computed(() => props.stageTransition)
const overlayColor1 = computed(() => stageTransition.value.primaryColor || '#666')
const overlayColor2 = computed(() => stageTransition.value.secondaryColor || '#ccc')
onMounted(() => {
document.documentElement.style.setProperty('--stage-transition-2-overlay-color-1', overlayColor1.value)
@@ -10,13 +10,17 @@ import RectanglesRotateTransition from './RectanglesRotateTransition.vue'
import SlideTransition from './SlideTransition.vue'
import SlopeSlideTransition from './SlopeSlideTransition.vue'
withDefaults(defineProps<{
const props = defineProps<{
primaryColor?: string
secondaryColor?: string
}>(), {
primaryColor: '#666',
secondaryColor: '#ccc',
})
}>()
interface StageTransitionCommonParams {
name: string
primaryColor?: string
secondaryColor?: string
direction?: 'top' | 'bottom' | 'left' | 'right'
}
type TransitionComponent =
| typeof SlideTransition
@@ -29,8 +33,10 @@ type TransitionComponent =
const router = useRouter()
const showTransition = ref(false)
const transitionStage = ref<TransitionStage>()
const activeTransitionName = ref('')
const transitionStage = ref<TransitionStage | null>(null)
const activeStageTransitionParams = ref<StageTransitionCommonParams>()
// Define transition lifecycle events
export type TransitionStage =
@@ -115,10 +121,10 @@ async function triggerHooks(stage: TransitionStage, data: any = {}) {
}
}
async function triggerTransitionAsyncFn(name: string, next: NavigationCallback, resolve: (value: void | PromiseLike<void>) => void) {
const transition = transitions.value[name]
async function triggerTransitionAsyncFn(params: StageTransitionCommonParams, next: NavigationCallback, resolve: (value: void | PromiseLike<void>) => void) {
const transition = transitions.value[params.name]
if (!transition) {
console.error(`Transition ${name} not found`)
console.error(`Transition ${params.name} not found`)
next()
resolve()
return
@@ -144,13 +150,14 @@ async function triggerTransitionAsyncFn(name: string, next: NavigationCallback,
try {
// Before enter
await triggerHooks('before-enter', { transitionName: name })
await triggerHooks('before-enter', { transitionName: params.name })
// Handle resetting current transition if needed
if (showTransition.value) {
// Fast track to exit if a transition is already active
await triggerHooks('before-leave', { transitionName: activeTransitionName.value })
activeTransitionName.value = ''
activeStageTransitionParams.value = undefined
showTransition.value = false
await triggerHooks('after-leave', { transitionName: activeTransitionName.value })
@@ -159,7 +166,8 @@ async function triggerTransitionAsyncFn(name: string, next: NavigationCallback,
}
// Start new transition
activeTransitionName.value = name
activeTransitionName.value = params.name
activeStageTransitionParams.value = params
showTransition.value = true
// Enter active phase
@@ -167,10 +175,7 @@ async function triggerTransitionAsyncFn(name: string, next: NavigationCallback,
// Trigger navigation phase at the configured time
setTimeout(async () => {
await triggerHooks('navigation', {
transitionName: name,
config: transition, // Pass full config to hooks
})
await triggerHooks('navigation', { transitionName: params.name, config: transition })
// Ensure navigation happens even if no hook handled it
if (!hasNavigated) {
@@ -181,17 +186,17 @@ async function triggerTransitionAsyncFn(name: string, next: NavigationCallback,
// After enter - entry phase complete
setTimeout(async () => {
await triggerHooks('after-enter', { transitionName: name })
await triggerHooks('after-enter', { transitionName: params.name })
}, transition.duration)
// Before leave - start exit phase
setTimeout(async () => {
await triggerHooks('before-leave', { transitionName: name })
await triggerHooks('before-leave', { transitionName: params.name })
}, transition.duration + 10) // Small offset after enter completes
// Leave active - exit animation running
setTimeout(async () => {
await triggerHooks('leave-active', { transitionName: name })
await triggerHooks('leave-active', { transitionName: params.name })
}, transition.duration + 20) // Small offset
// After leave - completely finished
@@ -199,10 +204,14 @@ async function triggerTransitionAsyncFn(name: string, next: NavigationCallback,
setTimeout(async () => {
showTransition.value = false
activeTransitionName.value = ''
await triggerHooks('after-leave', { transitionName: name })
activeStageTransitionParams.value = undefined
await triggerHooks('after-leave', { transitionName: params.name })
resolve()
}, transition.duration + totalDuration)
}
catch (error) {
console.error(error)
}
finally {
// Always remove the navigation hook
removeNavHook()
@@ -218,14 +227,27 @@ async function triggerTransitionAsyncFn(name: string, next: NavigationCallback,
}
// Improved transition trigger with navigation callback
function triggerTransition(name: string, next: NavigationCallback) {
function triggerTransition(params: StageTransitionCommonParams, next: NavigationCallback) {
return new Promise<void>((resolve) => {
triggerTransitionAsyncFn(name, next, resolve)
triggerTransitionAsyncFn(params, next, resolve)
})
}
router.beforeEach((to, _from, next) => {
triggerTransition(to.meta.stageTransition as string, next)
if (typeof to.meta.stageTransition !== 'object') {
next()
return
}
const stageTransition = to.meta.stageTransition as StageTransitionCommonParams
if (typeof props.primaryColor !== 'undefined') {
stageTransition.primaryColor = props.primaryColor
}
if (typeof props.secondaryColor !== 'undefined') {
stageTransition.secondaryColor = props.secondaryColor
}
triggerTransition(stageTransition, next)
})
</script>
@@ -233,7 +255,7 @@ router.beforeEach((to, _from, next) => {
<slot />
<template v-if="showTransition">
<template v-if="transitions[activeTransitionName]">
<component :is="transitions[activeTransitionName].component" :primary-color="primaryColor" :secondary-color="secondaryColor" />
<component :is="transitions[activeTransitionName].component" :stage-transition="activeStageTransitionParams" />
</template>
</template>
</template>