代码演示
如何引入
<script setup lang="ts">
import { Slider } from '@fibrous/semi-ui-vue';
</script>
基本用法
基本滑动条。当 range 为 true 时,支持两侧滑动。当 disabled 为 true 时,滑块处于不可用状态。
代码
<script setup lang="ts">
import { Slider } from '@fibrous/semi-ui-vue';
</script>
<template>
<div>
<div>
<div>默认</div>
<Slider aria-label="默认滑动输入条" showBoundary />
</div>
<br />
<br />
<div>
<div>范围</div>
<Slider aria-label="范围滑动输入条" :defaultValue="[20, 60]" range />
</div>
<br />
<br />
<div>
<div>禁用</div>
<Slider aria-label="禁用滑动输入条" :defaultValue="40" disabled />
</div>
</div>
</template>带输入框的
滑动条的滑块和输入框组件保持同步。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { InputNumber, Slider } from '@fibrous/semi-ui-vue';
const value = ref(10);
const getSliderValue = (newValue: number | string | number[]) => {
if (isNaN(Number(newValue))) {
return;
}
value.value = Number(newValue);
};
</script>
<template>
<div>
<div :style="{ width: '320px', marginRight: '15px' }">
<Slider aria-label="数值" :modelValue="value" :step="1" @change="getSliderValue" />
</div>
<InputNumber aria-label="数值" :max="100" :min="0" :modelValue="value" :style="{ width: '100px' }" @change="getSliderValue" />
</div>
</template>自定义提示
使用 tipFormatter 可以设置 Tooltip 的显示格式,传入 null 则隐藏 Tooltip。getAriaValueText 用于给滑块的当前值提供一个用户友好的名称,对屏幕阅读器用户很重要。
提示需要节点内容时使用 #tip 插槽,作用域参数 value 为 tipFormatter 的结果。
代码
<script setup lang="ts">
import { Slider } from '@fibrous/semi-ui-vue';
const formatPercent = (value: number) => `${value}%`;
</script>
<template>
<div>
<Slider aria-label="带提示的滑块" :getAriaValueText="formatPercent" :tipFormatter="formatPercent" />
<br />
<br />
<Slider aria-label="不带提示的滑块" :tipFormatter="null" />
</div>
</template>带标签的
使用 marks 属性标注滑块的刻度,使用 modelValue / defaultValue 指定滑块位置。刻度标签需要节点内容时使用 #mark 插槽。
代码
<script setup lang="ts">
import { Slider } from '@fibrous/semi-ui-vue';
const stepTenMarks = { 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50', 100: '100' };
const stepTenthMarks = { 0.1: '0.1', 0.2: '0.2', 0.3: '0.3', 0.4: '0.4', 0.5: '0.5' };
const celsiusMarks = { 20: '20°C', 40: '40°C' };
const formatCelsius = (value: number) => `${value}°C`;
</script>
<template>
<div>
<div id="slider-marks-step-10">step=10</div>
<Slider aria-labelledby="slider-marks-step-10" :defaultValue="[10, 100]" :marks="stepTenMarks" range :step="10" />
<br />
<br />
<div id="slider-marks-step-01">step=0.1</div>
<Slider aria-labelledby="slider-marks-step-01" :defaultValue="[0.1, 0.5]" :marks="stepTenthMarks" :max="1" :min="0" range :step="0.1" />
<br />
<br />
<div id="slider-marks-marks">Marks</div>
<Slider aria-labelledby="slider-marks-marks" :defaultValue="[0, 100]" :getAriaValueText="formatCelsius" :marks="celsiusMarks" range :tipFormatter="formatCelsius" />
<br />
<br />
<div id="slider-marks-included">Included</div>
<Slider aria-labelledby="slider-marks-included" :defaultValue="[0, 100]" :getAriaValueText="formatCelsius" :included="false" :marks="celsiusMarks" range :tipFormatter="formatCelsius" />
</div>
</template>分段背景
通过使用 linear-gradient 及 railStyle ,配合 @change 可以实现动态的分段背景效果。
代码
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Slider } from '@fibrous/semi-ui-vue';
const range = [10, 100];
const getRailStyle = (value: number[]) => {
// transparent lets the middle segment show the track color
const color = ['var(--semi-color-danger)', 'transparent', 'var(--semi-color-success)'];
const gradientPos = value.map((val) => Number(((val - range[0]) / (range[1] - range[0])).toFixed(2)) * 100);
return {
background: `linear-gradient(to right, ${color[0]} ${gradientPos[0]}%, ${color[1]} ${gradientPos[0]}%, ${color[1]} ${gradientPos[1]}%, ${color[2]} ${gradientPos[1]}%)`,
};
};
const value = ref([20, 60]);
const changeValue = (newValue: number | number[]) => {
if (Array.isArray(newValue)) {
value.value = newValue;
}
};
const railStyle = computed(() => getRailStyle(value.value));
</script>
<template>
<Slider aria-label="Value range" :max="range[1]" :min="range[0]" :modelValue="value" :railStyle="railStyle" range @change="changeValue" />
</template>受控组件
滑块位置即 Slider 的值由 modelValue 控制,配合 v-model 使用。只传 :modelValue 而不回写时,拖动、点击与方向键仅触发 change 通知,滑块不移动。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Slider } from '@fibrous/semi-ui-vue';
const value = ref(10);
const changeValue = () => {
value.value += 10;
};
</script>
<template>
<div>
<Button :style="{ marginRight: '20px' }" @click="changeValue">点击改变value值</Button>
<br />
<br />
<Slider aria-label="受控滑块" :modelValue="value" />
</div>
</template>垂直
代码
<script setup lang="ts">
import { Slider } from '@fibrous/semi-ui-vue';
const boxStyle = {
height: '300px',
marginLeft: '30px',
marginTop: '10px',
paddingRight: '30px',
display: 'inline-block',
};
const marks = { 20: '20°C', 40: '40°C' };
</script>
<template>
<div>
<div :style="boxStyle">
<Slider aria-label="Vertical slider" vertical />
</div>
<div :style="boxStyle">
<Slider aria-label="Reversed vertical slider" vertical verticalReverse />
</div>
<div :style="boxStyle">
<Slider aria-label="Vertical range slider" :defaultValue="[20, 60]" range vertical />
</div>
<div :style="boxStyle">
<Slider aria-label="Reversed vertical range slider" :defaultValue="[20, 60]" range vertical verticalReverse />
</div>
<div :style="boxStyle">
<Slider aria-label="Vertical range slider with marks" :defaultValue="[20, 60]" :marks="marks" range :step="10" vertical />
</div>
<div :style="boxStyle">
<Slider aria-label="Reversed vertical range slider with marks" :defaultValue="[20, 60]" :marks="marks" range :step="10" vertical verticalReverse />
</div>
</div>
</template>滑块带圆点
代码
<script setup lang="ts">
import { Slider } from '@fibrous/semi-ui-vue';
</script>
<template>
<div>
<div>
<div id="slider-handle-dot-default">Default</div>
<Slider aria-labelledby="slider-handle-dot-default" :handleDot="{ size: '4px', color: 'blue' }" showBoundary />
</div>
<br />
<br />
<div>
<div id="slider-handle-dot-range">Range</div>
<Slider
aria-labelledby="slider-handle-dot-range"
:defaultValue="[20, 60]"
:handleDot="[
{ size: '4px', color: 'blue' },
{ size: '4px', color: 'pink' },
]"
range
/>
</div>
</div>
</template>API参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| modelValue | 当前取值,配合 v-model 使用;只传不回写时拖动、点击与方向键仅触发通知,滑块不移动。变回 undefined 时回到 defaultValue,没有则回到 min,且不触发 afterChange。 | number | number[] | undefined |
| defaultValue | 非受控时的初始取值。 | number | number[] | semiGlobalDefault("Slider", "defaultValue") |
| disabled | 滑块是否禁用。 | boolean | semiDefault("Slider", "disabled", false) |
| included | marks 不为空对象时有效,true 表示刻度为包含关系,false 表示并列。 | boolean | semiDefault("Slider", "included", true) |
| marks | 刻度,key 为 number 且取值在闭区间 [min, max] 内,值为字符串或数字;节点内容使用 mark 插槽。 | TypeRecord<number, string | number> | semiGlobalDefault("Slider", "marks") |
| max | 最大值。 | number | semiDefault("Slider", "max", 100) |
| min | 最小值。 | number | semiDefault("Slider", "min", 0) |
| range | 是否支持两端同时可滑动。 | boolean | semiDefault("Slider", "range", false) |
| step | 步长,从 min 起计算。 | number | semiDefault("Slider", "step", 1) |
| tipFormatter | 设置提示的展示格式,每个手柄单独调用,参数为该手柄的值;默认显示当前取值,null 隐藏提示。 | Type((value: number) => string | number) | null | undefined |
| tooltipVisible | 是否始终显示提示。 | boolean | semiGlobalDefault("Slider", "tooltipVisible") |
| tooltipOnMark | 刻度上是否带有提示。 | boolean | semiDefault("Slider", "tooltipOnMark", false) |
| showMarkLabel | 是否显示刻度标签。 | boolean | semiDefault("Slider", "showMarkLabel", true) |
| showArrow | 提示是否带箭头。 | boolean | semiDefault("Slider", "showArrow", true) |
| showBoundary | 是否在悬停时展示最大值与最小值。 | boolean | semiDefault("Slider", "showBoundary", false) |
| vertical | 是否为垂直方向。 | boolean | semiDefault("Slider", "vertical", false) |
| verticalReverse | 反转垂直方向,即上大下小。 | boolean | semiDefault("Slider", "verticalReverse", false) |
| railStyle | 滑块轨道的样式。 | CSSProperties | semiGlobalDefault("Slider", "railStyle") |
| handleDot | 滑块上的圆点,单个滑块传一个对象,range 时按手柄传数组。 | HandleDot | HandleDot[] | semiGlobalDefault("Slider", "handleDot") |
| id | 落在第一个手柄的 input 上,用于 label for 关联,点击 label 会聚焦该手柄。 | string | undefined |
| ariaLabel | 手柄的可访问名称;range 模式下两个手柄在其后分别追加语言包中的起点、终点。 | string | semiGlobalDefault("Slider", "ariaLabel") |
| ariaLabelledby | 引用标签元素的 id,作为手柄的可访问名称;range 模式下每个手柄再加上起点或终点。 | string | semiGlobalDefault("Slider", "ariaLabelledby") |
| ariaValuetext | 为当前值提供用户友好的名称。 | string | semiGlobalDefault("Slider", "ariaValuetext") |
| getAriaValueText | 为当前值提供用户友好的名称,参数为滑块的值与滑块的顺序。 | Type((value: number, index?: number | undefined) => string) | undefined |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 值变化时触发,供 v-model 回写。 | [value: number | number[]] |
| change | 值变化时触发,参数为当前取值。 | [value: number | number[]] |
| afterChange | 值变化完成后触发,参数为当前取值;受控值被改回 undefined 时不触发。 | [value: number | number[]] |
| mouseUp | 拖动滑块的指针(鼠标、触摸、触控笔)松开或被取消时,以及取值键松开时触发。 | [event: KeyboardEvent | PointerEvent] |
| focus | 任一手柄获得焦点时触发。 | [event: FocusEvent] |
| blur | 焦点离开整个 Slider 时触发,在两个手柄之间切换不触发。 | [event: FocusEvent] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| mark | 自定义刻度标签,也用于 tooltipOnMark 的提示;作用域参数为刻度的 value 与 label。 | SliderMarkSlotProps |
| tip | 自定义手柄提示的内容;value 为 tipFormatter 的结果,handle 为 min 或 max。 | SliderTipSlotProps |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
class、style 与其余原生属性经 attrs 落在 .semi-slider-wrapper 上。aria-describedby、aria-invalid、aria-errormessage、name 与 @keydown 落在每个手柄的原生 input 上;tabindex、aria-required 不输出。name 在 range 模式下两个 input 同名,依次提交两个值。
组件模板 ref 的 $el 在水平模式下是外层 .semi-slider 元素,垂直模式下是 .semi-slider-wrapper。
RTL/LTR
- Slider 的水平方向支持 RTL,可通过 ConfigProvider 配置
direction="rtl"启用。 - RTL 模式下,Slider 水平方向的最小值在右侧,最大值在左侧,滑块拖动方向与 LTR 相反。
- 垂直方向(
vertical)不受 RTL 影响。 - RTL 模式下,键盘方向键的行为会镜像:
左箭头增加值,右箭头减少值。
Accessibility
ARIA
- 每个滑块内有一个视觉隐藏的原生
<input type="range">,它是真正的焦点和表单控件(隐式 role 为slider),可见的滑块只负责绘制和拖动。查询控件时请使用getByRole('slider')或input[type="range"],不要查询.semi-slider-handle。 - 当前值、最小值、最大值由 input 的
value、min、max提供给辅助技术。range模式下两个 input 的min、max都是整个滑条的范围,一个滑块移动到另一个滑块处即停止。 - 当 Slider 为纵向时,input 带
aria-orientation="vertical",并以writing-mode: vertical-lr呈现纵向。 - 当值不容易被理解时,支持通过 API
aria-valuetext传递一个字符串使其更友好。也可以通过 APIgetAriaValueText(value, index)方法得到aria-valuetext的值。 - 支持通过 API
aria-label或者aria-labelledby确定 slider 的标签,也可以用<label for>指向id:id落在第一个滑块的 input 上,点击 label 会聚焦该滑块。 - 禁用时(
disabled)不会用语言包文案覆盖手柄的名称:禁用状态由原生disabled表达,aria-label、aria-labelledby与<label for>给出的名称保持不变;没有任何命名时,禁用的手柄和可用的手柄一样没有名称。 range模式下两个滑块各有自己的名称:aria-label之后追加语言包中的「起点」「终点」(如价格 起点、价格 终点);使用aria-labelledby时,每个滑块的名称是该标签加上「起点」或「终点」;都没有传时名称就是「起点」「终点」。外层 wrapper 没有角色,不再带aria-label。- 在 Form 中使用时,Form 注入的
id、aria-labelledby、aria-describedby、aria-invalid、aria-errormessage落在 input 上;aria-required不输出,因为 slider 角色不支持该属性。 - 传入
name后,Slider 可以随原生<form>提交:单值提交一个值,range模式下两个 input 使用同一个name,依次提交两个值(FormData.getAll(name)得到[最小值, 最大值])。禁用时与其他原生控件一样不参与提交。
键盘和焦点
- 方向键由组件处理,各浏览器行为一致;
Home、End、Page Up、Page Down由原生 range 处理。滑块可获取焦点,键盘聚焦时在可见滑块上绘制聚焦轮廓,并展示当前滑块的提示信息;用指针按下滑块时焦点同样落到该滑块,但不显示聚焦轮廓。 onKeyDown(Vue 为onKeydown)绑定在手柄的 input 上,每个按键都会先调用它再由滑块处理;取值键(方向键、Home、End、Page Up、Page Down)默认不冒泡到祖先元素,在回调中调用preventDefault可以拦截该键,滑块不再处理它。- 当用户使用
range属性时,可以使用Tab及Shift+Tab切换左右两个滑块的焦点。 - 方向键与滑块在屏幕上的移动方向一致:水平时
右箭头、上箭头增加值,左箭头、下箭头减少值(RTL 下左右镜像);纵向时值从上往下增大,下箭头增加值、上箭头减少值;verticalReverse时值从下往上增大,上箭头增加值、下箭头减少值;纵向时右箭头始终增加值,左箭头始终减少值。 - 若想要滑块高于步长的变化量,
Page Up、Page Down每次变化整个范围的 10%(按步长取整):- Windows 用户:
Page Up用于增加,Page Down用于减少; - Mac 用户使用:
Fn+上箭头用于增加,Fn+下箭头用于减少; - 当用户使用
range属性时,前一个滑块的Page Up(Fn+上箭头) 键仅支持到与后一个滑块相遇,重合后再对前一个滑块使用 Page Up 键则无响应。后一个滑块同理,相遇后,对Page Down(Fn+下箭头) 键无响应。
- Windows 用户:
- 若想将滑块移动到滑杆的最小值处:
- Windows 用户:
Home; - Mac 用户:
Fn+左箭头; - 当用户使用
range属性时,后一个滑块的Home(Fn+左箭头) 键仅支持到与前一个滑块相遇,重合后再次使用Home(Fn+左箭头) 键无响应。
- Windows 用户:
- 若想将滑块移动到滑杆的最大值处:
- Windows 用户:
End; - Mac 用户:
Fn+右箭头; - 当用户使用
range属性时,前一个滑块的End(Fn+右箭头) 键仅支持到与后一个滑块相遇,重合后再次使用End(Fn+右箭头) 键无响应。
- Windows 用户:
设计变量
聚焦样式说明
Slider 组件有两个与聚焦相关的设计变量,分别对应不同的交互场景:
| 变量名 | CSS 变量 | 触发场景 | 说明 |
|---|---|---|---|
| 圆形按钮轮廓 - 聚焦 | --semi-color-primary-light-active | 键盘导航(Tab 键聚焦) | 滑块内的 input 匹配 :focus-visible 时(.semi-slider-handle:has(> input:focus-visible))在可见滑块上显示 outline,仅在键盘聚焦时显示 |
| 滑动条圆形描边颜色 - 激活态 | --semi-color-focus-border | 鼠标点击/拖动 | 使用 .semi-slider-handle-clicked 类,在鼠标交互时显示 border |
如果你希望在主题配置中修改圆形按钮的聚焦样式,请注意上述两个变量对应不同的交互场景。
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $transition_duration-slider_handle-bg | var(--semi-transition_duration-none) | 滑动条圆形按钮-背景色-动画持续时间 |
| $transition_function-slider_handle-bg | var(--semi-transition_function-easeIn) | 滑动条圆形按钮-背景色-过渡曲线 |
| $transition_delay-slider_handle-bg | var(--semi-transition_delay-none) | 滑动条圆形按钮-背景色-延迟时间 |
| $transform_scale-slider_handle | var(--semi-transform_scale-small) | 滑动条圆形按钮-放大 |
| $color-slider-text-default | var(--semi-color-text-0) | 滑动条文本颜色 - 默认 |
| $color-slider_dot-bg-default | var(--semi-color-white) | 滑动条圆形刻度点颜色 - 默认态 |
| $color-slider_dot-border-active | var(--semi-color-white) | 滑动条圆形刻度点描边颜色 - 按下态 |
| $color-slider_dot-border | var(--semi-color-white) | 滑动条圆形刻度点描边颜色 - 默认态 |
| $color-slider_handle-bg-default | var(--semi-color-white) | 滑动条圆形按钮颜色 - 默认态 |
| $color-slider_handle-bg-hover | var(--semi-color-white) | 滑动条圆形按钮颜色 - 悬停态 |
| $color-slider_handle-border-focus | var(--semi-color-focus-border) | 滑动条圆形描边颜色 - 激活态 |
| $color-slider_handle_disabled-border-hover | var(--semi-color-white) | 禁用滑动条圆形描边颜色 - 悬停态 |
| $color-slider_handle_disabled-border | var(--semi-color-border) | 禁用滑动条圆形描边颜色 - 默认态 |
| $color-slider_mark-text-default | var(--semi-color-text-2) | 滑动条刻度文字颜色 |
| $color-slider_rail-bg-default | var(--semi-color-fill-0) | 滑动条轨道颜色 - 未填充 |
| $color-slider_rail | rgba(0, 0, 0, 0.65) | |
| $color-slider_track-bg-default | var(--semi-color-primary) | 滑动条轨道颜色 - 已填充 |
| $color-slider_track_disabled-bg | var(--semi-color-primary-disabled) | 禁用滑动条轨道颜色 - 已填充 |
| $color-slider_handle-focus | $color-focus-ring | 圆形按钮轮廓 - 聚焦 |
| $color-slider_handle_dot | var(--semi-color-primary) | 圆形按钮内部圆点颜色 |
| $spacing-slider-paddingX | 13px | 滑动条整体水平内边距 |
| $spacing-slider-paddingY | 0px | 滑动条整体垂直内边距 |
| $spacing-slider-margin | 0px | 滑动条整体外边距 |
| $spacing-slider_rail-margin | 0px | 滑动条轨道外边距 |
| $spacing-slider_rail-padding | 0px | 滑动条轨道内边距 |
| $spacing-slider_rail-top | 14px | 滑动条未填充轨道顶部距离 |
| $spacing-slider_handle-marginTop | 4px | 滑动条圆形按钮顶部外边距 |
| $spacing-slider_track-top | 14px | 滑动条已填充轨道顶部距离 |
| $spacing-slider_tooltip-top | -40px | 滑动条工具提示顶部距离 |
| $spacing-slider_dot-top | 14px | 滑动条圆形刻度点顶部距离 |
| $spacing-slider_marks-top | 23px | 滑动条刻度标签顶部距离 |
| $spacing-slider_marks-left | 0px | 滑动条刻度标签左侧距离 |
| $spacing-slider_boundary-top | 30px | |
| $spacing-slider_boundary_min-left | 0px | |
| $spacing-slider_boundary_max-right | 0px | |
| $spacing-slider_vertical_marks-marginTop | -30px | 垂直滑动条刻度标签顶部外边距 |
| $spacing-slider_vertical_marks-marginLeft | 29px | 垂直滑动条刻度标签左侧外边距 |
| $spacing-slider_vertical_marks-reverse-marginLeft | -26px | 垂直滑动条刻度标签左侧外边距(标签在左侧时) |
| $spacing-slider_vertical_rail-top | 0px | 垂直滑动条轨道顶部距离 |
| $spacing-slider_vertical_handle-marginTop | 0px | 垂直滑动条原型按钮顶部外边距 |
| $spacing-slider_vertical_handle-marginLeft | -10px | 垂直滑动条原型按钮左侧外边距 |
| $spacing-slider_handle-translateY | 0px | 水平滑动条原型按钮垂直偏移量 |
| $spacing-slider_vertical_handle-translateX | 0px | 垂直滑动条原型按钮水平偏移量 |
| $spacing-slider_dot-translateX | 0px | 水平滑动条圆形刻度点水平偏移量 |
| $spacing-slider_vertical_dot-translateY | 0px | 垂直滑动条圆形刻度点垂直偏移量 |
| $radius-slider_rail | var(--semi-border-radius-small) | 滚动条未填充轨道圆角 |
| $radius-slider_track | var(--semi-border-radius-small) | 滚动条已填充轨道圆角 |
| $height-slider_wrapper | 32px | 滚动条容器整体高度 |
| $height-slider_vertical_wrapper | 4px | 垂直滚动条整体宽度 |
| $height-slider_rail | 4px | 滚动条未填充轨道高度 |
| $width-slider_handle | 24px | 滚动条圆形按钮宽度 |
| $width-slider_handle_clicked | 1px | 滚动条圆形按钮按下后描边宽度 |
| $height-slider_track | 4px | 滚动条已填充轨道高度 |
| $width-slider_dot | 4px | 滚动条圆形刻度点宽度 |
| $width-slider_handle_border_disabled | 1px | 禁用滚动条圆形按钮按下后描边宽度 |
| $width-slider_handle-focus | $width-focus-ring | 圆形按钮轮廓 - 聚焦 |
| $width-slider_handle_dot | 4px | 圆形按钮内部圆点宽度 |
| $font-slider_rail-fontSize | 14px | 滚动条轨道文本字号 |
| $font-slider_marks-fontSize | 14px | 滚动条刻度标签字号 |
| $font-slider_rail-lineHeight | 1.5 | 滚动条轨道文本行高倍数 |