其他
在 TS/JS/React/Vue/Solid/Svelte…
需要单值、范围或多范围滑块,并希望使用无头、可访问、完全掌控 markup 与样式的方案时。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 40/100 ×0.15
适用场景
在 TS/JS/React/Vue/So特定
解决思路
- 安装并选择适配器:React 用 `npm install @tanstack/react-ranger`;框架无关核心用 `@tanstack/ranger-core`;Vue/Solid/Svelte/Angular 分别用 `@tanstack/vue-ranger`、`@tanstack/solid-ranger`、`@tanstack/svelte-ranger`、`@tanstack/angular-ranger`。
- 从对应包导入 `useRanger`,用 `useState` 保存 `values`,用 `useRef<HTMLDivElement>(null)` 创建 `rangerRef`。
- 调用 `useRanger` 并传入必需配置:`getRangerElement: () => rangerRef.current`、`values`、`min`、`max`、`onChange: (instance) => setValues(instance.sortedValues)`。
- 将 `rangerRef` 挂到滑块轨道容器,并设置 `position: 'relative'`、`height: '8px'`、`background: '#ddd'`、`borderRadius: '4px'`、`width: '100%'` 等样式。
- 用 `rangerInstance.getSteps().map(({ left, width }, i) => ...)` 渲染轨道段;每段用绝对定位,`left` 设为 `${left}%`、`width` 设为 `${width}%`、`height: '100%'`,按需要给 active 段设置背景色(示例 `i === 1` 或 `i % 2 === 1`)。
- 用 `rangerInstance.handles.map((handle, i) => ...)` 渲染把手;将 `{...handle.getHandleProps()}` 展开到 `<button>`,用 `left` 设为 `${handle.getPercentage()}%` 和 `transform: 'translateX(-50%)'` 定位,并设置尺寸、圆形、背景、边框、`cursor: 'grab'`。
- 配置可选参数:`stepSize`(默认 1)、`steps`(自定义步进位置,覆盖 `stepSize`)、`tickSize`、`ticks`(自定义刻度位置)、`interpolator`(默认 linear)、`onDrag`(拖动期间调用)。
- 使用实例 API:`rangerInstance.sortedValues` 获取升序值;`rangerInstance.handles` 获取把手;`rangerInstance.getSteps()` 获取 `{ left, width }` 轨道段;`rangerInstance.getTicks()` 获取 `{ value, percentage }` 刻度;`rangerInstance.setValues(newValues)` 以编程方式设置值。
- 使用 Handle API:`handle.getPercentage()` 返回 0-100 的百分比位置;`handle.getValue()` 返回当前值;`handle.getHandleProps()` 返回 `role: 'slider'`、`tabIndex`、`aria-valuemin`、`aria-valuemax`、`aria-valuenow`、`onKeyDown`、`onMouseDown`、`onTouchStart`。
- 单值滑块:`values` 设为 `[50]`,只渲染 `rangerInstance.handles`,在 `<button>` 中显示 `handle.getValue()`。
- 多范围滑块:`values` 设为如 `[10, 40, 60, 90]`,可设 `stepSize: 5`;用 `getSteps()` 渲染多段,按 `i % 2 === 1` 标记 active 段,再渲染所有 handles。
- 自定义步进:传入 `steps: [0, 10, 25, 50, 75, 100]`,只允许这些值。
- 刻度标记:传入 `ticks: [0, 25, 50, 75, 100]`,用 `rangerInstance.getTicks().map((tick, i) => ...)` 渲染,使用 `left` 设为 `${tick.percentage}%`,并显示 `tick.value`。
- 对数刻度:从 `@tanstack/react-ranger` 导入 `logarithmicInterpolator`,在 `useRanger` 中传 `interpolator: logarithmicInterpolator`,例如 `min: 1`、`max: 1000`。
- 无障碍:依赖内置 `role='slider'`、`aria-valuemin`、`aria-valuemax`、`aria-valuenow`、键盘导航(Arrow keys、Home、End、Page Up/Down)和焦点管理;为 `<button>` 添加 `aria-label`,例如 `Value: ${handle.getValue()}`。
- 受控用法:推荐用 `useState` 保存 `values`,在 `onChange` 中执行 `setValues(instance.sortedValues)`;需要验证时在 `onChange` 中检查约束,例如解构 `[min, max] = instance.sortedValues`,仅当 `max - min >= 10` 时 `setValues(instance.sortedValues)`。
- 样式轨道:`.slider-track` 设置 `position: relative`、`height: 8px`、`background: #e5e7eb`、`border-radius: 4px`、`width: 100%`;active 段 `.segment.active` 设置 `background: #3b82f6`;把手 `.slider-thumb` 设置 `position: absolute`、`transform: translateX(-50%)`、`width: 20px`、`height: 20px`、`border-radius: 50%`、`background: #3b82f6`、`border: 2px solid white`、`box-shadow: 0 2px 4px rgba(0,0,0,0.2)`、`cursor: grab`;`.slider-thumb:active` 设置 `cursor: grabbing`;`.slider-thumb:focus` 设置 `outline: none`、`box-shadow: 0 0 0 3px rgba(59,130,246,0.3)`。
- 框架适配器:React 用 `@tanstack/react-ranger`、Vue 用 `@tanstack/vue-ranger`、Solid 用 `@tanstack/solid-ranger`、Svelte 用 `@tanstack/svelte-ranger`、Angular 用 `@tanstack/angular-ranger`、Core 用 `@tanstack/ranger-core`,均为 Stable。
- 最佳实践:始终使用 `onChange` 中的 `sortedValues`,因为拖动时 handles 可能交叉。
- 最佳实践:缓存 `getRangerElement` 回调,防止不必要重渲染。
- 最佳实践:使用语义 HTML,将 handles 渲染为 `<button>` 元素以保证无障碍。
- 最佳实践:为每个 handle 添加 `aria-label` 描述用途。
- 最佳实践:使用 CSS transforms(`translateX`)定位,而不是 `left`,以提升性能。
- 最佳实践:在 `onChange` 中验证,以强制约束(最小间隔、最大范围等)。
- 最佳实践:使用 `onDrag` 在拖动期间提供实时反馈。
- 最佳实践:考虑触摸目标,移动端把手至少 44x44px。
- 常见陷阱:确保轨道容器有 `position: relative`。
- 常见陷阱:不要使用 `values` 而要用 `sortedValues`(handles 可交换位置)。
- 常见陷阱:必须把 `getRangerElement` 作为 callback 提供。
- 常见陷阱:不要用 `left` 设置 thumb 位置,而用 `transform: translateX()`。
- 常见陷阱:不要忘记处理键盘导航(通过 `getHandleProps` 内置)。
- 常见陷阱:计算位置时要考虑 thumb 宽度。
适用边界 · 注意事项
- 不需要范围或多范围选择,只用普通单值输入或原生 `input[type=range]` 即可时。
- 不想引入 `@tanstack/*-ranger` 依赖,或需要开箱即用带样式组件而非 headless 方案时。
- 使用的框架没有对应适配器且不愿直接用 `@tanstack/ranger-core` 或自行封装时。