콘텐츠로 이동

slider —— 슬라이더

슬라이더는 숫자 선택을 공간적 조작으로 바꿉니다: 입력하는 것보다 끌어당기는 것이 더 직관적이며, 값이 범위 안에서 어디 있는지 한눈에 볼 수 있습니다. core/slider가 관리하는 것은 “값 ↔ 위치”의 양방향 환산, 스텝 흡착, 구간 (range) 모드의 두 점 초깃값, 그리고 APG slider 의미입니다.

  • 각 말단 구현 LhSlider: 음량, 가격 범위, 불투명도 조절.
  • 값 범위가 연속적이거나 스텝 단위가 비교적 굵은 상황.
  • 사용하면 안 되는 경우: 정밀한 숫자 입력 (input-number 사용, 슬라이더는 “37” 같은 스텝이 아닌 값을 표현할 수 없음).
import {
snapToStep,
clampAndSnap,
getValueFromPosition,
getPositionFromValue,
getSliderClassNames,
getInitialValue,
buildMarksList,
getSliderThumbAria,
} from '@lh-ui/share/core/slider'
import type { LhSliderState, LhSliderMark } from '@lh-ui/share/core/slider'

snapToStep는 step이 유한하지 않거나 <= 0일 때 NaN을 반환하는 대신 원래 값을 반환합니다——step: 0은 모듈러 연산에서 NaN을 만들어내며, 여기서는 “잘못된 스텝”을 “흡착 없음”으로 취급하고, 순수한 경계 클램프는 clampAndSnap에 맡깁니다. 역할의 분리: 흡착과 클램프는 독립적인 두 가지 일입니다.

getPositionFromValue가 max === min일 때 0을 반환하는 것은 0으로 나누기를 막기 위함입니다: 값 범위가 한 점으로 퇴화하면 슬라이더는 당연히 시작점에 있어야 합니다.

getInitialValue는 구간 모드에서 [min 클램프, (min+max)/2 클램프]를 반환합니다——두 번째 슬라이더의 기본 위치가 max가 아니라 중간점인 것은 시각적 고려입니다 (두 슬라이더가 붙어 있으면 버그처럼 보입니다).

함수 시그니처 반환 역할
snapToStep (value: number, min: number, step: number) => number 흡착값 step에 가장 가까운 격자점으로 정렬
clampAndSnap (value: number, min: number, max: number, step: number) => number 정규화값 먼저 흡착 후 클램프
getValueFromPosition (pct: number, min: number, max: number, step: number) => number 숫자 백분율 → 값
getPositionFromValue (value: number, min: number, max: number) => number 백분율 [0,1] 값 → 위치
getSliderClassNames (state: LhSliderState) => string[] 클래스명 disabled/range/size
getInitialValue (state: LhSliderState) => number | [number, number] 초깃값 구간은 두 점 반환
buildMarksList (marks?: Record<number, string>) => LhSliderMark[] 눈금 배열 value 오름차순
getSliderThumbAria (options: { value, min, max, vertical?, disabled?, ariaValueText? }) => LhAriaAttrs aria 속성 팩 slider 의미
export interface LhSliderMark {
label: string
value: number
}
export interface LhSliderState {
value: number | [number, number] | null
min: number
max: number
step: number
disabled: boolean
range: boolean // 구간 모드 (두 개의 슬라이더)
showInput: boolean // input-number 연동
size: LhComponentSize
marks: Record<number, string> | undefined
}

getSliderThumbAria는 APG slider 의미를 출력합니다. 구간 모드에서는 두 슬라이더에 각각 한 번씩 호출해야 합니다:

getSliderThumbAria({ value: 30, min: 0, max: 100 })
// { role: 'slider', 'aria-valuenow': 30, 'aria-valuemin': 0, 'aria-valuemax': 100 }
getSliderThumbAria({ value: 60, min: 0, max: 100, vertical: true, disabled: true })
// { role: 'slider', 'aria-valuenow': 60, 'aria-valuemin': 0, 'aria-valuemax': 100,
// 'aria-orientation': 'vertical', 'aria-disabled': true }
  • vertical은 aria-orientation: 'vertical'을 출력합니다 (수평은 기본 방향이므로 선언하지 않음).
  • ariaValueText는 aria-valuetext를 출력합니다——숫자가 의미적으로 표현되어야 할 때 사용하며 (예: “30%” 또는 “느리게”), valuenow보다 우선하여 읽힙니다.
  • 키보드 상호작용 (UI 계층): ←/→ 또는 ↓/↑로 step 단위 증감, Home/End로 min/max로 이동, PageUp/Down으로 큰 스텝.
