<script setup lang="ts">
import { ref } from 'vue'
import { RangeSlider } from '@hina-ui/vue'
const price = ref<[number, number]>([120, 480])
</script>
<template>
<RangeSlider v-model="price" :max="1000" :step="10" aria-label="价格区间" class="w-64" />
</template>
vue
用法
import { RangeSlider } from '@hina-ui/vue'
ts
范围滑块有两个拇指,v-model 绑定 [起点, 终点] 的二元数组,填充覆盖两个拇指之间的一段。轨道、拇指、取值标签与 Slider 相同。未声明的属性都会传给根元素,应当用 aria-label 或者 aria-labelledby 给整组命名;两个拇指各自带有「最小值」「最大值」的名称。
当前值:2010 至 2020
<script setup lang="ts">
import { ref } from 'vue'
import { RangeSlider, Stack, Text } from '@hina-ui/vue'
const year = ref<[number, number]>([2010, 2020])
</script>
<template>
<Stack gap="sm" class="w-64">
<RangeSlider v-model="year" :min="1990" :max="2026" aria-label="年份" />
<Text size="sm" tone="muted">当前值:{{ year[0] }} 至 {{ year[1] }}</Text>
</Stack>
</template>
vue
示例
方向
dir 支持 ltr 和 rtl,优先于 Reka 的全局方向配置;未配置时继承外层元素的 dir。切换方向会同步更新轨道、拇指、刻度点与刻度文字。
RTL 下最小值在右、最大值在左;右方向键减小数值,左方向键增大数值,Home / End 仍分别跳到最小值与最大值。
LTR
RTL
<script setup lang="ts">
import { ref } from 'vue'
import { RangeSlider, Stack, Text } from '@hina-ui/vue'
const value = ref<[number, number]>([25, 75])
const marks = [0, 25, 50, 75, 100].map(value => ({ value, label: String(value) }))
</script>
<template>
<Stack gap="lg" class="w-72 max-w-full">
<Stack v-for="dir in ['ltr', 'rtl'] as const" :key="dir" gap="xs">
<Text size="sm" tone="muted">{{ dir.toUpperCase() }}</Text>
<RangeSlider
v-model="value"
:dir="dir"
:marks="marks"
:step="5"
:aria-label="'区间 (' + dir.toUpperCase() + ')'"
/>
</Stack>
</Stack>
</template>
vue
刻度
marks 与 Slider 相同,step 决定拇指的落点。
<script setup lang="ts">
import { ref } from 'vue'
import { RangeSlider } from '@hina-ui/vue'
const range = ref<[number, number]>([25, 75])
const marks = [
{ value: 0, label: '0' },
{ value: 25 },
{ value: 50, label: '50' },
{ value: 75 },
{ value: 100, label: '100' },
]
</script>
<template>
<RangeSlider v-model="range" :step="25" :marks="marks" aria-label="区间" class="w-64" />
</template>
vue
最小间隔
minSteps 规定两个拇指之间至少相隔的步数,拖动或者按键都不能越过。
两个拇指之间至少相隔四步
<script setup lang="ts">
import { ref } from 'vue'
import { RangeSlider, Stack, Text } from '@hina-ui/vue'
const hours = ref<[number, number]>([9, 18])
</script>
<template>
<Stack gap="sm" class="w-64">
<RangeSlider v-model="hours" :max="24" :min-steps="4" label="always" aria-label="营业时间" />
<Text size="sm" tone="muted">两个拇指之间至少相隔四步</Text>
</Stack>
</template>
vue
取值标签
每个拇指各有一个取值标签,label 与 format 的用法与 Slider 相同。
<script setup lang="ts">
import { ref } from 'vue'
import { RangeSlider, Stack } from '@hina-ui/vue'
const discount = ref<[number, number]>([20, 60])
const hidden = ref<[number, number]>([30, 70])
</script>
<template>
<Stack gap="lg" class="w-64 pt-6">
<RangeSlider
v-model="discount"
label="always"
:format="(v: number) => `${v}%`"
aria-label="折扣区间"
/>
<RangeSlider v-model="hidden" label="none" aria-label="不显示标签" />
</Stack>
</template>
vue
尺寸
<script setup lang="ts">
import { RangeSlider, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack gap="sm" class="w-64">
<RangeSlider size="sm" :model-value="[20, 60]" aria-label="小号" />
<RangeSlider size="md" :model-value="[20, 60]" aria-label="中号" />
<RangeSlider size="lg" :model-value="[20, 60]" aria-label="大号" />
</Stack>
</template>
vue
状态
<script setup lang="ts">
import { RangeSlider } from '@hina-ui/vue'
</script>
<template>
<RangeSlider disabled :model-value="[30, 70]" aria-label="已禁用" class="w-64" />
</template>
vue
在表单中
放进 FormField 后,标签通过 aria-labelledby 关联到整个滑块组,说明与错误信息由字段渲染;校验规则与提交交给 Form。值是二元数组,区间宽度这类规则写在数组层。
<script setup lang="ts">
import { reactive, ref } from 'vue'
import * as v from 'valibot'
import { Button, Form, FormField, RangeSlider, Text } from '@hina-ui/vue'
const schema = v.object({
budget: v.pipe(
v.tuple([v.number(), v.number()]),
v.check(([low, high]) => high - low >= 200, '预算区间至少相差 200 元'),
),
})
const values = reactive({ budget: [300, 400] as [number, number] })
const saved = ref('')
async function save(data: unknown) {
await new Promise(resolve => setTimeout(resolve, 600))
saved.value = JSON.stringify(data)
}
</script>
<template>
<Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
<FormField name="budget" label="预算" description="拖动两端设定区间">
<RangeSlider v-model="values.budget" :min="0" :max="1000" :step="50" />
</FormField>
<Button type="submit" :loading="submitting" class="self-start">保存</Button>
<Text v-if="saved" tone="muted" size="sm">已保存:{{ saved }}</Text>
</Form>
</template>
vue
行为
- 点击轨道时,离点击处更近的拇指跳过去;拖动只移动被按住的拇指,两个拇指不会交叉。
- 键盘 Tab 依次落在两个拇指上,方向键只移动持有焦点的那个。
- 拖动过程中值持续更新,松手时另发一次
commit。
无障碍
- 根元素是
role="group",通过aria-label或者aria-labelledby命名;两个拇指是role="slider",名称取自语言包的「最小值」与「最大值」,并带aria-valuenow、aria-valuemin、aria-valuemax。
API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue | [number, number] | [min, max] | 起点与终点 |
min | number | 0 | 最小值 |
max | number | 100 | 最大值 |
step | number | 1 | 步长 |
dir | 'ltr' | 'rtl' | — | 方向,未指定时继承 |
minSteps | number | 0 | 两个拇指之间至少相隔的步数 |
marks | Array<{ value: number; label?: string }> | — | 刻度 |
label | 'auto' | 'always' | 'none' | 'auto' | 取值标签的显示方式 |
format | (value: number) => string | — | 取值标签的文字 |
size | 'sm' | 'md' | 'lg' | 'md' | 尺寸 |
disabled | boolean | false | 是否禁用 |
class | string | — | 追加至根元素的类名 |
事件
| 事件 | 参数 | 说明 |
|---|---|---|
update:modelValue | value: [number, number] | 值变化时,拖动中持续触发 |
commit | value: [number, number] | 一次拖动或者按键结束时 |