Skip to content

調皮的按鈕 button

停用時會越跑越遠的按鈕。ᕕ( ゚ ∀。)ᕗ

越想嚕他,就跑得越遠,和你家的貓一樣。(._.`)

使用範例

基本用法

當按鈕狀態為 disabled 並觸發 hover、click、key enter 事件時,按鈕會開始亂跑

查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
    <div class="flex flex-col gap-4 border rounded p-4">
      <base-checkbox
        v-model="disabled"
        label="停用按鈕"
      />

      <base-input
        v-model="text"
        placeholder="點擊這裡並使用 tab 將焦點轉移至按鈕後,再按下 Enter 看看"
        class="w-full"
      />
    </div>

    <div class="flex justify-center">
      <btn-naughty
        label="調皮的按鈕"
        class="font-bold"
        :disabled="disabled"
        z-index="30"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import BaseCheckbox from '../../base-checkbox.vue'
import BaseInput from '../../base-input.vue'
import BtnNaughty from '../btn-naughty.vue'

const text = ref('')
const disabled = ref(true)
</script>

移動距離

指定 maxDistanceMultiple 可以設定最大移動距離倍數(自身寬高倍數),若按鈕跑出指定範圍或超出畫面,都會自動回歸原點

查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col items-center justify-center gap-4 border border-gray-300 p-6">
    <base-input
      v-model="maxMultiple"
      type="number"
      outlined
      label="倍數"
    />

    <btn-naughty
      label="按鈕"
      class="font-bold"
      disabled
      :max-distance-multiple="maxMultiple"
      z-index="30"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import BaseInput from '../../base-input.vue'
import BtnNaughty from '../btn-naughty.vue'

const maxMultiple = ref(5)
</script>

呼叫 method

除了元件自身行為外,也可以直接呼叫 method 產生動作

查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col items-center justify-center gap-4 border border-gray-300 p-6">
    <div class="w-full flex gap-4 border rounded p-4">
      <base-btn
        label="移動"
        @click="run"
      />
      <base-btn
        label="返回"
        @click="back"
      />
    </div>

    <btn-naughty
      ref="btn"
      label="按鈕"
      z-index="30"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import BaseBtn from '../../base-btn.vue'
import BtnNaughty from '../btn-naughty.vue'

const btn = ref<InstanceType<typeof BtnNaughty>>()
function run() {
  btn.value?.run()
}
function back() {
  btn.value?.back()
}
</script>

自訂按鈕

可以使用 default slot 自定義按鈕外觀

自定義按鈕
查看範例原始碼
vue
<template>
  <div class="flex justify-center border border-gray-300 p-6">
    <btn-naughty
      disabled
      z-index="30"
    >
      <div class="custom-button">
        自定義按鈕
      </div>
    </btn-naughty>
  </div>
</template>

<script setup lang="ts">
import BtnNaughty from '../btn-naughty.vue'
</script>

<style scoped lang="sass">
.custom-button
  background: #ff8345
  color: white
  padding: 0.5rem 1.5rem
  border-radius: 999rem
</style>

自訂拓印

你說拓印能不能自定義?可以啦,哪次不可以了。

使用 rubbing slot,自訂按鈕拓印內容

啪!跑了
自定義按鈕
查看範例原始碼
vue
<template>
  <div class="flex justify-center border border-gray-300 p-6">
    <btn-naughty
      disabled
      z-index="30"
    >
      <template #rubbing>
        <div class="rubbing">
          啪!跑了
        </div>
      </template>

      <template #default>
        <div class="btn">
          自定義按鈕
        </div>
      </template>
    </btn-naughty>
  </div>
</template>

<script setup lang="ts">
import BtnNaughty from '../btn-naughty.vue'
</script>

<style scoped lang="sass">
.btn
  padding: 0.5rem 1.5rem
  background: #26A69A
  border-radius: 999rem
  font-weight: bold
  color: white
  cursor: pointer

.rubbing
  padding: 0.5rem 1.5rem
  background: #FEFEFE
  border-radius: 999rem
  border: 1px dashed #777
  text-align: center
</style>

Slot Props

使用 slot prop 可以玩出更多花樣

😗
點此中獎
查看範例原始碼
vue
<template>
  <div class="flex justify-center border border-gray-300 p-6">
    <btn-naughty
      disabled
      z-index="30"
    >
      <template #rubbing="{ isRunning }">
        <div class="rubbing">
          {{ isRunning ? '😜' : '😗' }}
        </div>
      </template>

      <template #default="{ isRunning }">
        <div class="btn">
          {{ isRunning ? '點不到咧' : '點此中獎' }}
        </div>
      </template>
    </btn-naughty>
  </div>
</template>

<script setup lang="ts">
import BtnNaughty from '../btn-naughty.vue'
</script>

<style scoped lang="sass">
.btn
  padding: 0.5rem 1.5rem
  background: #26A69A
  border-radius: 999rem
  font-weight: bold
  color: white
  font-size: 1.25rem
  cursor: pointer

.rubbing
  padding: 0.5rem 1.5rem
  background: #FEFEFE
  border-radius: 999rem
  border: 1px dashed #777
  text-align: center
  font-size: 1.25rem
</style>

原理

滑鼠碰觸按鈕時,計算滑鼠位置到按鈕中心的單位向量,並以此向量為基準,移動一個按鈕尺寸的距離。

如果按鈕移動到畫面外,則會自動返回原點,使用 IntersectionObserver 實作。

📚 甚麼是 IntersectionObserver

注意!Σ(ˊДˋ;)

請不要將 overflow 設定為 hidden,否則按鈕一移動就會啪沒了,消失的無影無蹤。

原始碼

API

Props

interface Props {
  /** 按鈕內文字 */
  label?: string;
  /** 是否停用 */
  disabled?: boolean;
  /** 同 CSS z-index */
  zIndex?: number | string;
  /** 最大移動距離,為按鈕尺寸倍數 */
  maxDistanceMultiple?: number;
  /** 同 html tabindex */
  tabindex?: number | string;
}

Emits

const emit = defineEmits<{
  (e: 'click'): void;
  /** 開始移動時 */
  (e: 'run'): void;
  /** 開始返回時 */
  (e: 'back'): void;
}>()

Methods

defineExpose({
  /** 移動 */
  run,
  /** 返回原點 */
  back,
  /** 是否正在移動 */
  isRunning,
})

Slots

defineSlots<{
  /** 按鈕 */
  default?: (props: { isRunning: boolean }) => unknown;
  /** 拓印 */
  rubbing?: (props: { isRunning: boolean }) => unknown;
}>()

v0.21.2