import { getValueFromPosition, getPositionFromValue, snapToStep } from '@lh-ui/share/core/slider'
getValueFromPosition(0.5, 0, 100, 1) // 50
getValueFromPosition(0.333, 0, 100, 5) // 35 (5의 배수로 흡착)
getPositionFromValue(30, 0, 100) // 0.3
getPositionFromValue(150, 0, 100) // 1 (클램프)
getPositionFromValue(50, 50, 50) // 0 (max===min, 0으로 나누기 방지)
snapToStep(7, 0, 5) // 5
snapToStep(8, 0, 5) // 10
snapToStep(7, 0, 0) // 7 (잘못된 step은 흡착하지 않음)
buildMarksList({ 0: '낮음', 100: '높음' }) // [{ label: '낮음', value: 0 }, { label: '높음', value: 100 }]
<!-- Vue3 -->
<div :class="getSliderClassNames({ value, min, max, step, disabled, range, showInput, size, marks })">
<div class="lh-slider__track">
<div class="lh-slider__bar" :style="{ width: pct + '%' }" />
<div
class="lh-slider__button-wrapper"
:style="{ left: pct + '%' }"
v-bind="getSliderThumbAria({ value, min, max, disabled })"
@pointerdown="startDrag"
>
<div class="lh-slider__button" />
</div>
<template v-if="marks">
<div v-for="mark in buildMarksList(marks)" :key="mark.value" class="lh-slider__marks">
<span class="lh-slider__mark" :style="{ left: getPositionFromValue(mark.value, min, max) * 100 + '%' }">
{{ mark.label }}
</span>
</div>
</template>
</div>
</div>
// React: 드래그는 pointer events + getBoundingClientRect로 백분율 계산
// const pct = (clientX - rect.left) / rect.width
// onChange(getValueFromPosition(pct, min, max, step))
// uniapp: <slider>는 내장 컴포넌트 (단일 슬라이더), min/max/step/show-value 지원;
// 구간 모드와 marks는 커스텀 view로 구현해야 하며 드래그는 @touchmove 사용
클래스명 의미
.lh-slider 기본 클래스
.lh-slider--{small|large} 크기 단계 (슬라이더 크기)
.lh-slider--disabled 비활성
.lh-slider--range 구간 모드 (두 개의 슬라이더 + 중간 하이라이트 구간)
.lh-slider__track 트랙
.lh-slider__bar 선택 구간 (하이라이트 구간)
.lh-slider__button-wrapper / __button 슬라이더 손잡이
.lh-slider__marks / __mark 눈금
.lh-slider__input / __input-text 연동 입력란

트랙 색은 --lh-fill-color, 선택 구간은 --lh-primary-color입니다.

  • getPositionFromValue는 [0,1]의 소수를 반환, UI 계층이 %를 붙입니다; 백분율을 반환한다고 오해하지 마세요.
  • 흡착과 눈금의 정렬: marks의 value는 step의 배수여야 하며, 그렇지 않으면 눈금점이 두 흡착 값 사이에 떨어져 사용자가 거기까지 끌어당길 수 없습니다.
  • 구간 모드의 교차: 두 슬라이더는 서로를 통과할 수 있습니다 (UI 계층은 보통 금지하거나 자동으로 교환), core의 clampAndSnap은 이 의미를 처리하지 않습니다.
  • 수직 모드: vertical일 때는 트랙 높이가 너비를 대신하고 환산의 분모가 바뀝니다; core의 getValueFromPosition은 축에 무관하여 (백분율을 받음) pct를 계산하는 것은 UI 계층의 책임입니다.
  • showInput의 연동: input-number의 min/max/step은 slider와 일치해야 하며, 그렇지 않으면 양쪽의 값 범위가 달라집니다.
@lh-ui/share/core/slider → getValueFromPosition( )
返回值
(填好参数后点运行)

Copyright © 2024-2026 FengMo WenXue