输入类

滑动选择器

滑动选择器,使用拖动交互快速选择数值或数值范围,与 InputNumber 相比更直观

代码演示

如何引入

import { Button, InputNumber, Slider } from '@fibrous/semi-ui-solid';

基本用法

基本滑动条。当 range 为 true 时,支持两侧滑动。当 disabled 为 true 时,滑块处于不可用状态。

默认
0100


范围
0100


禁用
0100
代码
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>
    );
}

带输入框的

滑动条的滑块和输入框组件保持同步。

0100
代码
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 用于给滑块的当前值提供一个用户友好的名称,对屏幕阅读器用户很重要。

0100


0100
代码
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 时,标签与气泡各调用一次。

step=10
01020304050100
0100


step=0.1
0.10.20.30.40.5
01


Marks
20°C40°C
0100


Included
20°C40°C
0100
代码
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 可以实现动态的分段背景效果。

10100
代码
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 通知,滑块不移动。



0100
代码
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>
    );
}

垂直

0100
0100
0100
0100
20°C40°C
0100
20°C40°C
0100
代码
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>
    );
}

滑块带圆点

Default
0100


Range
0100
代码
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滑块是否禁用。booleanfalse
getAriaValueText为滑块的当前值提供用户友好的名称,参数为该滑块的值与顺序。Type(value: number, index?: number) => string-
handleDot滑块上的圆点,单个滑块传一个对象,range 时按手柄传数组。HandleDot | HandleDot[]-
includedmarks 不为空对象时有效,true 表示刻度为包含关系,false 表示并列。booleantrue
marks刻度,key 为 number 且取值在闭区间 [min, max] 内,值为字符串或数字;自定义节点使用 renderMark。TypeRecord<number, string | number>-
max最大值。number100
min最小值。number0
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是否支持两端同时可滑动。booleanfalse
renderMark渲染刻度标签,参数为 (value, label);开启 tooltipOnMark 时标签与气泡各调用一次。不传时显示 label。Type(value: number, label: string | number) => JSX.Element-
showArrow提示是否带箭头。booleantrue
showBoundary是否在悬停时展示最大值与最小值。booleanfalse
showMarkLabel是否显示刻度标签。booleantrue
step步长,从 min 起计算。number1
tipFormatter设置提示的展示格式,每个手柄单独调用,参数为该手柄的值;默认显示当前取值,null 隐藏提示。Type((value: number) => any) | nullType(value: number) => value
tooltipOnMark刻度上是否带有提示。booleanfalse
tooltipVisible是否始终显示提示。boolean-
value当前取值,受控使用;只传 value 而不在 onChange 中回写时,拖动、点击与方向键仅触发 onChange,滑块不移动。变回 undefined 时回到 defaultValue,没有则回到 min,且不触发 onAfterChange。number | number[]-
vertical是否为垂直方向。booleanfalse
verticalReverse反转垂直方向,即上大下小。booleanfalse

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 传递一个字符串使其更友好。也可以通过 API getAriaValueText(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 用户: Home ;
    • Mac 用户: Fn + 左箭头;
    • 当用户使用 range 属性时,后一个滑块的 Home(Fn + 左箭头) 键仅支持到与前一个滑块相遇,重合后再次使用 Home(Fn + 左箭头) 键无响应。
  • 若想将滑块移动到滑杆的最大值处:
    • Windows 用户:End ;
    • Mac 用户:Fn + 右箭头;
    • 当用户使用 range 属性时,前一个滑块的 End(Fn + 右箭头) 键仅支持到与后一个滑块相遇,重合后再次使用 End(Fn + 右箭头) 键无响应。

设计变量

聚焦样式说明

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 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$transition_duration-slider_handle-bgvar(--semi-transition_duration-none)滑动条圆形按钮-背景色-动画持续时间
$transition_function-slider_handle-bgvar(--semi-transition_function-easeIn)滑动条圆形按钮-背景色-过渡曲线
$transition_delay-slider_handle-bgvar(--semi-transition_delay-none)滑动条圆形按钮-背景色-延迟时间
$transform_scale-slider_handlevar(--semi-transform_scale-small)滑动条圆形按钮-放大
$color-slider-text-defaultvar(--semi-color-text-0)滑动条文本颜色 - 默认
$color-slider_dot-bg-defaultvar(--semi-color-white)滑动条圆形刻度点颜色 - 默认态
$color-slider_dot-border-activevar(--semi-color-white)滑动条圆形刻度点描边颜色 - 按下态
$color-slider_dot-bordervar(--semi-color-white)滑动条圆形刻度点描边颜色 - 默认态
$color-slider_handle-bg-defaultvar(--semi-color-white)滑动条圆形按钮颜色 - 默认态
$color-slider_handle-bg-hovervar(--semi-color-white)滑动条圆形按钮颜色 - 悬停态
$color-slider_handle-border-focusvar(--semi-color-focus-border)滑动条圆形描边颜色 - 激活态
$color-slider_handle_disabled-border-hovervar(--semi-color-white)禁用滑动条圆形描边颜色 - 悬停态
$color-slider_handle_disabled-bordervar(--semi-color-border)禁用滑动条圆形描边颜色 - 默认态
$color-slider_mark-text-defaultvar(--semi-color-text-2)滑动条刻度文字颜色
$color-slider_rail-bg-defaultvar(--semi-color-fill-0)滑动条轨道颜色 - 未填充
$color-slider_railrgba(0, 0, 0, 0.65)
$color-slider_track-bg-defaultvar(--semi-color-primary)滑动条轨道颜色 - 已填充
$color-slider_track_disabled-bgvar(--semi-color-primary-disabled)禁用滑动条轨道颜色 - 已填充
$color-slider_handle-focus$color-focus-ring圆形按钮轮廓 - 聚焦
$color-slider_handle_dotvar(--semi-color-primary)圆形按钮内部圆点颜色
$spacing-slider-paddingX13px滑动条整体水平内边距
$spacing-slider-paddingY0px滑动条整体垂直内边距
$spacing-slider-margin0px滑动条整体外边距
$spacing-slider_rail-margin0px滑动条轨道外边距
$spacing-slider_rail-padding0px滑动条轨道内边距
$spacing-slider_rail-top14px滑动条未填充轨道顶部距离
$spacing-slider_handle-marginTop4px滑动条圆形按钮顶部外边距
$spacing-slider_track-top14px滑动条已填充轨道顶部距离
$spacing-slider_tooltip-top-40px滑动条工具提示顶部距离
$spacing-slider_dot-top14px滑动条圆形刻度点顶部距离
$spacing-slider_marks-top23px滑动条刻度标签顶部距离
$spacing-slider_marks-left0px滑动条刻度标签左侧距离
$spacing-slider_boundary-top30px
$spacing-slider_boundary_min-left0px
$spacing-slider_boundary_max-right0px
$spacing-slider_vertical_marks-marginTop-30px垂直滑动条刻度标签顶部外边距
$spacing-slider_vertical_marks-marginLeft29px垂直滑动条刻度标签左侧外边距
$spacing-slider_vertical_marks-reverse-marginLeft-26px垂直滑动条刻度标签左侧外边距(标签在左侧时)
$spacing-slider_vertical_rail-top0px垂直滑动条轨道顶部距离
$spacing-slider_vertical_handle-marginTop0px垂直滑动条原型按钮顶部外边距
$spacing-slider_vertical_handle-marginLeft-10px垂直滑动条原型按钮左侧外边距
$spacing-slider_handle-translateY0px水平滑动条原型按钮垂直偏移量
$spacing-slider_vertical_handle-translateX0px垂直滑动条原型按钮水平偏移量
$spacing-slider_dot-translateX0px水平滑动条圆形刻度点水平偏移量
$spacing-slider_vertical_dot-translateY0px垂直滑动条圆形刻度点垂直偏移量
$radius-slider_railvar(--semi-border-radius-small)滚动条未填充轨道圆角
$radius-slider_trackvar(--semi-border-radius-small)滚动条已填充轨道圆角
$height-slider_wrapper32px滚动条容器整体高度
$height-slider_vertical_wrapper4px垂直滚动条整体宽度
$height-slider_rail4px滚动条未填充轨道高度
$width-slider_handle24px滚动条圆形按钮宽度
$width-slider_handle_clicked1px滚动条圆形按钮按下后描边宽度
$height-slider_track4px滚动条已填充轨道高度
$width-slider_dot4px滚动条圆形刻度点宽度
$width-slider_handle_border_disabled1px禁用滚动条圆形按钮按下后描边宽度
$width-slider_handle-focus$width-focus-ring圆形按钮轮廓 - 聚焦
$width-slider_handle_dot4px圆形按钮内部圆点宽度
$font-slider_rail-fontSize14px滚动条轨道文本字号
$font-slider_marks-fontSize14px滚动条刻度标签字号
$font-slider_rail-lineHeight1.5滚动条轨道文本行高倍数