Skip to content

Every little fish swimming around is a visitor on this page right now, and one of them is you ( ´ ▽ ` )ノ If they get in the way of reading, feel free to turn them off.

Fragile Slider slider ​

Yes, it breaks. (´・ω・`)

Tech Keywords ​

NameDescription
Anime.jsLightweight JavaScript animation library
Pointer EventsDetects pointer movement, clicks, hovers, and more, providing coordinates and target information
Physics SimulationSimulates real-world physics such as gravity, collisions, and velocity
CSS AnimationImplemented with CSS transition and animation
JS AnimationJavaScript-driven animation for more complex, precise control; popular libraries include GSAP and anime.js

Examples ​

Basic Usage ​

Pull it too far and the handle snaps off! (´・ω・`)

value: 50
View Example Source Code
vue
<template>
  <div class="mx-auto max-w-[20rem] w-full flex flex-col gap-4 py-14">
    value: {{ 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>

Repair & Snap ​

You can break it on purpose... or fix it if you’re feeling kind. (´・ω・`)ノ

View Example Source Code
vue
<template>
  <div class="mx-auto max-w-[20rem] 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="t('repair')"
        class="flex-1"
        @click="sliderRef?.repair()"
      />
      <base-btn
        :label="t('break')"
        class="flex-1"
        @click="sliderRef?.break()"
      />
    </div>
  </div>
</template>

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

const sliderRef = useTemplateRef('slider')
const value = ref(50)

const { t } = useI18n()
</script>

Sweetness Preference ​

Unyielding sweetness level! ◝(´・ω・`)◟

Drink Type:
Sweetness:10 points
Ice:0 points
View Example Source Code
vue
<template>
  <div class="mx-auto max-w-[28rem] w-full flex flex-col gap-6 py-20">
    <div class="example-wrap flex flex-col items-start gap-4">
      <div class="font-bold">
        {{ t('drinkType') }}
      </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"
          >
          {{ t(`drinkOptions.${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">
        {{ t('sweetness') }}:10 {{ t('points') }}
      </div>

      <div class="col-span-4 pr-4">
        <slider-fragile
          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">
        {{ t('ice') }}:{{ iceValue }} {{ t('points') }}
      </div>

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

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

const sliderRef = useTemplateRef('slider')

const { t } = useI18n()

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

const iceValue = ref(0)

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

How It Works ​

This slider is made up of two main components:

Main Body (slider-fragile.vue) ​

Handles the core logic—calculating values, detecting mouse interactions, and sending that info (like ratio and isHeld) to the thumb component.

The Thumb (slider-fragile-thumb.vue) ​

This is where all the drama happens: breaking, shaking, and flying off into the void.

When the user clicks and drags the thumb (isHeld is true), we measure how far the mouse is from the thumb’s center.

The farther it gets, the shakier it becomes—like it’s being yanked hard.

Here are its main states:

  • Normal: The thumb is neatly positioned using the ratio from the parent component.
  • Broken: If dragged too far (beyond breakLength) and held long enough (minSecondsToBreak), the thumb snaps. isBroken turns true.
  • After Breaking:
    • When the mouse is released, physics kicks in: it launches with initial velocity (based on your dragging), then falls with gravity, bounces off screen edges, and slows down until it... naps. (thumbData.sleep)
    • You can click and drag it again, and it’ll float around with your mouse.

A quirky detail: while in the normal state, the thumb uses absolute positioning; once broken, it switches to fixed so it can freely roam the screen.

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.92.0