Va.DoubleSlider — 두 개의 thumb으로 범위(min-max)를 선택하는 슬라이더
Va.Slider가 단일 값을 조절한다면, Va.DoubleSlider는 두 개의 thumb으로 값 범위를 지정하는 컴포넌트. 낮은 값 thumb + 높은 값 thumb이 있고, 두 thumb 사이가 "선택된 범위"로 강조 표시됩니다. 가격 범위 필터·나이 범위·시간대 범위 같은 UI에 정확히 어울려요.
- 클래스: Va.DoubleSlider — va_slider.js:322
- short name: doubleSlider
- 상속: Va.Div (PureField 아님!)
- 파일: va_slider.js (별도 파일 — import 필요)
- isContainer: true

1. 시작하기 전 — 별도 파일 import
import Va from '../../lib/va.js';
import '../../lib/va_slider.js'; // ← Slider 계열 모두 이 파일에
2. 기본 사용
{
tagName: 'doubleSlider',
lowValue: 30,
highValue: 70,
min: 0,
max: 100,
step: 1
}
- 두 개의 thumb — 낮은 값(30), 높은 값(70)
- 두 thumb 사이 범위 강조 표시
- 각 thumb 위에 값 텍스트 표시
3. Va.Slider / Va.DoubleSlider의 차이
항목Va.SliderVa.DoubleSlider
| thumb 개수 | 1개 | 2개 (낮은 값 + 높은 값) |
| 선택 대상 | 단일 값 | 범위 (from-to) |
| 값 옵션 | value | lowValue + highValue |
| getValue() | 단일 값 | getMinValue() / getMaxValue() 분리 |
| 상속 | PureField | Div (필드 아님) |
| 숫자 입력창 병기 | ✓ | ✕ |
| 접근성 role | slider | 별도 세팅 없음 |
한 줄 요약: "범위 필터를 슬라이더 UI로 표현하는 컴포넌트."
4. 주요 속성
범위 값
속성기본값설명
| lowValue | 30 | 낮은 값 thumb의 초기 위치 |
| highValue | 70 | 높은 값 thumb의 초기 위치 |
| min | — | 최솟값 (전체 rail의 시작) |
| max | — | 최댓값 (전체 rail의 끝) |
| step | — | 증감 단위 |
표시 옵션
속성설명
| visibleCurrentValue | 숫자 입력창 표시 (실질 활용 제한적) |
| visibleMinMax | min/max 라벨 표시 |
| thumbText | thumb 위 값 텍스트 표시 |
| inputWidth | 숫자 입력창 폭 |
색상 커스터마이즈
속성설명
| progressColor | 선택된 범위 색상 |
| railColor | 빈 rail 색상 |
5. 배타 로직 — 두 thumb이 서로 넘지 못하도록
DoubleSlider의 내부에서 자동 처리하는 규칙:
// minRange가 maxRange를 넘어가려 하면 → maxRange 값으로 되돌림
if (Number(minRangeElement.value) > Number(maxRangeElement.value)) {
lowValue = highValue;
minRangeElement.value = maxRangeElement.value;
}
// maxRange가 minRange 아래로 내려가려 하면 → minRange 값으로 되돌림
if (Number(maxRangeElement.value) < Number(minRangeElement.value)) {
highValue = lowValue;
maxRangeElement.value = minRangeElement.value;
}
결과: 사용자가 실수로 낮은 값 thumb을 높은 값 thumb보다 오른쪽으로 드래그해도 서로 밀리지 않고 자동 맞닿음.
6. 이벤트
DoubleSlider는 자체 이벤트가 매우 적습니다. this.events에 명시 추가된 게 없고, 내부적으로 input 이벤트에서 update()만 호출.
실질적으로 개발자가 감지할 이벤트가 부족한 편입니다. 필요하면 component.minRangeElement, component.maxRangeElement에 직접 리스너를 붙여야 합니다:
mounted() {
const ds = this.getRef('range');
ds.minRangeElement.addEventListener('input', (evt) => {
console.log('low:', ds.lowValue);
});
ds.maxRangeElement.addEventListener('input', (evt) => {
console.log('high:', ds.highValue);
});
}
⚠️ 표준 change/input 이벤트를 dispatch하는 코드가 명확히 없어, 실제 프로젝트에선 이 부분 확인 필수. 데모에서 실동작 검증 권장.
7. 메서드
값 조회·세팅
메서드설명
| setMinValue(value) | 낮은 값 thumb 위치 세팅 |
| setMaxValue(value) | 높은 값 thumb 위치 세팅 |
| getMinValue() | 낮은 값 반환 |
| getMaxValue() | 높은 값 반환 |
⚠️ 주의: 소스상 getMinValue() / getMaxValue()는 this.minValue / this.maxValue를 반환하는데, 이 필드는 setMinValue/setMaxValue 호출 시에만 세팅됩니다. 사용자가 thumb을 드래그하면 실제로는 this.lowValue/this.highValue가 갱신되니, 드래그 후 값을 조회하려면 component.lowValue / component.highValue에 직접 접근하는 게 정확합니다.
그 외
메서드설명
| focus() / blur() | 빈 구현 (동작 안 함) |
8. 내부 구조
<div elname="element" class="va-double-slider" ...>
<div elname="inner" class="double-slider-inner">
<span elname="minValue" class="text">0</span> ← min 라벨
<div elname="sliderDiv" class="slider-div">
<div elname="track" class="track"></div> ← 전체 rail
<div elname="range" class="range" ← 선택된 범위 강조
style="left: 30%; width: 40%"></div>
<input elname="minRange" type="range" class="min-range"> ← 낮은 값 slider
<input elname="maxRange" type="range" class="max-range"> ← 높은 값 slider
<div elname="highValueText" class="thumb">70</div> ← 높은 값 텍스트
<div elname="lowValueText" class="thumb">30</div> ← 낮은 값 텍스트
</div>
<span elname="maxValue" class="text">100</span> ← max 라벨
</div>
</div>
핵심 트릭:
- 두 개의 <input type="range">가 겹쳐 있음 — 각각 낮은 값과 높은 값을 담당
- range div가 선택된 범위 강조 — 두 thumb 사이의 배경 채움
- 값 텍스트 위치는 픽셀 단위로 계산 — draw()에서 슬라이더 폭과 텍스트 폭 기준으로 픽셀 계산
9. mounted()의 500ms 지연 렌더링
mounted()에서 _setTimer('render', () => this.draw(), 500)으로 500ms 후 draw() 재실행합니다 (va_slider.js:421-423).
이유: 두 thumb의 텍스트 위치를 정확히 계산하려면 슬라이더 폭(offsetWidth)이 확정되어야 하는데, 초기 렌더 시엔 아직 레이아웃이 안 잡혀 있을 수 있음. 500ms 대기 후 정확한 위치로 다시 그림.
⚠️ 초기 렌더링이 살짝 튀어 보일 수 있음 — 처음엔 잘못된 위치, 500ms 후 정확한 위치. UX상 티가 크게 나진 않지만 인지하고 있어야 함.
10. 언제 쓰나
DoubleSlider가 맞을 때
- 가격 범위 필터 (5만원 ~ 20만원)
- 나이 범위 (20~35세)
- 시간대 범위 (09:00~18:00 같은)
- 날짜 범위 (숫자로 매핑 가능한 것)
- 감각적 범위 선택 (정확한 값 두 개보다 대략적 범위가 더 자연스러운 상황)
다른 걸 쓸 때
- 단일 값 조절 → Va.Slider
- 정확한 두 숫자 입력 → Va.NumberField × 2
- 날짜 범위 → Va.DateRangePicker (별도)
- 시간 범위 → Va.TimeField × 2
- 별점 → Va.StarSlider
11. 흔한 조합 예시
// 가격 범위 필터
{
tagName: 'doubleSlider',
lowValue: 50000,
highValue: 200000,
min: 0,
max: 500000,
step: 10000,
progressColor: '#0f6cbd'
}
// 나이 범위
{
tagName: 'doubleSlider',
lowValue: 20,
highValue: 35,
min: 0,
max: 100,
step: 1
}
// 시간대 (0~24시)
{
tagName: 'doubleSlider',
lowValue: 9,
highValue: 18,
min: 0,
max: 24,
step: 1
}
// 커스텀 색상
{
tagName: 'doubleSlider',
lowValue: 30,
highValue: 70,
min: 0,
max: 100,
progressColor: 'var(--colorBrandStroke1)',
railColor: '#e0e0e0'
}
12. 실전 예 — 가격 범위 상품 필터
class ProductFilter extends Va.View {
mounted() {
// DoubleSlider의 표준 이벤트가 부족하므로 직접 리스너
const ds = this.getRef('price');
const trigger = () => this.applyFilter();
ds.minRangeElement.addEventListener('change', trigger);
ds.maxRangeElement.addEventListener('change', trigger);
}
applyFilter() {
const ds = this.getRef('price');
const low = Number(ds.lowValue);
const high = Number(ds.highValue);
this.getRef('priceLabel').setValue(
`${low.toLocaleString()}원 ~ ${high.toLocaleString()}원`
);
ProductService.search(this, { minPrice: low, maxPrice: high },
(view, ok, res) => {
if (ok) view.getRef('grid').setData(res.data.list);
}
);
}
config() {
return {
tagName: 'page',
tags: [{
tagName: 'panel',
tags: [
{ tagName: 'h2', innerHTML: '가격 범위 필터' },
{
tagName: 'doubleSlider',
ref: 'price',
lowValue: 50000,
highValue: 200000,
min: 0,
max: 500000,
step: 10000
},
{ tagName: 'div', ref: 'priceLabel', innerHTML: '' },
{ tagName: 'grid', ref: 'grid', columns: [/* ... */] }
]
}]
};
}
}
13. 알아두면 좋을 주의사항
- va_slider.js import 필수 — 없으면 인식 못 함.
- Va.Div 상속 — PureField/Field 계열 아님. getValue()/setValue() 관행 없음.
- 표준 이벤트 부족 — change/input 재발화 코드 명확하지 않음. minRangeElement/maxRangeElement에 직접 리스너 부착 권장.
- getMinValue()/getMaxValue() 실사용 주의 — 사용자 드래그 후 값은 lowValue/highValue 필드에. getMinValue가 이 값을 반환하지 않을 수 있음. 실제 확인 필요.
- 초기 500ms 렌더링 지연 — thumb 텍스트 위치 계산 문제로. UX상 미세하게 튀어 보일 수 있음.
- focus()/blur()가 빈 구현 — 프로그램적 포커스 제어 안 됨.
- 두 thumb 배타 로직 자동 — 서로 넘어가지 않게 자동 맞닿음.
- 값은 문자열 — <input type="range"> 특성. 계산 시 Number() 변환.
- 범위 강조 계산이 minVal / 100 * 100 하드코딩 — va_slider.js:446-447. min/max가 0~100이 아닌 경우 강조 표시가 잘못될 수 있음. 확인 필수.
- 접근성 — role="slider" 자동 부여 없음. 스크린리더 지원이 Slider보다 약함.
- 라벨 있는 폼 필드로 쓰려면 Va.DoubleSliderField — 별도 확인 필요.
14. doubleSlider vs 두 numberField vs slider × 2 선택
상황추천
| 범위 감각적 조절 (가격, 나이) | doubleSlider |
| 정확한 두 숫자 입력 | numberField × 2 |
| 단일 값 감각 조절 | slider |
| 날짜 범위 | dateRangePicker |
| 시간대 범위 | timeField × 2 |
"범위를 감각적으로 좁히는 UI가 필요하면 doubleSlider" — 명확한 사용처.
참고
- API 문서 페이지: https://vanillafront.com/docs.html?theme=light#main#apidoubleslider
- 연관: Va.Slider(단일 값 형제), Va.DoubleSliderField(라벨 포함 버전)
'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글
| StarSliderField (스타슬라이더필드) (0) | 2026.09.13 |
|---|---|
| StarSlider (스타슬라이더) (0) | 2026.09.13 |
| SliderField (슬라이더 필드) (0) | 2026.09.12 |
| Slider (슬라이더) (0) | 2026.09.12 |
| RadioGroupField (라디오그룹 필드) (0) | 2026.09.12 |