每條游來游去的小魚,都是正在瀏覽這一頁的訪客,有一條是你自己 ( ´ ▽ ` )ノ 覺得干擾閱讀的話,可以把小魚關掉。
貓臉
util各種表情變化,讓網站提供更多情緒價值。ヾ(◍'౪`◍)ノ゙
| 名稱 | 描述 |
|---|---|
| Anime.js | 輕量級 JavaScript 動畫函式庫 |
| JS 動畫 | 基於 JavaScript 實現的動畫,達成更複雜、精準的動畫控制,常見套件有 GSAP、anime.js 等 |
| SVG | 可在 DOM 中使用的圖形格式,適合複雜圖形和動畫,配合 Vue v-bind 可以實現基於資料的動態效果 |
| SVG Morph | 讓 SVG 在不同形狀間平滑過渡的技術 |
可以切換各種生動的表情。
<template> <div class="example-wrap w-full flex flex-col gap-4"> <util-cat-face class="h-[20vmin]" :facial-expression :stroke-color />
<select-stepper v-model="facialExpression" :options class="w-full" /> </div></template>
<script setup lang="ts">import { computed, ref } from 'vue'import { useSiteDark } from '../../../composables/use-site-dark'import SelectStepper from '../../select-stepper.vue'import { FacialExpression } from '../type'import UtilCatFace from '../util-cat-face.vue'
const facialExpression = ref<`${FacialExpression}`>('neutral')const options = Object.values(FacialExpression) as `${FacialExpression}`[]
const isDark = useSiteDark()const strokeColor = computed(() => isDark.value ? '#FAFAFA' : '#222')</script>配合使用者動作互動
<template> <div class="example-wrap w-full flex-center gap-4"> <div ref="faceRef" class="cursor-pointer" > <util-cat-face class="h-[20vmin]" :facial-expression :stroke-color /> </div> </div></template>
<script setup lang="ts">import type { FacialExpression } from '../type'import { useCycleList, useIntervalFn, useMouseInElement, useMousePressed } from '@vueuse/core'import { computed, reactive, ref } from 'vue'import { useSiteDark } from '../../../composables/use-site-dark'import UtilCatFace from '../util-cat-face.vue'
const isDark = useSiteDark()const strokeColor = computed(() => isDark.value ? '#ddd' : '#222')
const faceRef = ref<HTMLDivElement>()const mouseInElement = reactive(useMouseInElement(faceRef))const { pressed: isPressed } = useMousePressed()
const { state, next } = useCycleList([ 'neutral', 'confidence', 'pleasant', 'derpy',] satisfies `${FacialExpression}`[])
useIntervalFn(next, 3000)
const facialExpression = computed<`${FacialExpression}`>(() => { if (isPressed.value && !mouseInElement.isOutside) { return 'excited' }
if (!mouseInElement.isOutside) { return 'happy' }
return state.value})</script>滿滿的情緒價值,讓使用者更有共鳴。
玩到表單永遠填不完。ᕕ( ゚ ∀。)ᕗ
<!-- #region main-code --><template> <div class="example-wrap w-full flex flex-col items-center gap-4"> <util-cat-face class="h-[20vmin] cursor-pointer" :facial-expression="currentFacialExpression" :stroke-color @click="handleClick()" />
<form class="relative flex flex-col gap-4 p-8" @submit="handleSubmit" > <base-input v-model="form.name" :label="t('姓名')" required @invalid="handleInvalid" @focus="handleFocus()" />
<base-input v-model="form.phone" :label="t('電話')" pattern="09\d{8}" required :placeholder="t('必須為 09 開頭的 10 位數字')" @invalid="handleInvalid" @focus="handleFocus()" />
<base-btn type="submit" :label="t('送出')" />
<transition name="opacity"> <div v-if="isSubmitted" class="absolute inset-0 z-[40] flex flex-col items-center justify-center gap-6 rounded-xl bg-slate-600 bg-opacity-90 text-white" @click="reset" > <span class="text-xl tracking-wide"> {{ t('表單已送出!(*´∀`)~♥') }} </span>
<span class="cursor-pointer text-xs"> {{ t('點一下再來一次') }} </span> </div> </transition> </form> </div></template>
<script setup lang="ts">import { refAutoReset } from '@vueuse/core'import { sample } from 'remeda'import { computed, ref } from 'vue'import { useI18n } from 'vue-i18n'import { useSiteDark } from '../../../composables/use-site-dark'import BaseBtn from '../../base-btn.vue'import BaseInput from '../../base-input.vue'import { FacialExpression } from '../type'import UtilCatFace from '../util-cat-face.vue'
const isDark = useSiteDark()const { t } = useI18n()const strokeColor = computed(() => isDark.value ? '#ddd' : '#222')
const form = ref({ name: '', phone: '',})const isSubmitted = ref(false)
const facialExpression = refAutoReset(FacialExpression.NEUTRAL, 600)function setFacialExpression(type: FacialExpression) { if (facialExpression.value !== FacialExpression.NEUTRAL) { return } facialExpression.value = type}
const currentFacialExpression = computed(() => { if (isSubmitted.value) { return FacialExpression.HAPPY }
return facialExpression.value})
function handleClick() { const [type] = sample([ FacialExpression.DERPY, ], 1) setFacialExpression(type)}
function handleInvalid() { const [type] = sample([ FacialExpression.SAD, FacialExpression.SURPRISED, FacialExpression.SPEECHLESS, ], 1) setFacialExpression(type)}
function handleFocus() { const [type] = sample([ FacialExpression.PLEASANT, FacialExpression.EXCITED, FacialExpression.CONFIDENCE, ], 1) setFacialExpression(type)}
function handleSubmit(evt: Event) { evt.preventDefault() isSubmitted.value = true}
function reset() { isSubmitted.value = false
form.value = { name: '', phone: '', }}</script>
<style lang="sass" scoped>.rubbing padding: 0.75rem color: #ff7530 opacity: 0.8 border: 1px dashed #ff7530 border-radius: 0.2rem white-space: nowrap text-align: center
.opacity-enter-active, .opacity-leave-active transition-duration: 0.4s.opacity-enter-from, .opacity-leave-to opacity: 0 !important</style><!-- #endregion main-code -->
<i18n lang="json">{ "zh-hant": { "姓名": "姓名", "電話": "電話", "送出": "送出", "表單已送出!(*´∀`)~♥": "表單已送出!(*´∀`)~♥", "點一下再來一次": "點一下再來一次", "必須為 09 開頭的 10 位數字": "必須為 09 開頭的 10 位數字" }, "en": { "姓名": "Name", "電話": "Phone", "送出": "Submit", "表單已送出!(*´∀`)~♥": "Form submitted! (*´∀`)~♥", "點一下再來一次": "Click to try again", "必須為 09 開頭的 10 位數字": "Must be 10 digits starting with 09" }}</i18n>嘗試複雜一點的 svg 動畫,表情變化皆有流暢的轉換過程。
實務上推薦使用 Lottie 或 Rive,功能更強更容易開發。
Lottie 比較早出來,社群較大,有素材網站販售;Rive 功能更強,有狀態機等功能,可以做出複雜互動,但是素材比較少。
interface Props { facialExpression?: `${FacialExpression}`; strokeColor?: string; /** 眼睛追蹤偏移半徑 */ eyeOffsetRadius?: number;}interface Emits { change: [];}