Skip to content

Awkward Slider slider

A very opinionated slider ( ´థ౪థ)

Tech Keywords

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

Usage Examples

Basic Usage

You can customize the sliding logic.

Dragging right is fast, dragging left is slow, and the handle runs away when the mouse gets too close. ԅ(´∀` ԅ)

value: 0
View example source code
vue
<template>
  <div class="w-full flex flex-col gap-4 py-14">
    value: {{ data }}

    <slider-awkward
      v-model="data"
      :physical-data="physicalDataStrategy"
    />
  </div>
</template>

<script setup lang="ts">
import type { ComponentProps } from 'vue-component-type-helpers'
import { ref } from 'vue'
import SliderAwkward from '../slider-awkward.vue'

type Props = ComponentProps<typeof SliderAwkward>

const data = ref(0)

const physicalDataStrategy: Props['physicalData'] = (params) => {
  const { velocity, value, targetValue, pointerPosition } = params

  if (pointerPosition.x < -50) {
    return {
      acceleration: -0.0001,
      maxSpeed: velocity > 0 ? 0.05 : 1,
    }
  }

  if (targetValue <= value) {
    return {
      acceleration: 0.001,
      maxSpeed: 5,
    }
  }

  const sign = Math.sign(targetValue - value)

  return {
    velocity: sign * 9999,
  }
}
</script>

Budget Form

Let the client think it over („ಡωಡ„)

$10,000
View example source code
vue
<template>
  <div class="w-full flex flex-col gap-4 py-14">
    ${{ money }}

    <slider-awkward
      v-model="data"
      :physical-data="physicalDataStrategy"
      :min="1000"
      :step="100"
      :max
    />
  </div>
</template>

<script setup lang="ts">
import type { ComponentProps } from 'vue-component-type-helpers'
import { computed, ref } from 'vue'
import SliderAwkward from '../slider-awkward.vue'

type Props = ComponentProps<typeof SliderAwkward>

const max = 1000000
const data = ref(10000)
const money = computed(() => data.value.toLocaleString())

const stuckThreshold = max / 10 * 8
const physicalDataStrategy: Props['physicalData'] = (params) => {
  const { value, targetValue } = params

  if (value > stuckThreshold) {
    return {
      stuckAt: max,
      escapeValue: max / 3 * 2,
    }
  }

  if (targetValue > value) {
    return {
      velocity: 100,
    }
  }

  return {
    acceleration: -0.0001,
    maxSpeed: 0.01,
  }
}
</script>

How It Works

Movement is based on physics simulation.

Source Code

API

Props

export type PhysicalData = {
  /** px/ms */
  velocity: number;
} | {
  /** px/ms^2 */
  acceleration: number;
  /** 可限制最大速度,僅為正數 */
  maxSpeed?: number;
} | {
  /** 卡在指定數值,限制於 min、max 之間 */
  stuckAt: number;
  /** 滑鼠拉動目標數值之差大於此值即可掙脫,掙脫後 thumb 會移動至此 */
  escapeValue: number;
}

export interface Props {
  modelValue: number;
  disabled?: boolean;
  min?: number;
  max?: number;
  step?: number;
  thumbSize?: number;
  thumbColor?: string;
  trackClass?: string;

  /** 物理參數,可以控制移動方式
   *
   * 如果是函式,則會在每次動畫更新時呼叫
   */
  physicalData?: PhysicalData | ((data: {
    /** 目前移動速度 */
    velocity: number;
    /** 目前數值 */
    value: number;
    /** 目標數值 */
    targetValue: number;
    /** 目前比例 */
    ratio: number;
    /** 滑鼠在 slider 上的比例 */
    pointerRatio: number;
    /** 以 thumb 中心點為 (0, 0) */
    pointerPosition: { x: number; y: number };
  }) => PhysicalData);
}

Emits

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

v0.71.4