貓臉 util
各種表情變化,讓網站提供更多情緒價值。ヾ(◍'౪`◍)ノ゙
使用範例
基本用法
可以切換各種生動的表情。
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
<util-cat-face
class="h-[14vh]"
:facial-expression
/>
<div class="border-2 border-[#999] rounded">
<select
v-model="facialExpression"
class="w-full p-2"
>
<option
v-for="option in options"
:key="option"
:value="option"
>
{{ option }}
</option>
</select>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { FacialExpression } from '../type'
import UtilCatFace from '../util-cat-face.vue'
const facialExpression = ref<`${FacialExpression}`>('neutral')
const options = Object.values(FacialExpression)
</script>
簡單互動
配合使用者動作互動
查看範例原始碼
vue
<template>
<div class="w-full flex-center gap-4 border border-gray-300 p-6">
<div
ref="faceRef"
class="cursor-pointer"
>
<util-cat-face
class="h-[14vh]"
:facial-expression
/>
</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 UtilCatFace from '../util-cat-face.vue'
const faceRef = ref<HTMLDivElement>()
const mouseInElement = reactive(useMouseInElement(faceRef))
const { pressed: isPressed } = useMousePressed()
const { state, next } = useCycleList([
'neutral',
'angry',
'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>
表單
滿滿的情緒價值,讓使用者更有共鳴。
玩到表單永遠填不完。ᕕ( ゚ ∀。)ᕗ
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center gap-4 border border-gray-300 p-6">
<util-cat-face
class="h-[10vh] cursor-pointer"
:facial-expression="currentFacialExpression"
@click="handleClick()"
/>
<form
class="relative flex flex-col gap-4 p-8"
@submit="handleSubmit"
>
<base-input
v-model="form.name"
label="姓名"
required
@invalid="handleInvalid"
@blur="handleBlur(form.name)"
/>
<base-input
v-model="form.phone"
label="電話"
pattern="09\d{8}"
required
placeholder="必須為 09 開頭的 10 位數字"
@invalid="handleInvalid"
@blur="handleBlur(form.phone)"
/>
<base-btn
type="submit"
label="送出"
/>
<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">
表單已送出!(*´∀`)~♥
</span>
<span class="cursor-pointer text-xs">
點一下再來一次
</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 BaseBtn from '../../base-btn.vue'
import BaseInput from '../../base-input.vue'
import { FacialExpression } from '../type'
import UtilCatFace from '../util-cat-face.vue'
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,
], 1)
setFacialExpression(type)
}
function handleBlur(value?: any) {
if (!value) {
const [type] = sample([
FacialExpression.ANGRY,
FacialExpression.SPEECHLESS,
], 1)
setFacialExpression(type)
return
}
const [type] = sample([
FacialExpression.PLEASANT,
FacialExpression.EXCITED,
], 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>
原理
嘗試複雜一點的 svg 動畫,表情變化皆有完整的過度效果。
實務上推薦使用 Lottie 或 Rive,功能更強更容易開發。
Lottie 比較早出來,社群較大,有素材網站販售;Rive 功能更強,有狀態機等功能,可以做出複雜互動,但是素材比較少。
原始碼
API
Props
interface Props {
facialExpression?: `${FacialExpression}`;
strokeColor?: string;
/** 眼睛追蹤偏移半徑 */
eyeOffsetRadius?: number;
}
Emits
interface Emits {
change: [];
}
Methods
defineExpose({})