每條游來游去的小魚,都是正在瀏覽這一頁的訪客,有一條是你自己 ( ´ ▽ ` )ノ 覺得干擾閱讀的話,可以把小魚關掉。
逐字轉場
text讓每個文字都有進入進出動畫。( •̀ ω •́ )✧
| 名稱 | 描述 |
|---|---|
| Anime.js | 輕量級 JavaScript 動畫函式庫 |
| JS 動畫 | 基於 JavaScript 實現的動畫,達成更複雜、精準的動畫控制,常見套件有 GSAP、anime.js 等 |
預設就是經典的淡入淡出。( •̀ ω •́ )✧
我是鱈魚🐟
一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的文字
<template> <div class="w-full flex flex-col gap-4 example-wrap"> <base-checkbox v-model="visible" :label="t('show')" class="example-ctrl" />
<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>可以自行設定文字分割邏輯或是提供分好的文字。
一隻熱愛程式的魚,但是沒有手指可以打鍵盤,更買不到能在水裡用的電腦。
鱈魚是一種很油很油的肥魚
<template> <div class="w-full flex flex-col gap-4 example-wrap"> <base-checkbox v-model="visible" :label="t('show')" class="example-ctrl" />
<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>元件內建了一些簡單的效果,來試試吧。◝( •ω• )◟
(點擊以下任一方塊,開始切換)
一段展示用的文字
clip-right
一段展示用的文字
random-spin
一段展示用的文字
landing
一段展示用的文字
flicker
一段展示用的文字
converge
一段展示用的文字
whirling
一段展示用的文字
gather
一段展示用的文字
emerge
<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-4 py-4 sm:px-10" :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>參數皆可自定義,寫法詳見 anime.js 文件
來打造各種獨特的轉場效果!(≖‿ゝ≖)✧
(點擊以下任一方塊,開始切換)
程式如詩寫,美到無人解
人和程式,一個能跑就行
聽說鱈魚體重沒有破百
鱈魚:「那個沒有也太晚出現了吧!Σ(ˊДˋ;)」
查看範例原始碼
<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-3 py-6 sm:px-9" :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 constconst 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>切分文字後,為每個區塊建立唯一 id,利用 anime.js 實現動畫效果
詳細說明請見此文章。
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;}const emit = defineEmits<{ beforeEnter: []; afterEnter: []; beforeLeave: []; afterLeave: [];}>()