Text Characters Transition text
Give every character its own enter and leave animation. ( •̀ ω •́ )✧
Usage Examples
Basic Usage
The default is the classic fade in and fade out. ( •̀ ω •́ )✧
I am Codfish 🐟
A very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long piece of text
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6">
<base-checkbox
v-model="visible"
:label="t('show')"
class="border rounded p-4"
/>
<div class="flex flex-col gap-2">
<text-characters-transition
:visible="visible"
:label="t('iAmCodfish')"
class="text-2xl tracking-wider"
/>
<text-characters-transition
:visible="visible"
:label="t('longText')"
:enter="(i) => ({
delay: i * 5,
})"
:leave="(i) => ({
delay: i * 5,
})"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import TextCharactersTransition from '../text-characters-transition.vue'
const { t } = useI18n()
const visible = ref(false)
</script>Custom Splitter
You can set your own text splitting logic or provide pre-split text.
A fish that loves programming, but has no fingers to type on a keyboard, and can't even buy a computer that works underwater.
Codfishis a kind ofvery oilyvery oilyfat fish
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6">
<base-checkbox
v-model="visible"
:label="t('show')"
class="border rounded p-4"
/>
<div class="flex flex-col gap-2">
<text-characters-transition
:visible="visible"
:label="t('fishStory')"
:splitter="/(,)/"
/>
<text-characters-transition
:visible="visible"
:label="codfishLabelList"
:enter="(i) => ({
delay: i * 200,
})"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import TextCharactersTransition from '../text-characters-transition.vue'
const { t } = useI18n()
const visible = ref(false)
const codfishLabelList = computed(() => [
t('codfish'),
t('isAKindOf'),
t('veryOily'),
t('veryOily'),
t('de'),
t('fatFish'),
])
</script>Transition Types
The component comes with some simple built-in effects. Give them a try! ◝( •ω• )◟
(Click any block below to start the transition)
A demo text for display
A demo text for display
A demo text for display
A demo text for display
A demo text for display
A demo text for display
A demo text for display
A demo text for display
View example source code
<template>
<div class="w-full flex flex-col gap-4">
<div class="flex flex-col items-center gap-2 text-3xl font-bold tracking-wider">
<div
v-for="(item, i) in list"
:key="i"
class="clickable-box relative border px-10 py-4"
:class="{ 'border-x-4': item.visible }"
@click="toggleVisible(item)"
>
<text-characters-transition
:label="t('demoText')"
v-bind="item"
class="pointer-events-none"
/>
<div class="absolute bottom-0 left-0 p-2 px-3 text-sm font-normal tracking-normal opacity-20">
{{ item.name }}
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Writable } from 'type-fest'
import type { ExtractComponentProps } from '../../../types'
import { addProp, map } from 'remeda'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import TextCharactersTransition from '../text-characters-transition.vue'
type Param = Writable<ExtractComponentProps<typeof TextCharactersTransition>>
type Item = Pick<Param, 'name' | 'visible'>
const { t } = useI18n()
const list = ref(
map(
[
{ name: 'clip-right' },
{ name: 'random-spin' },
{ name: 'landing' },
{ name: 'flicker' },
{ name: 'converge' },
{ name: 'whirling' },
{ name: 'gather' },
{ name: 'emerge' },
] satisfies Item[],
addProp('visible', false),
),
)
function toggleVisible(item: Pick<Param, 'visible'>) {
item.visible = !item.visible
}
</script>
<style scoped lang="sass">
.clickable-box
cursor: pointer
transition-duration: 0.4s
&:active
transition-duration: 0.1s
scale: 0.98
</style>Custom Transitions
All parameters are customizable. See the anime.js documentation for details.
Create all kinds of unique transition effects! (≖‿ゝ≖)✧
(Click any block below to start the transition)
Code written like poetry, so beautiful no one understands
People and programs, as long as they can run it's fine
They say codfish weighsnotover 100
Codfish: "That 'no' appeared way too late! Σ(ˊДˋ;)"
View example source code
<template>
<div class="w-full flex flex-col gap-4">
<div class="flex flex-col flex-1 items-center justify-around gap-6 text-xl">
<div
v-for="(item, i) in list"
:key="i"
class="clickable-box border px-9 py-6"
:class="{ 'border-x-4': item.visible }"
@click="toggleVisible(item)"
>
<text-characters-transition v-bind="item" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Writable } from 'type-fest'
import type { ExtractComponentProps } from '../../../types'
import anime from 'animejs'
import { sample } from 'remeda'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import TextCharactersTransition from '../text-characters-transition.vue'
type Param = Writable<ExtractComponentProps<typeof TextCharactersTransition>>
type Item = Pick<Param, 'label' | 'enter' | 'leave' | 'visible'> & {
class?: string;
}
const { t } = useI18n()
const negativeList = [1, -1] as const
const randomNegative = () => sample(negativeList, 1)[0]
const list = ref<Item[]>([
{
visible: false,
label: t('poeticCode'),
class: 'font-wenkai tracking-[0.2rem]',
enter: (i) => ({
opacity: [0, 1],
filter: ['blur(20px)', 'blur(0px)'],
translateX: () => [
anime.random(40, 60) * randomNegative(),
0,
],
translateY: () => [
anime.random(50, 60) * randomNegative(),
0,
],
delay: i * 100,
duration: 1600,
easing: 'easeOutCirc',
}),
leave: (i) => ({
opacity: 0,
filter: 'blur(20px)',
delay: i * 50,
duration: 900,
easing: 'easeInCirc',
}),
},
{
visible: false,
class: 'tracking-[0.2rem] perspective',
label: [t('people'), t('and'), t('programs'), t('comma'), t('asLongAs'), t('canRun'), t('itsFine')],
enter: (i) => ({
opacity: [0, 1],
rotateX: [anime.random(180, 90), 0],
rotateY: [270, 0],
rotateZ: [anime.random(-90, 90), 0],
scaleX: [0.5, 1],
easing: 'easeOutCirc',
duration: 1000,
delay: i * 300,
}),
leave: (i) => ({
opacity: 0,
rotateX: anime.random(-180, -90),
rotateY: anime.random(-90, 90),
rotateZ: anime.random(-90, 90),
scaleX: 0.5,
easing: 'easeInExpo',
duration: 1400,
delay: i * 100,
}),
},
{
visible: false,
class: 'flex flex-nowrap justify-center items-center font-bold tracking-wider w-[18rem] h-[2.8rem]',
label: [
{
value: t('codfishWeight'),
enter: () => ({
opacity: [0, 1],
}),
leave: () => ({
opacity: 0,
delay: 500,
}),
},
{
value: t('didNot'),
enter: () => ({
fontSize: [
'0rem',
'1.25rem',
],
delay: 2000,
}),
leave: () => ({
opacity: 0,
delay: 500,
}),
},
{
value: t('exceed100'),
enter: () => ({
opacity: [0, 1],
fontSize: [
{ value: '3rem' },
{ value: '1.25rem' },
],
color: [
{ value: '#f00' },
{ value: '#000' },
],
rotate: [
{ value: '10deg' },
{ value: '0deg' },
],
delay: 500,
}),
leave: () => ({
opacity: [
{ value: 1 },
{ value: 0 },
],
fontSize: [
{ value: '3rem' },
{ value: '1.25rem' },
],
color: [
{ value: '#f0f' },
{ value: '#000' },
],
rotate: [
{ value: '-10deg' },
{ value: '0deg' },
],
}),
},
],
},
])
function toggleVisible(item: Pick<Param, 'visible'>) {
item.visible = !item.visible
}
</script>
<style scoped lang="sass">
.font-wenkai
text-shadow: 0 0 10px rgba(#111, 0.1)
.perspective
perspective: 100px
transform-style: preserve-3d
.clickable-box
cursor: pointer
transition-duration: 0.4s
&:active
transition-duration: 0.1s
scale: 0.98
</style>How It Works
After splitting the text, each segment is assigned a unique ID, and anime.js is used to implement the animation effects.
For detailed explanation, see this article.
Source Code
API
Props
interface Props {
visible?: boolean;
label: string | string[] | Array<{
value: string;
enter: AnimeFuncParam;
leave: AnimeFuncParam;
}>;
/** html tag
*
* @default 'p'
*/
tag?: string;
/** 如何切割文字
*
* 只有在 label 為 string 時有效
*
* @default /.*?/u
*/
splitter?: RegExp | ((label: string) => string[]);
/** 過場名稱。使用預設內容 */
name?: `${TransitionName}`;
/** 進入動畫設定 */
enter?: AnimeFuncParam;
/** 離開動畫設定 */
leave?: AnimeFuncParam;
}Emits
const emit = defineEmits<{
beforeEnter: [];
afterEnter: [];
beforeLeave: [];
afterLeave: [];
}>()