VFX Transition transition
We previously used an SVG Filter implementation. Not only did it occupy the filter property on style, it was also limited by SVG support and other issues—in short, there were quite a lot of constraints. ( ˘•ω•˘ )
Thanks to snapDOM, we can easily put the DOM onto a canvas and create all sorts of cool effects! ヾ(◍'౪`◍)ノ゙
Usage examples
Basic usage
Its usage is the same as Vue’s built-in Transition component.
hello
View example source code
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6">
<div class="flex flex-col gap-4 border rounded">
<base-checkbox
v-model="visible"
:label="$t('show')"
class="p-4"
/>
</div>
<div class="flex justify-center">
<transition-vfx>
<div
v-if="visible"
class="card rounded p-6"
>
{{ $t('hello') }}
</div>
</transition-vfx>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BaseCheckbox from '../../base-checkbox.vue'
import TransitionVfx from '../transition-vfx.vue'
const visible = ref(true)
</script>
<style scoped lang="sass">
.card
background-color: light-dark(#edf0f2, #383e45)
</style>
<i18n lang="json">
{
"zh-hant": {
"show": "顯示",
"hello": "安安"
},
"en": {
"show": "Show",
"hello": "Hello"
}
}
</i18n>Enter & leave
You can specify different effects for enter and leave.
Enter Effect
Leave Effect

Codfish
View example source code
vue
<template>
<div class="w-full flex flex-col gap-4">
<div class="grid grid-cols-6 items-center gap-2 border rounded p-4">
<div class="col-span-2 md:col-span-1">
{{ t('enterEffect') }}
</div>
<div class="col-span-4 border rounded md:col-span-2">
<select
v-model="enterName"
class="w-full p-2"
>
<option
v-for="option in options"
:key="option"
:value="option"
>
{{ option }}
</option>
</select>
</div>
<div class="col-span-2 md:col-span-1">
{{ t('leaveEffect') }}
</div>
<div class="col-span-4 border rounded md:col-span-2">
<select
v-model="leaveName"
class="w-full p-2"
>
<option
v-for="option in options"
:key="option"
:value="option"
>
{{ option }}
</option>
</select>
</div>
<div
class="col-span-6 border rounded-lg duration-300"
:class="{
'cursor-not-allowed opacity-30': isTransitioning,
'cursor-pointer': !isTransitioning,
}"
>
<base-checkbox
v-model="visible"
:label="t('show')"
class="w-full cursor-pointer p-4"
:class="{ 'pointer-events-none': isTransitioning }"
/>
</div>
</div>
<div
class="h-[50vh] flex items-center justify-center"
:class="{ 'pointer-events-none': isTransitioning }"
>
<transition-vfx
:enter-params="params.enter"
:leave-params="params.leave"
@enter="isTransitioning = true"
@leave="isTransitioning = true"
@after-enter="isTransitioning = false"
@after-leave="isTransitioning = false"
>
<div
v-if="visible"
class="card flex flex-col items-center gap-4 border rounded-xl p-6"
>
<img
src="/low/profile.webp"
class="mb-4 h-[180px] w-[180px] overflow-hidden border-4 border-white rounded-full shadow-xl"
>
<div class="text-xl font-bold">
{{ t('codfishName') }}
</div>
<base-input
v-model="text"
class="w-[22rem] text-center"
:class="{
'font-bold': styles.bold,
'italic': styles.italic,
}"
/>
<div class="flex gap-2">
<base-checkbox
v-model="styles.bold"
:label="t('bold')"
/>
<base-checkbox
v-model="styles.italic"
:label="t('italic')"
/>
</div>
</div>
</transition-vfx>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import BaseInput from '../../base-input.vue'
import TransitionVfx from '../transition-vfx.vue'
import { TransitionName } from '../type'
const { t } = useI18n()
const text = ref(t('inputPlaceholder'))
const styles = ref({
bold: false,
italic: false,
})
const isTransitioning = ref(false)
const visible = ref(true)
const enterName = ref<`${TransitionName}`>('shatter')
const leaveName = ref<`${TransitionName}`>('shatter')
const params = computed(() => ({
enter: { name: enterName.value },
leave: { name: leaveName.value },
}))
const options = Object.values(TransitionName)
</script>
<style scoped lang="sass">
.card
background-color: light-dark(#edf0f2, #383e45)
</style>How it works
- Intercept
Transitionevents - Create a canvas that covers the element and position it using CSS Anchor
- Use
snapDOMto draw the DOM onto the canvas - During
enterandleave, hide the original DOM and show the canvas, like a stunt double ( •̀ ω •́ )✧ - Implement the
enterandleaveanimation logic on the canvas - After the animation finishes, restore the original DOM and hide the canvas
Source code
API
Props
interface Props {
appear?: boolean;
enterParams?: TransitionParams;
leaveParams?: TransitionParams;
duration?: number;
}Emits
const emit = defineEmits<{
(e: 'enter'): void;
(e: 'afterEnter'): void;
(e: 'leave'): void;
(e: 'afterLeave'): void;
}>()Slots
const slots = defineSlots<{
default?: () => unknown;
}>()