代码演示
如何引入
import { Button, InputNumber, Slider } from '@fibrous/semi-ui-solid';
基本用法
基本滑动条。当 range 为 true 时,支持两侧滑动。当 disabled 为 true 时,滑块处于不可用状态。
代码
import { Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
return (
<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>
);
}带输入框的
滑动条的滑块和输入框组件保持同步。
代码
import { createSignal } from 'solid-js';
import { InputNumber, Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
const [value, setValue] = createSignal(10);
const getSliderValue = (newValue?: number | string | number[]) => {
if (isNaN(Number(newValue))) {
return;
}
setValue(Number(newValue));
};
return (
<div>
<div style={{ width: '320px', 'margin-right': '15px' }}>
<Slider aria-label="数值" step={1} value={value()} onChange={getSliderValue} />
</div>
<InputNumber aria-label="数值" onChange={getSliderValue} style={{ width: '100px' }} value={value()} min={0} max={100} />
</div>
);
}自定义提示
使用 tipFormatter 可以设置 Tooltip 的显示格式,传入 null 则隐藏 Tooltip。getAriaValueText 用于给滑块的当前值提供一个用户友好的名称,对屏幕阅读器用户很重要。
代码
import { Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
return (
<div>
<Slider aria-label="带提示的滑块" tipFormatter={(v) => `${v}%`} getAriaValueText={(v) => `${v}%`} />
<br />
<br />
<Slider aria-label="不带提示的滑块" tipFormatter={null} />
</div>
);
}带标签的
使用 marks 属性标注滑块的刻度,使用 value / defaultValue 指定滑块位置。刻度标签只接受字符串或数字,自定义节点使用 renderMark(value, label);开启 tooltipOnMark 时,标签与气泡各调用一次。
代码
import { Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
return (
<div>
<div id="slider-marks-step-10">step=10</div>
<Slider aria-labelledby="slider-marks-step-10" step={10} marks={{ 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50', 100: '100' }} defaultValue={[10, 100]} range />
<br />
<br />
<div id="slider-marks-step-01">step=0.1</div>
<Slider aria-labelledby="slider-marks-step-01" step={0.1} marks={{ 0.1: '0.1', 0.2: '0.2', 0.3: '0.3', 0.4: '0.4', 0.5: '0.5' }} min={0} max={1} defaultValue={[0.1, 0.5]} range />
<br />
<br />
<div id="slider-marks-marks">Marks</div>
<Slider aria-labelledby="slider-marks-marks" marks={{ 20: '20°C', 40: '40°C' }} defaultValue={[0, 100]} tipFormatter={(v) => `${v}°C`} range getAriaValueText={(value) => `${value}°C`} />
<br />
<br />
<div id="slider-marks-included">Included</div>
<Slider aria-labelledby="slider-marks-included" marks={{ 20: '20°C', 40: '40°C' }} included={false} defaultValue={[0, 100]} range tipFormatter={(v) => `${v}°C`} getAriaValueText={(value) => `${value}°C`} />
</div>
);
}分段背景
通过使用 linear-gradient 及 railStyle,配合 onChange 可以实现动态的分段背景效果。
代码
import { createSignal } from 'solid-js';
import { Slider } from '@fibrous/semi-ui-solid';
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]}%)`,
};
};
export default function SliderDemo() {
const [value, setValue] = createSignal([20, 60]);
const changeValue = (newValue?: number | number[]) => {
if (Array.isArray(newValue)) {
setValue(newValue);
}
};
return <Slider aria-label="Value range" range min={range[0]} max={range[1]} onChange={changeValue} railStyle={getRailStyle(value())} value={value()} />;
}受控组件
滑块位置即 Slider 的值由 value 控制,配合 onChange 使用。只传 value 而不在 onChange 中回写时,拖动、点击与方向键仅触发 onChange 通知,滑块不移动。
代码
import { createSignal } from 'solid-js';
import { Button, Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
const [value, setValue] = createSignal(10);
const changeValue = () => {
setValue(value() + 10);
};
return (
<div>
<Button onClick={changeValue} style={{ 'margin-right': '20px' }}>
点击改变value值
</Button>
<br />
<br />
<Slider aria-label="受控滑块" value={value()} />
</div>
);
}垂直
代码
import { Slider } from '@fibrous/semi-ui-solid';
const boxStyle = {
height: '300px',
'margin-left': '30px',
'margin-top': '10px',
'padding-right': '30px',
display: 'inline-block',
};
const marks = { 20: '20°C', 40: '40°C' };
export default function SliderDemo() {
return (
<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" vertical range defaultValue={[20, 60]} />
</div>
<div style={boxStyle}>
<Slider aria-label="Reversed vertical range slider" vertical verticalReverse range defaultValue={[20, 60]} />
</div>
<div style={boxStyle}>
<Slider aria-label="Vertical range slider with marks" vertical range marks={marks} step={10} defaultValue={[20, 60]} />
</div>
<div style={boxStyle}>
<Slider aria-label="Reversed vertical range slider with marks" vertical verticalReverse range marks={marks} step={10} defaultValue={[20, 60]} />
</div>
</div>
);
}滑块带圆点
代码
import { Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
return (
<div>
<div>
<div id="slider-handle-dot-default">Default</div>
<Slider aria-labelledby="slider-handle-dot-default" showBoundary handleDot={{ size: '4px', color: 'blue' }} />
</div>
<br />
<br />
<div>
<div id="slider-handle-dot-range">Range</div>
<Slider
aria-labelledby="slider-handle-dot-range"
defaultValue={[20, 60]}
range
handleDot={[
{ size: '4px', color: 'blue' },
{ size: '4px', color: 'pink' },
]}
/>
</div>
</div>
);
}API参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| aria-label | 滑块的可访问名称;range 模式下两个手柄在其后分别追加语言包中的起点、终点。 | string | - |
| aria-labelledby | 引用标签元素的 id,作为手柄的可访问名称;range 模式下每个手柄再加上起点或终点。 | string | - |
| aria-valuetext | 为当前值提供用户友好的名称。 | string | - |
| autofocus | 挂载后自动聚焦第一个滑块。 | boolean | - |
| defaultValue | 非受控时的初始取值,未设置时为 min。 | number | number[] | - |
| disabled | 滑块是否禁用。 | boolean | false |
| getAriaValueText | 为滑块的当前值提供用户友好的名称,参数为该滑块的值与顺序。 | Type(value: number, index?: number) => string | - |
| handleDot | 滑块上的圆点,单个滑块传一个对象,range 时按手柄传数组。 | HandleDot | HandleDot[] | - |
| included | marks 不为空对象时有效,true 表示刻度为包含关系,false 表示并列。 | boolean | true |
| marks | 刻度,key 为 number 且取值在闭区间 [min, max] 内,值为字符串或数字;自定义节点使用 renderMark。 | TypeRecord<number, string | number> | - |
| max | 最大值。 | number | 100 |
| min | 最小值。 | number | 0 |
| name | 原生表单提交时的字段名,落在每个手柄的 input 上;range 模式下两个 input 同名,依次提交两个值。 | string | - |
| onAfterChange | 值变化完成后触发,参数为当前取值;受控值被改回 undefined 时不触发。 | Type(value: SliderProps['value']) => void | - |
| onBlur | 焦点离开两个手柄时触发,挂在 .semi-slider-wrapper 上;在两个手柄之间移动焦点不触发。 | TypeJSX.EventHandlerUnion<HTMLDivElement, FocusEvent> | - |
| onChange | 值变化时触发,参数为当前取值。 | Type(value: SliderProps['value']) => void | - |
| onFocus | 任一手柄获得焦点时触发,挂在 .semi-slider-wrapper 上。 | TypeJSX.EventHandlerUnion<HTMLDivElement, FocusEvent> | - |
| onKeyDown | 手柄 input 上的按键回调,先于滑块处理;在回调中调用 preventDefault 可拦截取值键。 | TypeJSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent> | - |
| onMouseUp | 拖动滑块的指针(鼠标、触摸、触控笔)松开或被取消时,以及取值键松开时触发。 | Type(event: PointerEvent | KeyboardEvent) => void | - |
| railStyle | 滑块轨道的样式,可传对象(键为 CSS 属性名)或字符串。 | JSX.CSSProperties | string | - |
| range | 是否支持两端同时可滑动。 | boolean | false |
| renderMark | 渲染刻度标签,参数为 (value, label);开启 tooltipOnMark 时标签与气泡各调用一次。不传时显示 label。 | Type(value: number, label: string | number) => JSX.Element | - |
| showArrow | 提示是否带箭头。 | boolean | true |
| showBoundary | 是否在悬停时展示最大值与最小值。 | boolean | false |
| showMarkLabel | 是否显示刻度标签。 | boolean | true |
| step | 步长,从 min 起计算。 | number | 1 |
| tipFormatter | 设置提示的展示格式,每个手柄单独调用,参数为该手柄的值;默认显示当前取值,null 隐藏提示。 | Type((value: number) => any) | null | Type(value: number) => value |
| tooltipOnMark | 刻度上是否带有提示。 | boolean | false |
| tooltipVisible | 是否始终显示提示。 | boolean | - |
| value | 当前取值,受控使用;只传 value 而不在 onChange 中回写时,拖动、点击与方向键仅触发 onChange,滑块不移动。变回 undefined 时回到 defaultValue,没有则回到 min,且不触发 onAfterChange。 | number | number[] | - |
| vertical | 是否为垂直方向。 | boolean | false |
| verticalReverse | 反转垂直方向,即上大下小。 | boolean | false |
class、style、ref 与其余原生属性(如 data-*)落在 .semi-slider-wrapper 上,onFocus、onBlur、onMouseEnter、onMouseLeave 也绑在它上面。id(只在第一个手柄上)、name、aria-describedby、aria-invalid、aria-errormessage 与 onKeyDown 落在每个手柄的原生 input 上;range 模式下两个 input 的 name 相同,依次提交两个值。tabIndex、aria-required 不输出。
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 | 滚动条轨道文本行高倍数 |