Slider
Sliderは、指定された数値の範囲から値を選択するためのコンポーネントです。
<script>
import Slider from '$lib/components/ui/atoms/Slider.svelte';
let max = 100;
let min = 0;
let value = $state(25);
</script>
<div class="flex justify-center w-full">
<Slider {max} {min} bind:value step={1}></Slider>
</div>
プロパティ
Sliderは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
max |
number |
100 |
最大値を指定します。 |
min |
number |
0 |
最小値を指定します。 |
step |
number |
1 |
stepを指定します。 |
value |
number |
0 |
初期値を指定します。 |
trackClass |
string |
0 |
sliderのtrackのクラスを指定します。 |
インストールの手順
以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。
atoms/Slider.svelte
<!--
@component
## 概要
- Sliderは、指定された数値の範囲から値を選択するためのコンポーネントです。
## 機能
- 範囲制限(min, max)とステップ刻み(step)に対応
- キーボード操作(Arrowキー)で値の増減が可能
- カスタムラベル(minContent, maxContent)のスロット対応
## Props
- min: スライダーの最小値を指定します(デフォルト: 0)
- max: スライダーの最大値を指定します(デフォルト: 100)
- step: 値の増減単位を指定します(デフォルト: 1)
- value: スライダーの現在の値(bind:value 可能、デフォルト: 50)
- class: 外部から渡されるクラス名を指定します
- minContent: 最小値のラベルに表示するスロットコンテンツ
- maxContent: 最大値のラベルに表示するスロットコンテンツ
## Usage
```svelte
<Slider min={0} max={100} bind:value={current} step={5}>
{#snippet minContent()}
<span>Low</span>
{/snippet}
{#snippet maxContent()}
<span>High</span>
{/snippet}
</Slider>
```
-->
<script module lang="ts">
import type { Snippet } from 'svelte';
import type { ClassValue } from 'svelte/elements';
import { cva } from 'class-variance-authority';
export const sliderTrackVariants = cva('relative flex items-center min-w-64 w-full h-5');
export const sliderThumbVariants = cva('absolute bg-primary rounded-full outline-none touch-none transition-shadow cursor-pointer focus-visible:ring-2 hover:ring-[0.25rem] focus-visible:ring-offset-2 focus-visible:ring-primary hover:ring-primary/20');
export interface SliderProps {
/** スライダーの最小値 */
min: number;
/** スライダーの最大値 */
max: number;
/** スライダーのステップ値 */
step?: number;
/** 現在の値 */
value?: number;
/** クラス */
class?: ClassValue;
/** スライダーのトラックのクラス名 */
trackClass?: ClassValue;
/** 最小値のsnippet */
minContent?: Snippet<[]>;
/** 最大値のsnippet */
maxContent?: Snippet<[]>;
}
</script>
<script lang="ts">
let { class: className, min = 0, max = 100, step = 1, value = $bindable(0), trackClass, minContent, maxContent }: SliderProps = $props();
let thumbSize = 20;
let trackWidth = $state(0);
let thumbLeft = $derived.by(() => {
const range = max - min;
const ratio = (value - min) / range;
const usable_width = trackWidth - thumbSize;
return ratio * usable_width;
});
function onKeyDown(e: KeyboardEvent) {
if (e.key === 'ArrowRight') {
e.preventDefault();
value = Math.min(value + step, max);
}
else if (e.key === 'ArrowLeft') {
e.preventDefault();
value = Math.max(value - step, min);
}
}
function onPointerDown(e: PointerEvent) {
e.preventDefault();
if (!(e.currentTarget instanceof HTMLElement)) return;
const track = e.currentTarget;
const rect = track.getBoundingClientRect();
function update_value(clientX: number) {
const pos = Math.min(Math.max(clientX - rect.left, 0), rect.width);
const new_percent = pos / rect.width;
const raw_value = min + (max - min) * new_percent;
value = Math.round(raw_value / step) * step;
}
update_value(e.clientX);
function onMove(e: PointerEvent) {
update_value(e.clientX);
}
function onUp() {
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
}
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
}
</script>
<div class={[className, 'flex items-center justify-center w-fit gap-x-2 mx-auto']}>
{@render minContent?.()}
<div class={[sliderTrackVariants(), trackClass]} onpointerdown={onPointerDown} bind:clientWidth={trackWidth}>
<!-- 背景バー -->
<div class="absolute top-1/2 w-full h-2 bg-base-container-muted rounded-full -translate-y-1/2 cursor-pointer"></div>
<!-- アクティブバー -->
<div class="absolute top-1/2 h-2 bg-primary rounded-full -translate-y-1/2 cursor-pointer" style={`width: ${thumbLeft + thumbSize / 2}px;`}></div>
<!-- サム -->
<div class={sliderThumbVariants()} style={`width: ${thumbSize}px; height: ${thumbSize}px; left: ${thumbLeft}px;`} tabindex="0" role="slider" aria-valuemin={min} aria-valuemax={max} aria-valuenow={value} onkeydown={onKeyDown}></div>
</div>
{@render maxContent?.()}
</div>
使い方
<script>
import Slider from '$lib/components/ui/atoms/Slider.svelte';
let max = 100;
let min = 0;
let value = $state(25);
</script>
<div class="flex justify-center w-full">
<Slider {max} {min} bind:value step={1}></Slider>
</div>
サンプル
Default
デフォルトの状態です。
<script>
import Slider from '$lib/components/ui/atoms/Slider.svelte';
let max = 100;
let min = 0;
let value = $state(25);
</script>
<div class="flex justify-center w-full">
<Slider {max} {min} bind:value step={1}></Slider>
</div>
With RangeLabel
最小値と最大値を数値で表示するスライダーです。
<script>
import Slider from '$lib/components/ui/atoms/Slider.svelte';
let max = 100;
let min = 0;
let value = $state(25);
</script>
<div class="flex flex-col gap-2">
<div class="w-full flex justify-center">
<Slider {max} {min} bind:value step={1}>
{#snippet minContent()}
<p class="leading-none text-sm text-base-foreground-muted shrink-0 grow-0">{min}</p>
{/snippet}
{#snippet maxContent()}
<p class="leading-none text-sm text-base-foreground-muted shrink-0 grow-0">{max}</p>
{/snippet}
</Slider>
</div>
<p class="leading-none text-base-foreground-muted">現在の数値:{value}</p>
</div>
With Icon
最小値と最大値をアイコンで表示するスライダーです。
<script>
import Slider from '$lib/components/ui/atoms/Slider.svelte';
import { Sun, SunDim } from '@lucide/svelte';
let max = 100;
let min = 10;
let value = $state(25);
</script>
<div class="flex flex-col gap-2">
<div class="w-full flex justify-center">
<Slider {max} {min} bind:value step={1}>
{#snippet minContent()}
<SunDim class="size-4 text-base-foreground-muted shrink-0 grow-0"></SunDim>
{/snippet}
{#snippet maxContent()}
<Sun class="size-4 text-base-foreground-muted shrink-0 grow-0"></Sun>
{/snippet}
</Slider>
</div>
<p class="leading-none text-base-foreground-muted">現在の数値:{value}</p>
</div>
Step
stepの数値を指定できます。
<script>
import Slider from '$lib/components/ui/atoms/Slider.svelte';
let max = 100;
let min = 0;
let value = $state(30);
</script>
<div class="flex flex-col gap-2">
<div class="w-full flex justify-center">
<Slider {max} {min} step={10} bind:value>
{#snippet minContent()}
<p class="leading-none text-sm text-base-foreground-muted shrink-0 grow-0">{min}</p>
{/snippet}
{#snippet maxContent()}
<p class="leading-none text-sm text-base-foreground-muted shrink-0 grow-0">{max}</p>
{/snippet}
</Slider>
</div>
<p class="leading-none text-base-foreground-muted">現在の数値:{value}</p>
</div>