主動的開關 toggle
停用時切換狀態會和你唱反調。( ´థ౪థ)
靈感來自 Useless machine,這個小廢物可是 Maker 的浪漫。(´,,•ω•,,)
至於為甚麼要用貓手,因為貓手是我想的到最欠揍的小手手。ヾ(◍'౪`◍)ノ゙
使用範例
基本用法
開關停用時,切換開關會被貓貓手切回來。(◜௰◝)y
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
<base-checkbox
v-model="disabled"
label="停用開關"
class="border rounded p-4"
/>
<div class="flex flex-1 items-center justify-center">
<toggle-proactive
v-model="value"
:disabled="disabled"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BaseCheckbox from '../../base-checkbox.vue'
import ToggleProactive from '../toggle-proactive.vue'
const disabled = ref(false)
const value = ref(false)
</script>
元件參數
樣式可隨意調整
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center gap-10 border border-gray-300 p-8">
<toggle-proactive
:model-value="false"
disabled
size="3rem"
fur-color="#DFC57B"
pad-color="#FFF"
/>
<toggle-proactive
:model-value="true"
disabled
size="6rem"
track-inactive-class="bg-red-400"
track-active-class="bg-[#DFDFDF]"
fur-color="#8D6F64"
pad-color="#000"
/>
<toggle-proactive
:model-value="false"
disabled
size="4rem"
track-active-class="bg-[#7DDAEA]"
fur-color="#F3F2F2"
/>
</div>
</template>
<script setup lang="ts">
import ToggleProactive from '../toggle-proactive.vue'
</script>
使用須知
讓使用者心甘情願(?閱讀須知。(´,,•ω•,,)
閱讀率 100% 時才能切換開關,否則會被強制回復。
🐟 鱈魚使用須知
📌 重要聲明
本指南適用於任何與鱈魚相關的活動,例如食用、觀賞、聊天、或試圖與其建立深厚友誼(不建議)。
🍽️ 食用須知
- 請確保鱈魚已煮熟,除非你是北極熊。
- 如果你發現鱈魚在盤子上對你微笑,請確認你沒有嗑藥。
- 鱈魚富含不可名狀物質,吃多了可能出現幻覺。
🐠 觀賞須知
- 鱈魚外觀樸素,請勿種族歧視。
- 請勿在水族館對著鱈魚說「你好肥」,牠們也有自尊心。
💬 與鱈魚溝通須知
- 鱈魚不會講話,請不要對牠進行長篇演講。
- 如果鱈魚對你點頭,請不要高興得太早,牠可能只是因為水流晃動。
- 與鱈魚進行心靈溝通時,請確保你沒有餓過頭導致出現幻覺。
🚨 禁忌事項
- 請勿將鱈魚放入洗衣機,可能也洗不乾淨。
- 請勿將鱈魚作為武器使用,除非已事先凍成冰塊。
- 請勿遛鱈魚,因為他不會走路
🎉 結語
請以尊重與幽默的態度對待鱈魚,如有任何不滿,請記得它只是一隻魚
歡迎提出 MR 補充以上須知
UI:「這裡不應該用 toggle 吧! Σ(ˊДˋ;)」
查看範例原始碼
vue
<template>
<div class="relative w-full flex flex-center flex-col gap-4 border border-gray-300 p-6">
<div class="h-[40vh] overflow-y-auto border rounded-xl p-4">
<h1>🐟 鱈魚使用須知</h1>
<h2 :ref="titleRefList.set">
📌 重要聲明
</h2>
<p>本指南適用於任何與鱈魚相關的活動,例如食用、觀賞、聊天、或試圖與其建立深厚友誼(不建議)。</p>
<h2 :ref="titleRefList.set">
🍽️ 食用須知
</h2>
<ul>
<li>請確保鱈魚已煮熟,除非你是北極熊。</li>
<li>如果你發現鱈魚在盤子上對你微笑,請確認你沒有嗑藥。</li>
<li>鱈魚富含不可名狀物質,吃多了可能出現幻覺。</li>
</ul>
<h2 :ref="titleRefList.set">
🐠 觀賞須知
</h2>
<ul>
<li>鱈魚外觀樸素,請勿種族歧視。</li>
<li>請勿在水族館對著鱈魚說「你好肥」,牠們也有自尊心。</li>
</ul>
<h2 :ref="titleRefList.set">
💬 與鱈魚溝通須知
</h2>
<ul>
<li>鱈魚不會講話,請不要對牠進行長篇演講。</li>
<li>如果鱈魚對你點頭,請不要高興得太早,牠可能只是因為水流晃動。</li>
<li>與鱈魚進行心靈溝通時,請確保你沒有餓過頭導致出現幻覺。</li>
</ul>
<h2 :ref="titleRefList.set">
🚨 禁忌事項
</h2>
<ol>
<li>請勿將鱈魚放入洗衣機,可能也洗不乾淨。</li>
<li>請勿將鱈魚作為武器使用,除非已事先凍成冰塊。</li>
<li>請勿遛鱈魚,因為他不會走路</li>
</ol>
<h2 :ref="titleRefList.set">
🎉 結語
</h2>
<p>請以尊重與幽默的態度對待鱈魚,如有任何不滿,請記得它只是一隻魚</p>
<p>歡迎提出 MR 補充以上須知</p>
</div>
閱讀率:{{ readRate.toFixed(1) }}%
<label class="w-full flex-center cursor-pointer gap-6 border rounded-xl px-8 py-4 text-lg">
我已詳閱以上須知
<toggle-proactive
v-model="value"
:disabled
size="2rem"
/>
</label>
<transition name="opacity">
<div
v-if="value"
class="absolute inset-0 z-[40] flex flex-col items-center justify-center gap-6 rounded-xl bg-[#c7f6ff] bg-opacity-90"
>
<span class="text-xl tracking-wide">
感謝您的閱讀!(*´∀`)~♥
</span>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import type { ComputedRef } from 'vue'
import { useTemplateRefsList } from '@vueuse/core'
import { map, pipe } from 'remeda'
import { computed, onMounted, ref } from 'vue'
import { useElementVisibilityTime } from '../../../composables/use-element-visibility-time'
import ToggleProactive from '../toggle-proactive.vue'
const titleRefList = useTemplateRefsList<HTMLElement>()
const timeList = ref<ComputedRef<number>[]>([])
onMounted(() => {
timeList.value = titleRefList.value.map((el) => {
const { totalVisibleTime } = useElementVisibilityTime(el)
return totalVisibleTime
})
})
/** 最小閱讀時間 */
const MIN_READ_MS = 1000
/** 閱讀率 */
const readRate = computed(() => pipe(
timeList.value,
map((time) => time.value > MIN_READ_MS ? MIN_READ_MS : time.value),
(timeList) => {
const total = timeList.reduce((acc, time) => acc + time, 0)
if (total === 0)
return 0
return total / (MIN_READ_MS * timeList.length) * 100
},
))
const disabled = computed(() => readRate.value < 100)
const value = ref(false)
</script>
<style lang="sass" scoped>
.opacity-enter-active, .opacity-leave-active
transition-duration: 0.4s
.opacity-enter-from, .opacity-leave-to
opacity: 0 !important
</style>
不可能的事
可愛又不失禮貌地打槍客戶。ヾ(◍'౪`◍)ノ゙
查看範例原始碼
vue
<template>
<div class="w-full flex-center border border-gray-300 p-10">
<div class="flex flex-col gap-4">
<label
v-for="state in stateList"
:key="state.label"
class="flex cursor-pointer items-center justify-end gap-5"
>
<div class="text-2xl">
{{ state.label }}
</div>
<toggle-proactive
ref="toggleRefList"
v-model="state.value"
v-bind="colorData"
size="3.5rem"
/>
</label>
</div>
</div>
</template>
<script setup lang="ts">
import { useCycleList } from '@vueuse/core'
import { pipe, reduce, sample } from 'remeda'
import { computed, ref, watch } from 'vue'
import ToggleProactive from '../toggle-proactive.vue'
interface State {
value: boolean;
label: string;
}
type Toggle = InstanceType<typeof ToggleProactive>
const toggleRefList = ref<Toggle[]>()
const {
state: colorData,
next: nextColor,
} = useCycleList([
{
furColor: '#7DDAEA',
padColor: '#000',
},
{
furColor: '#FAFAFA',
padColor: '#FFA5A5',
},
{
furColor: '#DFC57B',
padColor: '#000',
},
{
furColor: '#8D6F64',
padColor: '#FFA5A5',
},
{
furColor: '#444',
padColor: '#FFA5A5',
},
{
furColor: '#F3F2F2',
padColor: '#000',
},
])
const stateList = ref<State[]>([
{
label: '要快',
value: false,
},
{
label: '要好',
value: false,
},
{
label: '要便宜',
value: false,
},
])
const booleanList = computed(
() => stateList.value.map((state) => state.value),
)
watch(booleanList, (value, oldValue) => {
const allTrue = value.every((v) => v)
if (!allTrue) {
return
}
const targetIndex = pipe(
oldValue,
/** 排除最後一個切換的開關 */
reduce(
(acc: number[], boolValue, i) => boolValue ? [...acc, i] : acc,
[],
),
sample(1),
([i]) => i ?? 0,
)
nextColor()
toggleRefList.value?.[targetIndex]?.toggle()
})
</script>
原理
注意!Σ(ˊДˋ;)
請不要將 overflow 設定為 hidden,否則貓貓手會被狠心切割
利用 anime.js 製作 svg 動畫。
裡面有個小細節,就是貓手一開始藏在 toggle 背後(手臂與手肘都在背後),播放切換動畫時,手臂一樣在 toggle 背後,但是手肘會伸到 toggle 前面。
SVG 內的物件為甚麼有辦法忽然變換堆疊順序?大家來猜猜看如何實現這個效果。(´,,•ω•,,)
賣個關子,想知道的話可以看看原始碼或留言讓我知道你的猜想。( ´ ▽ ` )ノ
原始碼
API
Props
interface Props {
modelValue: boolean;
disabled?: boolean;
/** @default '4rem' */
size?: string;
/** @default 'rounded-full' */
trackClass?: string;
/** @default 'bg-[#DFDFDF]' */
trackInactiveClass?: string;
/** @default 'bg-green-500' */
trackActiveClass?: string;
/** @default 'bg-white' */
thumbClass?: string;
/** @default '' */
thumbInactiveClass?: string;
/** @default '' */
thumbActiveClass?: string;
/** @default '#444' */
furColor?: string;
/** @default '#FFA5A5' */
padColor?: string;
}
Emits
interface Emits {
'update:modelValue': [value: boolean];
}
Methods
interface Expose {
/** 觸發切換動畫 */
toggle: () => Promise<void>;
}