네이티브 이중 범위 입력
(muffinman.io)@stanko/dual-range-input은 두 개의 네이티브 HTML range input을 조합해 이중 범위 슬라이더를 만들며, 브라우저 기본 상호작용과 접근성을 그대로 활용함- 구현은 입력값을 동기화하는 약 50줄의 JavaScript와 CSS가 중심이라, 드래그와 접근성을 직접 다시 만드는 기존 방식보다 단순함
- 두 입력은 선택값의 중간점에 맞춰
min,max, 너비가 갱신되며, 트랙 클릭 시 더 가까운 thumb가 해당 값으로 이동함 - range input 트랙은 실제 입력 너비보다 짧기 때문에, thumb 너비를 계산에 더하고 wrapper padding으로 초과 너비를 흡수함
- 테마는 CSS 변수와 gradient로 조정할 수 있어, 선택 범위 색상·thumb 크기·track 색상 등을 오버라이드 가능함
네이티브 range input으로 만든 이중 슬라이더
@stanko/dual-range-input은 두 개의 HTML range input을 나란히 배치해 이중 범위 입력을 구현함- 여기서 “네이티브”는 단일 브라우저 컨트롤이라는 뜻이 아니라, 두 range input을 사용해 브라우저의 기본 상호작용과 접근성 기능을 보존한다는 뜻임
- 구현은 입력을 동기화하는 약 50줄의 JavaScript와 시각적 완성도를 위한 CSS로 구성됨
- 시작점은 생성형 드로잉 도구에서 파라미터의 최솟값과 최댓값을 조정하는 UI가 필요했던 것임
- 요구사항은 두 가지였음
- 네이티브 HTML range input을 사용할 것
- 트랙을 클릭하면 두 thumb 중 클릭 지점에 더 가까운 쪽이 해당 값으로 이동할 것
값 동기화와 클릭 처리
- 두 입력 중 하나가 바뀌면 라이브러리는 선택된 두 값 사이의 중간점을 계산함
- 각 입력의
min과max속성을 중간점에 맞춰 설정함 - 두 입력의 너비도 중간점 기준으로 갱신함
- 각 입력의
- 입력들이 중간점에서 만나도록 크기가 조정되므로, 두 thumb 사이를 클릭하면 클릭 위치에 가까운 thumb가 이동함
- 두 thumb 사이 step 수가 홀수이면 마지막으로 사용한 입력이 우선됨
CSS 계산과 스타일링
- range input의 트랙은 전체 입력 너비보다 짧고, 브라우저는 thumb가 바깥으로 튀어나오지 않도록 양옆 공간을 남김
- 입력 너비를 단순히 25%와 75%처럼 나누면 실제 트랙 비율이 맞지 않음
- 구현은 각 입력 너비에
--dri-thumb-width를 더하고, wrapper에 padding을 추가해 초과 너비를 수용함
- 스타일링은 브라우저의 range input CSS 스타일링 기능을 사용함
- track과 thumb를 꾸미고, 두 track이 만나는 가운데 부분에는 border radius가 생기지 않도록 처리함
- 테마는 CSS 변수 오버라이드로 만들 수 있음
- 주요 변수에는
--dri-height,--dri-thumb-width,--dri-thumb-color,--dri-track-color,--dri-track-filled-color,--dri-track-border-radius등이 있음
- 주요 변수에는
- 선택된 범위는 CSS gradient로 칠함
- gradient 위치는
--dri-gradient-position변수로 지정함 - 코드에서 입력 너비를 갱신할 때 이 변수도 함께 갱신함
- gradient 위치는
- 이 구현은 커스텀 라이브러리로 드래그와 접근성을 다시 만드는 대신, 가능한 한 네이티브 요소를 먼저 활용하는 접근임
댓글과 토론
Hacker News 의견들
-
이런 종류의 슬라이더는 가운데 영역도 항상 드래그 가능해야 한다고 봄. 즉 가운데를 잡으면 양쪽 핸들이 동시에 움직이는 방식이어야 함
참고로 Unity는 이렇게 처리함: https://docs.unity3d.com/StaticFiles/ScriptRefImages/EditorG...- 반대로, 나는 가운데 영역을 클릭해볼 생각을 전혀 못 했을 듯함. 머릿속에서는 이게 완전히 새로운 위젯이 아니라 변형된 슬라이더로 보였기 때문임
“항상”은 조금 과한 표현일 수 있음
- 반대로, 나는 가운데 영역을 클릭해볼 생각을 전혀 못 했을 듯함. 머릿속에서는 이게 완전히 새로운 위젯이 아니라 변형된 슬라이더로 보였기 때문임
-
작성자임. HN에 올라와서 반가움
여기서 이슈와 버그 리포트를 남기기 시작한 걸 봤고, 여긴 시간이 늦어서 내일 살펴보려고 함
생성형 그림 작업에 필요해서 만들었고, 숫자를 입력하는 것보다 슬라이더를 드래그하면서 이미지가 어떻게 바뀌는지 보는 걸 더 선호함
최대한 미니멀하게 유지하려고 노력했음- 이 글이 정말 좋았음. 아침 커피를 마시며 계산 과정과 디버깅 모드 설명을 재미있게 봤고, 네이티브 컴포넌트를 영리하게 활용한 방식이라고 느낌
지금은 SwiftUI를 쓰고 있는데 직접 컴포넌트를 만들기가 거의 너무 쉬운 수준임. 하지만 작성자가 설명했듯 그게 항상 최선은 아니고, 네이티브 컴포넌트는 기능이 꽤 충실한 경우가 많음
iOS의 Firefox라는 다소 특이한 브라우저에서도 글을 확인해봤는데 잘 동작했음. 아주 잘 만들었음
- 이 글이 정말 좋았음. 아침 커피를 마시며 계산 과정과 디버깅 모드 설명을 재미있게 봤고, 네이티브 컴포넌트를 영리하게 활용한 방식이라고 느낌
-
직접 써보다가 바로 버그를 만남. 슬라이더를 100 - 100으로 드래그한 뒤 99 - 99로 만들려고 하면 안 됨
99 - 100에서도 갈 수 없고, 98 - 99에서도 갈 수 없음. 99 - 99로 가는 유일한 방법은 하한을 98보다 더 낮게 끌어내리는 것임
98 - 99 상태에서 97 - 99로 내리면 그때는 99 - 99로 갈 수 있음
이 버그는 어디서든 발생할 수 있고, 99 - 99가 슬라이더 끝에 가까워서 가장 쉽게 재현됐을 뿐임. 일반적으로 어떤 X - X 상태에서 한쪽 끝을 1단계 움직이면, 다른 쪽 끝을 그 빈틈 안으로 끌어넣을 수 없음. 그 간격을 닫으려면 먼저 다른 쪽 끝을 더 멀리 끌어야 함- 잘 찾았음! 사실 이 경우를 생각해뒀고 Chrome과 Firefox에서는 동작하는데, Safari는 마우스 다운에서 focus 이벤트를 발생시키지 않고 슬라이더를 움직이기 시작해야 발생시키는 것 같음
리포트 고맙고 꼭 수정하겠음 - 데스크톱 Linux의 Chromium에서는 100-100 → 99-100 → 99-99가 잘 됨
대신 다른 시각적 버그가 있음. 23-25에서 시작해서 24-25로 드래그하면 25에 있는 오른쪽 손잡이가 몇 픽셀 움직임. 왼쪽 손잡이를 다시 23으로 돌리면 오른쪽 손잡이도 다시 돌아감 - 그래서 내가 개발자 도구 만드는 걸 좋아함
- 잘 찾았음! 사실 이 경우를 생각해뒀고 Chrome과 Firefox에서는 동작하는데, Safari는 마우스 다운에서 focus 이벤트를 발생시키지 않고 슬라이더를 움직이기 시작해야 발생시키는 것 같음
-
멋짐! HTML input에는 늘 두 값 범위 입력 구현이 필요하다고 생각했는데, 상당히 말이 되기 때문임
예를 들어 “위젯이 최소 X개 이상, 최대 Y개 이하인 제품을 필터링하고 싶다” 같은 사용 사례가 있음
다만 Firefox에서 강조하고 싶은 버그를 찾았음. 최소 또는 최대 핸들을 클릭할 때마다 값이 한 단계 위나 아래로 바뀜
핸들의 정확한 중앙을 클릭하려고 노력하면 값이 바뀌지 않지만, 살짝 왼쪽을 클릭하면 선택값이 한 단계 감소하고 오른쪽을 살짝 클릭하면 증가함
버튼을 누른 채 마우스 포인터를 실제로 움직이는 명시적인 변경 동작을 했을 때만 값이 바뀌는 게 기대 동작임- 솔직히 그건 버그가 아니라고 봄. 모든 예제가 꽤 촘촘하고, 손잡이가 트랙의 여러 단계를 덮고 있음
Firefox는 그런 클릭을 트랙을 직접 클릭한 것처럼 해석함. 이 라이브러리는 거기에 전혀 개입하지 않고, 그냥 Firefox의 동작 방식임
- 솔직히 그건 버그가 아니라고 봄. 모든 예제가 꽤 촘촘하고, 손잡이가 트랙의 여러 단계를 덮고 있음
-
키보드로 동작한다는 점이 훌륭함. 제대로 동작해서 Shift-Tab을 누르고 있으면 뒤로 이동할 수도 있음
정말 잘 만들었고, 아주 작은 부분이지만 그 상호작용을 정확히 구현해준 점이 고마움 -
우리 회사 디자인 시스템에서도 인턴이 이런 식의 나란히 배치한 개념 증명을 만들었지만, 한 input의 픽셀이 조금씩 튀는 현상이 계속 있었고 결국 제대로 만들지 못했음
그래서 두 번째 인턴이 겹친 input range 2개를 쓰는 해결책을 냈음 -
범위 입력이 필요할 때마다 jQuery UI 슬라이더를 썼고, 슬라이더 하나 때문에 페이지에 jQuery와 jQuery UI를 추가해야 했음. 이건 정말 좋음
수정: ESM 모듈 대신 바로 쓸 수 있는 일반 JS 파일을 미리 만들어줄 가능성이 있을까?- 이 파일을 가져온 뒤 끝의
"export default"줄을 제거하면 됨
https://cdn.jsdelivr.net/npm/@stanko/dual-range-input@0.9.8/...
CSS도 잊지 말 것
https://cdn.jsdelivr.net/npm/@stanko/dual-range-input@0.9.8/...
- 이 파일을 가져온 뒤 끝의
-
버그를 여기 보고해도 되는지 모르겠음. 첫 두 예제의 핸들은 터치 상호작용을 가로채서 스크롤을 막음. 뒤의 세 예제는 그렇지 않음
그 부작용으로 손가락 아래의 라벨을 보이게 하려고 위로 드래그할 수 없고, 페이지가 스크롤되어 라벨이 계속 손가락 아래에 남음- 재현 가능하면 GitHub 이슈를 열어주면 좋겠음 [1]
어떤 브라우저와 운영체제를 쓰는지 궁금함. 이 라이브러리는 네이티브 input을 사용하기 때문에 스크롤 차단과 관련해서 아무것도 하지 않음
내 iOS 휴대폰에서는 스타일 적용 여부와 관계없이 모든input[type=range]요소에서 스크롤이 차단됨
[1] https://github.com/Stanko/dual-range-input/issues
- 재현 가능하면 GitHub 이슈를 열어주면 좋겠음 [1]
-
“네이티브”라는 부분은 논의 여지가 있음. 라이브러리가 두 개의 네이티브 HTML range input을 사용하므로 네이티브 상호작용과 접근성 기능이 보존된다는 뜻이라고 하지만, 내 기준은 명확함
의도한 대로 동작하는 데 JavaScript가 필요하면 더 이상 네이티브가 아님- 동의함. 보통도 그렇게 쓰지 않나 싶음
그래도 작성자가 의도한 바는 이해됨. JS로 움직이는 div 덩어리를 직접 구현하는 대신 일반 슬라이더를 조합했기 때문에 네이티브 해법에 훨씬 가까워졌음. 영리한 CSS로 JavaScript를 대체할 수 없다면 아마 가능한 한 가장 가까운 방식일 듯함
네이티브인지 아닌지와 별개로, 이런 위젯은 사용할 수 있으면 매우 유용하고 그 영역에는 구현체가 부족했음 - 보통은 그렇지만 완전히 선명하게 나뉘지는 않음. 요소는 네이티브지만, 모달 상태로 열려면 JavaScript를 써야 하는 경우가 있음
popover속성이 나오기 전에는 JavaScript 없이는 아예 열 수 없었음
또 폼 검증처럼 브라우저 내부 검증 도구를 전혀 쓰지 않는 라이브러리와는 구분해서 볼 만한 네이티브 바닐라 JavaScript 메서드들도 있음
- 동의함. 보통도 그렇게 쓰지 않나 싶음
-
https://spodj.intermer.net/를 만들 때 이런 걸 찾고 있었음
필요한 기능을 얻으려고 React를 엉성하게 써야 했는데, 내가 프런트엔드 개발을 잘 못해서 그렇게 됐음