Skip to content

脆弱的滑動條 slider

會斷掉 (´・ω・`)

使用範例

基本用法

拉太偏會讓握把斷掉 (´・ω・`)

目前數值:50
查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col gap-4 py-14">
    目前數值:{{ Math.floor(value) }}

    <slider-stubborn
      v-model="value"
      class="w-full"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import SliderStubborn from '../slider-fragile.vue'

const value = ref(50)
</script>

修復與斷開

可以主動修復或斷開握把 (´・ω・`)ノ

查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col gap-4">
    <div class="w-full py-12">
      <slider-stubborn
        ref="slider"
        v-model="value"
        class="w-full"
        durable
        thumb-color="#34c600"
        joint-color="#a0d48e"
      />
    </div>

    <div class="flex gap-4">
      <base-btn
        label="修復"
        class="flex-1"
        @click="sliderRef?.repair()"
      />
      <base-btn
        label="斷開"
        class="flex-1"
        @click="sliderRef?.break()"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import BaseBtn from '../../base-btn.vue'
import SliderStubborn from '../slider-fragile.vue'

const sliderRef = useTemplateRef('slider')
const value = ref(50)
</script>

飲料表單

寧死不屈的甜度 ◝(´・ω・`)◟

飲料類型:
甜度:10 分
冰量:0 分
查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col gap-6 py-6">
    <div class="flex flex-col items-start gap-4 border border-gray-300 rounded-xl p-6">
      <div class="font-bold">
        飲料類型:
      </div>

      <div class="w-full flex flex-wrap justify-between gap-4 whitespace-nowrap">
        <label
          v-for="item, i in list"
          :key="i"
        >
          <input
            v-model="value"
            type="radio"
            :value="item"
          >
          {{ item }}
        </label>
      </div>
    </div>

    <div class="grid grid-cols-6 w-full flex-nowrap items-center gap-8">
      <div class="col-span-2 text-lg">
        甜度:10 分
      </div>

      <div class="col-span-4 pr-4">
        <slider-stubborn
          ref="slider"
          :model-value="10"
          disabled
          :max="10"
        />
      </div>
    </div>

    <div class="grid grid-cols-6 w-full flex-nowrap items-center gap-8">
      <div class="col-span-2 text-lg">
        冰量:{{ iceValue }} 分
      </div>

      <div class="col-span-4 pr-4">
        <slider-stubborn
          v-model="iceValue"
          durable
          :step="3"
          :max="9"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, useTemplateRef, watch } from 'vue'
import SliderStubborn from '../slider-fragile.vue'

const sliderRef = useTemplateRef('slider')

const value = ref('綠茶')
const list = [
  '綠茶',
  '紅茶',
  '蜂蜜奶茶',
  '鮮奶茶',
  '泰奶',
  '水果茶',
  '檸檬茶',
]

const iceValue = ref(0)

watch(value, () => {
  sliderRef.value?.repair()
})
</script>

原理

主要由兩個元件組成:

主體(slider-fragile.vue

slider-fragile.vue 負責處理滑動條的基本邏輯,像是數值的計算、滑鼠在滑動條範圍內的偵測。

它會將計算好的比例 (ratio)、滑鼠互動狀態 (isHeld) 等資訊傳遞給握把元件 slider-fragile-thumb.vue

握把 (slider-fragile-thumb.vue

斷裂與移動效果皆由 slider-fragile-thumb.vue 負責。

當使用者按住握把 (isHeldtrue) 並拖曳時,計算滑鼠指標相對於握把中心的距離。

距離越大,震動效果越大,模擬被用力拉扯的感覺。

有以下狀態:

  • 正常狀態: 握把根據父元件傳來的 ratio 定位在滑動條上。

  • 斷裂: 若拖曳的距離超過了設定的 breakLength 閾值且時間超過 minSecondsToBreakisBroken 會變成 true,表示握把斷裂。

  • 斷裂後:

    • 放開滑鼠時,執行物理模擬:給予握把一個初始速度 (基於放開前滑鼠的移動),然後模擬重力、空氣阻力,並在碰到視窗邊界時反彈,直到速度減緩至停止 (thumbData.sleep)。
    • 斷裂後如果再次按住握把,它會跟隨滑鼠移動。

比較特別的部分是正常狀態與斷裂狀態定位方式不同,正常狀態是 absolute 定位,斷裂狀態是 fixed 定位。

API

Props

interface Props {
  modelValue: number;
  disabled?: boolean;
  min?: number;
  max?: number;
  step?: number;
  thumbSize?: number;
  thumbColor?: string;
  jointColor?: string;
  trackClass?: string;
  durable?: boolean;
  breakLength?: number;
  minSecondsToBreak?: number;
}

Emits

const emit = defineEmits<{
  'update:modelValue': [value: Props['modelValue']];
}>()

Methods

interface Expose {
  repair: () => void;
  break: () => void;
}

v0.38.8