貓耳包裝器 wrapper
任何元素包起來都會長出貓耳,讓萬物皆可萌吧!(^・ω・^ )✧
路人:「這是甚麼鬼玩意兒?(っ´Ι`)っ」
鱈魚:「聽說有貓就給讚啊?( •̀ ω •́ )✧」
路人:「只有耳朵算啥貓啊。(˘・_・˘)」
使用範例
基本用法
使用元件包裹後,萬物都會長出貓耳,變得比較討喜(?
耳朵還會自動調整尺寸呦!ˋ( ° ▽、° )
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center justify-center gap-16 border border-gray-300 py-16">
<wrapper-cat-ear main-color="#AAA">
<base-btn
class=""
label="按鈕"
/>
</wrapper-cat-ear>
<wrapper-cat-ear main-color="#AAA">
<div class="">
<img
class="w-80 rounded"
src="/codfish.webp"
>
</div>
</wrapper-cat-ear>
<wrapper-cat-ear main-color="#AAA">
<div class="border border-[#AAA] rounded">
<select class="px-10 py-2">
<option
disabled
selected
label="請選擇貓貓"
/>
<option label="(>'-'<)ノ" />
<option label="~(=^‥^)" />
<option label="/ᐠ。ꞈ。ᐟ\" />
<option label="(^._.^)ノ" />
<option label="(^・ω・^ )" />
</select>
</div>
</wrapper-cat-ear>
</div>
</template>
<script setup lang="ts">
import BaseBtn from '../../base-btn.vue'
import WrapperCatEar from '../wrapper-cat-ear.vue'
</script>
切換動作
可以切換各種不同的動作
查看範例原始碼
vue
<template>
<div class="h-[50vh] w-full flex flex-col items-center justify-center gap-16 border border-gray-300">
<wrapper-cat-ear
:action="action"
main-color="#999"
>
<div class="border-2 border-[#999] rounded">
<select
v-model="action"
class="p-2"
>
<option
v-for="option in options"
:key="option"
:value="option"
>
{{ option }}
</option>
</select>
</div>
</wrapper-cat-ear>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ActionName } from '..'
import WrapperCatEar from '../wrapper-cat-ear.vue'
const action = ref<`${ActionName}`>('relaxed')
const options = Object.values(ActionName)
</script>
調整顏色
調整毛色,個性化貓貓。
◕ ω ◕
◕ ω ◕
◕ ω ◕
> ◕ ω ◕ <
10 隻橘貓 9 隻胖,1 隻超級胖。(o゚v゚)ノ
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center justify-center gap-10 border border-gray-300 py-10">
<div class="flex items-center border border-gray-300 rounded p-10">
<input
v-model="mainColor"
type="color"
>
<input
v-model="innerColor"
type="color"
>
<wrapper-cat-ear
:main-color="mainColor"
:inner-color="innerColor"
class="ml-10"
>
<div
class="rounded p-2 px-3 text-white"
:style="{ backgroundColor: mainColor }"
v-text="`◕ ω ◕`"
/>
</wrapper-cat-ear>
</div>
<wrapper-cat-ear
main-color="#3b3b3b"
inner-color="#ffc2b8"
>
<div
class="rounded bg-[#3b3b3b] p-2 px-3 text-white"
v-text="`◕ ω ◕`"
/>
</wrapper-cat-ear>
<wrapper-cat-ear
main-color="#03a1fc"
inner-color="#8f003e"
>
<div
class="rounded bg-[#03a1fc] p-2 px-3 text-white"
v-text="`◕ ω ◕`"
/>
</wrapper-cat-ear>
<wrapper-cat-ear
main-color="#ff852e"
inner-color="#ffc2b8"
class="mt-10"
>
<div
class="rounded bg-[#ff852e] px-20 py-12 text-xl text-white"
v-text="`> ◕ ω ◕ <`"
/>
</wrapper-cat-ear>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import WrapperCatEar from '../wrapper-cat-ear.vue'
const mainColor = ref('#b38546')
const innerColor = ref('#ffc2b8')
</script>
互動效果
配合耳朵動作設計各類邏輯,讓互動更加有趣。
嘗試讓滑鼠從遠處慢慢靠近按鈕看看。(. ❛ ᴗ ❛.)
◕ ω ◕
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center justify-center gap-16 border border-gray-300 py-20">
<wrapper-cat-ear
:action="action"
main-color="#666"
class="cat border-2 border-[#666] rounded"
>
<div
ref="catRef"
class="flex items-center justify-center bg-white px-16 py-8"
>
<transition
name="cat"
mode="out-in"
>
<div
:key="face"
class="absolute select-none text-nowrap text-2xl font-bold"
v-text="face"
/>
</transition>
</div>
</wrapper-cat-ear>
</div>
</template>
<script setup lang="ts">
import type { ActionName } from '..'
import { throttleFilter, useMouseInElement, useMousePressed } from '@vueuse/core'
import { computed, ref } from 'vue'
import { getVectorLength } from '../../../common/utils'
import WrapperCatEar from '../wrapper-cat-ear.vue'
const faceMap: Record<ActionName, string> = {
peekaboo: '送出',
relaxed: '◕ ω ◕',
fear: '´•̥̥̥ ω •̥̥̥`',
displeased: '˘・ ω ・˘',
shake: '≧ X ≦',
}
const catRef = ref()
const {
elementX,
elementY,
elementWidth,
elementHeight,
isOutside,
} = useMouseInElement(catRef, {
eventFilter: throttleFilter(35),
})
const { pressed } = useMousePressed()
/** 滑鼠與貓的距離 */
const distance = computed(() => getVectorLength({
x: elementX.value - elementWidth.value / 2,
y: elementY.value - elementHeight.value / 2,
}))
/** 煩躁的距離 */
const displeasedDistance = computed(
() => Math.max(elementWidth.value, elementHeight.value),
)
/** 變成貓的距離 */
const catDistance = computed(
() => displeasedDistance.value * 2,
)
const action = computed<`${ActionName}`>(() => {
if (!isOutside.value) {
return pressed.value ? 'shake' : 'fear'
}
if (distance.value > catDistance.value) {
return 'peekaboo'
}
if (distance.value < displeasedDistance.value) {
return 'displeased'
}
return 'relaxed'
})
const face = computed(() => faceMap[action.value])
</script>
<style lang="sass">
.cat
transition-duration: 0.4s
box-shadow: 3px 3px 0px 0px rgba(#AAA, 0.8)
&:hover
transition-duration: 0.3s
scale: 1.02
box-shadow: 5px 5px 0px 0px rgba(#AAA, 0.5)
&:active
transition-duration: 0.1s
scale: 0.95
animation: shake 0.3s infinite ease-in-out
box-shadow: 0px 0px 0px 0px rgba(#AAA, 1)
.cat-enter-active, .cat-leave-active
transition-duration: 0.2s
.cat-enter-from, .cat-leave-to
transform: translateY(10px)
opacity: 0 !important
.cat-leave-to
transform: translateY(-10px)
@keyframes shake
35%
rotate: -3deg
70%
rotate: 3deg
</style>
調皮的貓貓
搭配調皮的按鈕,貓貓度更高了!ᕕ( ゚ ∀。)ᕗ
儲存
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center justify-center gap-16 border border-gray-300 py-10">
<div class="flex flex-col gap-4 border rounded p-4">
<base-checkbox
v-model="disabled"
label="停用"
/>
</div>
<btn-naughty
ref="btnRef"
:disabled="disabled"
class="cat-btn select-none"
>
<wrapper-cat-ear
:action="currentAction"
main-color="#777"
>
<div
class="h-[3rem] w-[6rem] flex-center border border-[#777] rounded bg-white text-xl font-bold"
v-text="face"
/>
</wrapper-cat-ear>
</btn-naughty>
</div>
</template>
<script setup lang="ts">
import type { ActionName } from '..'
import { refAutoReset, whenever } from '@vueuse/core'
import { computed, ref } from 'vue'
import BaseCheckbox from '../../base-checkbox.vue'
import BtnNaughty from '../../btn-naughty/btn-naughty.vue'
import WrapperCatEar from '../wrapper-cat-ear.vue'
const btnRef = ref<InstanceType<typeof BtnNaughty>>()
const disabled = ref(false)
const action = refAutoReset<`${ActionName}`>('relaxed', 700)
const face = refAutoReset('儲存', 700)
const currentAction = computed(() => {
if (!disabled.value) {
return 'peekaboo'
}
return action.value
})
whenever(() => btnRef.value?.isRunning, () => {
face.value = '˘・ ω ・˘'
action.value = 'displeased'
})
</script>
<style lang="sass">
.cat-btn
transition-duration: 0.4s
&:active
transition-duration: 0.01s
scale: 0.95
.flex-center
display: flex
justify-content: center
align-items: center
</style>
原理
使用 SVG 繪製耳朵,透過 anime.js 控制耳朵動畫。
📚 anime.js
為甚麼選擇 anime.js?
因為最主流的 GSAP 的 SVG 動畫要收費。
最酷炫的 Motion One 使用上有點小問題,而且資料太少。
最後選擇了資料還算齊全且 SVG 支援度不錯的 anime.js 了。
如果大家有更好的套件,拜託請推薦給我。(´▽`ʃ♡ƪ)
原始碼
API
ActionName
Props
interface Props {
/** 目前動作 */
action?: `${ActionName}`;
/** 主要毛色 */
mainColor?: string;
/** 耳朵內部的顏色 */
innerColor?: string;
}