Slider 滑块
在连续或离散的区间内拖出一个值或一段范围。
用法
值恒为数组,单滑块即长度 1;方向键移动一格 step,PageUp 与 PageDown 按 largeStep,Home 与 End 到达端点
组件结构
加粗的是必需部件。
data-scope="slider":root · label · control · track · range · thumb · value-text · tick-group · tick · tick-label · hidden-input
示例
区间选择
两个拇指互为对方的边界、永不交叉,minStepsBetweenThumbs 再为它们之间留出格数;getValueText 把值转换为读屏可朗读的文本
竖向
orientation 换为 vertical 后整条控件收为一块,键盘与拖动的方向随之翻转
禁用与只读
禁用的拇指退出 Tab 序列、值也不再随表单提交;只读仍可聚焦与朗读,只是不可推动
颜色
tone 决定已填轨道与滑块使用哪族颜色,不写时沿用品牌色
尺寸
size 改变轨道厚度与滑块直径,不写即默认中档
文字方向
dir 换为 rtl 后轨道从右向左填充,左右两键的语义随之对调;上下键与 Home、End 不受影响
滑块中的内容
thumb 是一个普通容器,放置什么由作者决定;容纳空间依靠 --xh-slider-thumb-size 撑开直径
轨道刻度
刻度分圆点与文案两层:圆点固定在轨道上、文案排在下方且点击跳转到该值,落入已选区间的刻度分段上色;snapToMarks 使拖动/点击/键盘只落在刻度上
拖动时的值气泡
value-text 挂在 thumb 中即随之移动;推动时由皮肤显示它,气泡中的文字取自作者的格式化函数
已选:¥1,800
离散档位
可选值不必是等距数值:让滑块在档位下标上移动,宿主再把下标映射回自己的取值表,键盘与拖动都只落在档位上
反向
inverted 把 min 放到轨道末端,已选区间从末端画起;竖排时 0 米在上、越往下越深,方向键跟随屏幕方向
整段拖动
draggableRange 让两端拇指之间的轨道可以整段拖动,时间窗宽度不变地平移;按在拇指上仍只推那一个
不填充轨道
取值没有「多少」之分时关掉 trackFill,只留底槽与拇指,免得从一端画起的区间暗示大小
设计指引
何时使用
- 用户关心的是相对位置而不是精确数字(音量、透明度、价格区间)。
- 需要即时看到调整的效果。
何时不用
特性
- 单值与区间共用一套结构,区间时
minStepsBetweenThumbs防止两头交叉。 - 轨道 2px、中性淡底,已选区间品牌色;拇指 12px 白底圆配 2px 品牌描边,不投影,悬停与拖动都不放大,聚焦与拖动画同一道环,收在描边以内;刻度点 8px 白底圆配 2px 描边(走过的取品牌色)。禁用时轨道、区间与拇指描边都退到中性淡底,拇指留白面。粗指针下控件沿交叉轴外扩到 44px 接住落指:外扩量按拇指直径补足,朝标签一侧不越过标签与控件之间的间距,点在标签上不会跳值。
marks绘制刻度,snapToMarks让值吸附到刻度。- 两个回调:拖动途中连续发出,松手时发出一次;持久化使用后者。
getValueText决定读屏读出的内容,不只读数字。inverted把 min 放到轨道末端(横排在行尾、竖排在顶端),已选区间从末端画起;方向键跟随屏幕方向,拇指在屏幕上往哪边挪就按哪个键。插槽里的thumbs[i].percent与range仍按值的位置报,自绘内容反向时自己取1 - percent。draggableRange:多拇指时按住两端拇指之间的轨道拖动,整段区间一起平移、宽度不变,挪到头就停;按在拇指上仍只推那一个,按在区间外照旧抓最近的拇指。只认刻度落点(snapToMarks)时不生效。键盘仍按拇指逐个调整。trackFill关掉后不画已选区间、刻度也不按区间上色,只留底槽与拇指:取值没有「多少」之分(声道平衡、色温中点)时用它,免得从一端画起的区间暗示大小。- 不写结构时按标准结构铺出整套部件(Vue 不写默认插槽、React 不给 children、Web Components 不写部件或只写
root),三端出自同一个结构函数:label给标题文字,控制区里依次是轨道与已选区间、刻度(给了marks才铺)、每个值一个拇指(给了name再带表单影子)。拖动时的值气泡不在其中,要用时手写结构。Web Components 里标题可以写在宿主的直接子节点slot="label"上接管。
组合
- 与数字字段并排,两边同步一个值。
最佳实践
- 两端标出最小与最大值,用户才能知道当前位置。
- 拖动时用值气泡显示当前值,松手后收起。
反模式
- 区间很大却不提供数字输入,拖到精确值几乎不可能。
- 在移动端把滑块做得过细过短。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-slider> |
| Vue 组件 | XhSliderControl XhSliderHiddenInput XhSliderLabel XhSliderRange XhSliderRoot XhSliderThumb XhSliderTickGroup XhSliderTrack XhSliderValueText |
| 组合式函数 | useSlider |
| 状态机 | sliderMachine |
| 皮肤 | @xihan-ui/styles/slider.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
labelled | boolean | 作者渲染了 label 部件时置真,由适配器统计而不是判断标题文字是否有值。 为假时可聚焦部件不把 label 的 id 写进 aria-labelledby:指向未渲染的 id 是无效引用,作者写在控件上的 aria-label 也会被它压住。 | |
ariaLabel | string | 不显示出来的名字,写成 aria-label 落在与字段标签同一个部件上。 放进字段(字段接线)或渲染了 label 部件时名字以它们为准,aria-labelledby 盖过它。 | |
value | number[] | ||
defaultValue | number[] | ||
min | number | ||
max | number | ||
step | number | ||
largeStep | number | PageUp / PageDown 的步长,默认 10 倍 step。 | |
orientation | Orientation | ||
dir | Direction | ||
disabled | boolean | ||
readOnly | boolean | ||
invalid | boolean | ||
required | boolean | 必填:根与标题带 data-required(标题据此画星号)。拇指是 role=slider,不收 aria-required; 滑块恒有值,表单影子不带原生 required。 | |
tone | Tone | 语气:brand / neutral / success / warning / danger / info,决定使用哪族颜色 | |
size | Size | 尺寸:sm / md / lg,决定拇指直径与轨道厚度 | |
name | string | 表单字段名;多滑块时逐个 append。 | |
minStepsBetweenThumbs | number | 相邻滑块至少相隔的格数,默认 0(可以贴在一起但不能交换顺序)。 | |
marks | SliderMark[] | 刻度表:轨道上的圆点与文案,点击文案即跳到该值。 | |
snapToMarks | boolean | 只接受刻度落点:拖动、点击与键盘都吸附到最近 / 下一档刻度。 | |
inverted | boolean | 反向:min 落在轨道的末端(横排在行尾、竖排在顶端),已选区间从末端画起。 方向键跟随屏幕方向:拇指在屏幕上往哪边挪,按的就是哪个键。默认 false。 | |
draggableRange | boolean | 多拇指时按住两端拇指之间的轨道拖动,整段区间一起平移、宽度不变;按在拇指上仍只推那一个。 只认刻度落点(snapToMarks)时不生效:整段平移会把拇指推离刻度。默认 false。 | |
trackFill | boolean | 画出已选区间(range 部件)并给落进区间的刻度上色,默认 true;关掉后轨道只剩底槽与拇指。 | |
getValueText | (details: SliderValueTextDetails) => string | 把值转换为可读文字,产出写入拇指的 aria-valuetext。 未提供时不写该属性,读屏回退为朗读 aria-valuenow。 | |
onValueChange | (details: SliderValueChangeDetails) => void | 每次推动都发出;拖动过程中连续发出。 | |
onValueChangeEnd | (details: SliderValueChangeEndDetails) => void | 只在一次操作结束时发出一次,适合用于发起请求。 |
SliderMark
marks 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
value | number | 是 | |
label | string |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
value-change | SliderValueTextDetails | 值变化(拖动途中连续发出);detail 为 { value: number[] } |
value-change-end | SliderValueChangeEndDetails | 一次操作收尾时发出一次;detail 为 { value: number[], index: number } |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhSliderRoot | default | SliderRootSlotProps | |
XhSliderRoot | label | — | |
XhSliderTickGroup | tick | SliderTickGroupTickSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhSliderRoot | label | ReactNode | 标题。不给 children 时铺 label 部件。 | |
XhSliderRoot | children | SlotChildren<SliderRootSlotProps> | ||
XhSliderThumb | index | number | string | 第几个滑块,多滑块时必须逐个写明;兼收字符串。 | |
XhSliderTickGroup | tick | SlotChildren<SliderTickSlotProps> | 逐档刻度的文案接管口;未提供时填入刻度自带的 label。 |
状态
以下名称仅用于内部状态机。
状态:idle · dragging
事件:VALUE.SET · THUMB.STEP · THUMB.TO_MIN · THUMB.TO_MAX · THUMB.SET · THUMB.FOCUS · DRAG.START · DRAG.MOVE · DRAG.END · FORM.RESET
判据:canDrag
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
value | number[] | |
range | { start: number, end: number } | 已选区间在轨道上的起止,0-1。 |
thumbs | SliderThumbState[] | |
marks | SliderMarkMeta[] | 刻度呈现数据:夹进区间、升序去重,带位置与分段上色标记。 |
dragging | boolean | |
disabled | boolean | |
readOnly | boolean | |
valueText | (index: number) => string | 某个拇指的值文本:提供 getValueText 时是其产出,否则是值本身。 |
setValue | (next: number[]) => void | |
setThumbValue | (index: number, next: number) => void | |
getRootProps | () => T['element'] | |
getLabelProps | () => T['label'] | |
getControlProps | () => T['element'] | |
getTrackProps | () => T['element'] | |
getRangeProps | () => T['element'] | |
getThumbProps | (index: number) => T['element'] | |
getValueTextProps | (index: number) => T['element'] | 值气泡:挂在拇指中显示该拇指的当前值;aria-hidden,读屏使用拇指自身的 aria-valuetext。 |
getTickGroupProps | () => T['element'] | 刻度容器。 |
getTickProps | (props: SliderTickProps) => T['element'] | 刻度点:轨道上的圆点,纯装饰。 |
getTickLabelProps | (props: SliderTickProps) => T['element'] | 刻度文案:点击把最近的滑块跳到该档。 |
getHiddenInputProps | (index: number) => T['input'] |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
ArrowRight / ArrowUp | focus in thumb, not disabled/readOnly | 按 step 增大;方向跟随屏幕:RTL 对调左右两键,inverted 再对调一次(竖直轨道 inverted 时 ArrowUp 朝 min) |
ArrowLeft / ArrowDown | focus in thumb, not disabled/readOnly | 按 step 减小,同上对调规则 |
PageUp | focus in thumb, not disabled/readOnly | 按 largeStep 增大(默认 10 倍 step) |
PageDown | focus in thumb, not disabled/readOnly | 按 largeStep 减小 |
Home | focus in thumb, not disabled/readOnly | 取 min;多滑块时取自己被邻居允许的下界 |
End | focus in thumb, not disabled/readOnly | 取 max;多滑块时取自己被邻居允许的上界 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
thumb | aria-disabled | 'true' | 'false' |
thumb | aria-label | props.ariaLabel |
thumb | aria-labelledby | label 部件的 id | undefined |
thumb | aria-orientation | props.orientation |
thumb | aria-valuemax | String(thumb.max) |
thumb | aria-valuemin | String(thumb.min) |
thumb | aria-valuenow | String(thumb.value) |
thumb | aria-valuetext | prop('getValueText')?.({ value: thumb.value, index: t… |
thumb | role | 'slider' |
value-text | aria-hidden | 'true' |
tick | aria-hidden | 'true' |
样式参考
皮肤
@xihan-ui/styles/slider.css 按 [data-scope="slider"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-slider 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-dragging | ''(条件成立时才出现) |
root | data-following | ''(条件成立时才出现) |
root | data-invalid | ''(条件成立时才出现) |
root | data-orientation | props.orientation |
root | data-readonly | ''(条件成立时才出现) |
root | data-required | ''(条件成立时才出现) |
root | data-size | props.size |
root | data-tone | props.tone |
label | data-disabled | ''(条件成立时才出现) |
label | data-dragging | ''(条件成立时才出现) |
label | data-following | ''(条件成立时才出现) |
label | data-invalid | ''(条件成立时才出现) |
label | data-orientation | props.orientation |
label | data-readonly | ''(条件成立时才出现) |
label | data-required | ''(条件成立时才出现) |
control | data-disabled | ''(条件成立时才出现) |
control | data-dragging | ''(条件成立时才出现) |
control | data-following | ''(条件成立时才出现) |
control | data-invalid | ''(条件成立时才出现) |
control | data-orientation | props.orientation |
control | data-readonly | ''(条件成立时才出现) |
track | data-disabled | ''(条件成立时才出现) |
track | data-dragging | ''(条件成立时才出现) |
track | data-following | ''(条件成立时才出现) |
track | data-invalid | ''(条件成立时才出现) |
track | data-orientation | props.orientation |
track | data-readonly | ''(条件成立时才出现) |
range | data-disabled | ''(条件成立时才出现) |
range | data-draggable | ''(条件成立时才出现) |
range | data-dragging | ''(条件成立时才出现) |
range | data-following | ''(条件成立时才出现) |
range | data-invalid | ''(条件成立时才出现) |
range | data-orientation | props.orientation |
range | data-readonly | ''(条件成立时才出现) |
thumb | data-disabled | ''(条件成立时才出现) |
thumb | data-dragging | ''(条件成立时才出现) |
thumb | data-following | ''(条件成立时才出现) |
thumb | data-index | String(thumb.index) |
thumb | data-invalid | ''(条件成立时才出现) |
thumb | data-orientation | props.orientation |
thumb | data-readonly | ''(条件成立时才出现) |
value-text | data-disabled | ''(条件成立时才出现) |
value-text | data-dragging | ''(条件成立时才出现) |
value-text | data-following | ''(条件成立时才出现) |
value-text | data-index | String(thumb.index) |
value-text | data-invalid | ''(条件成立时才出现) |
value-text | data-orientation | props.orientation |
value-text | data-readonly | ''(条件成立时才出现) |
tick-group | data-disabled | ''(条件成立时才出现) |
tick-group | data-dragging | ''(条件成立时才出现) |
tick-group | data-following | ''(条件成立时才出现) |
tick-group | data-invalid | ''(条件成立时才出现) |
tick-group | data-orientation | props.orientation |
tick-group | data-readonly | ''(条件成立时才出现) |
tick | data-passed | ''(条件成立时才出现) |
tick-label | data-passed | ''(条件成立时才出现) |
hidden-input | data-index | String(thumb.index) |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-slider-control-gutter | controlroot | margin-inline | has([data-part='tick-label'])orientation=horizontal | --xh-space-6 | slider 的 control、root 部件 margin-inline 覆盖槽。 |
--xh-slider-gap | labelroot | gapmargin-block-end | default | --xh-space-1 | slider 的 label、root 部件 gap、margin-block-end 覆盖槽。 |
--xh-slider-label-fg | label | color | default | --xh-fg-muted | slider 的 label 部件 color 覆盖槽。 |
--xh-slider-label-fg-disabled | label | color | disabled | --xh-fg-muted | slider 的 label 部件 color 覆盖槽。 |
--xh-slider-label-font-size | label | font-size | default | --xh-text-label-size | slider 的 label 部件 font-size 覆盖槽。 |
--xh-slider-label-font-weight | label | font-weight | default | --xh-text-label-weight | slider 的 label 部件 font-weight 覆盖槽。 |
--xh-slider-label-gap | controllabel | inset-block-endinset-block-startmargin-block-end | @media (pointer: coarse)defaultorientation=horizontal | --xh-space-2 | slider 的 control、label 部件 inset-block-end、inset-block-start、margin-block-end 覆盖槽。 |
--xh-slider-range-bg | range | background | default | --xh-_tone | slider 的 range 部件 background 覆盖槽。 |
--xh-slider-range-bg-disabled | range | background | disabled | --xh-bg-subtle-hover-opaque | slider 的 range 部件 background 覆盖槽。 |
--xh-slider-range-bg-invalid | range | background | invalid | --xh-border-invalid | slider 的 range 部件 background 覆盖槽。 |
--xh-slider-range-radius | range | border-radius | default | --xh-shape-pill | slider 的 range 部件 border-radius 覆盖槽。 |
--xh-slider-thumb-bg | thumb | background | default | --xh-bg-surface | slider 的 thumb 部件 background 覆盖槽。 |
--xh-slider-thumb-bg-disabled | thumb | background | disabled | --xh-bg-surface | slider 的 thumb 部件 background 覆盖槽。 |
--xh-slider-thumb-border | thumb | border | default | --xh-_tone | slider 的 thumb 部件 border 覆盖槽。 |
--xh-slider-thumb-border-disabled | thumb | border-color | disabled | --xh-bg-subtle-hover-opaque | slider 的 thumb 部件 border-color 覆盖槽。 |
--xh-slider-thumb-border-invalid | thumb | border-color | invalid | --xh-border-invalid | slider 的 thumb 部件 border-color 覆盖槽。 |
--xh-slider-thumb-radius | thumb | border-radius | default | --xh-shape-circle | slider 的 thumb 部件 border-radius 覆盖槽。 |
--xh-slider-thumb-size | controlrootthumb | block-sizeinline-sizeinset-block-endinset-block-startinset-inlinemargin-block-endmargin-block-startmargin-inline-start | @media (pointer: coarse)defaultorientation=horizontalorientation=verticalsize=lgsize=sm | --xh-track-thumb-size-lg--xh-track-thumb-size-md--xh-track-thumb-size-sm | slider 的 control、root、thumb 部件 block-size、inline-size、inset-block-end、inset-block-start、inset-inline、margin-block-end、margin-block-start、margin-inline-start 覆盖槽。 |
--xh-slider-tick-bg | tick | background | default | --xh-bg-surface | slider 的 tick 部件 background 覆盖槽。 |
--xh-slider-tick-border | tick | border | default | --xh-bg-subtle-hover | slider 的 tick 部件 border 覆盖槽。 |
--xh-slider-tick-border-active | tick | border-color | passed | --xh-_tone | slider 的 tick 部件 border-color 覆盖槽。 |
--xh-slider-tick-border-active-disabled | ticktick-group | border-color | disabledpassed | --xh-bg-subtle-hover-opaque | slider 的 tick、tick-group 部件 border-color 覆盖槽。 |
--xh-slider-tick-border-disabled | ticktick-group | border-color | disabled | --xh-bg-subtle | slider 的 tick、tick-group 部件 border-color 覆盖槽。 |
--xh-slider-tick-label-fg | tick-label | color | default | --xh-fg-subtle | slider 的 tick-label 部件 color 覆盖槽。 |
--xh-slider-tick-label-fg-active | tick-label | color | passed | --xh-fg-default | slider 的 tick-label 部件 color 覆盖槽。 |
--xh-slider-tick-label-fg-disabled | tick-grouptick-label | color | disabled | --xh-fg-disabled | slider 的 tick-group、tick-label 部件 color 覆盖槽。 |
--xh-slider-tick-label-font-size | tick-label | font-size | default | --xh-text-caption-size | slider 的 tick-label 部件 font-size 覆盖槽。 |
--xh-slider-tick-label-gap | roottick-label | margin-block-startmargin-inline-start | defaultorientation=vertical | --xh-space-1 | slider 的 root、tick-label 部件 margin-block-start、margin-inline-start 覆盖槽。 |
--xh-slider-tick-radius | tick | border-radius | default | --xh-shape-circle | slider 的 tick 部件 border-radius 覆盖槽。 |
--xh-slider-tick-size | tick | block-sizeinline-size | default | --xh-space-2 | slider 的 tick 部件 block-size、inline-size 覆盖槽。 |
--xh-slider-track-bg | track | background | default | --xh-bg-subtle-hover | slider 的 track 部件 background 覆盖槽。 |
--xh-slider-track-bg-disabled | track | background | disabled | --xh-bg-subtle | slider 的 track 部件 background 覆盖槽。 |
--xh-slider-track-radius | track | border-radius | default | --xh-shape-pill | slider 的 track 部件 border-radius 覆盖槽。 |
--xh-slider-track-thickness | roottrack | block-sizeinline-size | orientation=horizontalorientation=verticalsize=lg | --xh-stroke-strong--xh-stroke-thick | slider 的 root、track 部件 block-size、inline-size 覆盖槽。 |
--xh-slider-value-text-bg | value-text | background | default | --xh-_tone | slider 的 value-text 部件 background 覆盖槽。 |
--xh-slider-value-text-fg | value-text | color | default | --xh-_tone-on | slider 的 value-text 部件 color 覆盖槽。 |
--xh-slider-value-text-font-size | value-text | font-size | default | --xh-text-caption-size | slider 的 value-text 部件 font-size 覆盖槽。 |
--xh-slider-value-text-offset | value-text | margin-block-endmargin-inline | defaultorientation=vertical | --xh-space-2 | slider 的 value-text 部件 margin-block-end、margin-inline 覆盖槽。 |
--xh-slider-value-text-px | value-text | padding-inline | default | --xh-space-2 | slider 的 value-text 部件 padding-inline 覆盖槽。 |
--xh-slider-value-text-py | value-text | padding-block | default | --xh-space-0_5 | slider 的 value-text 部件 padding-block 覆盖槽。 |
--xh-slider-value-text-radius | value-text | border-radius | default | --xh-shape-control | slider 的 value-text 部件 border-radius 覆盖槽。 |
--xh-slider-vertical-length | control | block-size | orientation=vertical | 10rem | slider 的 control 部件 block-size 覆盖槽。 |
动效
动效角色:状态 · 指示与换位(见动效规范)。
background-color · block-size · border-color · inline-size · inset-block-end · inset-inline-start · opacity 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
响应式
皮肤另按输入能力分档:pointer: coarse:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;只认物理方向的量乘 --xh-direction-sign 换向,按就近的 dir 走。
