범위 슬라이더는 사용자가 특정 범위 내에서 최소, 최대값을 선택할 수 있는 UI 요소이다. 보통 볼륨, 밝기 조절할 때 해당 UI가 사용된다.
이건 나의 노트북의 볼륨, 밝기 조절 슬라이더이다. 이런 게 바로 범위 슬라이더이다.
범위 슬라이더는 <input>
태그의 range
타입을 이용해 구현할 수 있다. 해당 타입을 사용하면 슬라이드 바를 조정하여 범위 내의 숫자를 선택할 수 있는 입력 필드가 정의된다.
이 외에도 <input>
태그에는 정말 다양한 type들이 존재한다. 타입에 종류들은 여기서 확인할 수 있다. => input 태그 type 종류
range
타입을 사용하게 되면 함께 사용 가능한 다른 속성들도 있는데, 바로 아래와 같다.
이 모든 속성들을 적용하면 아래와 같다.
const [score, setScore] = useState(50)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { ... }
return (
<input
type="range"
value={score}
min={0}
max={100}
step={1}
onChange={handleChange}
/>
)
위와 같이 작성하면 기본적인 형태의 슬라이더가 생성된다. 다만, 아직 onChange
속성을 구현하지 않았기 때문에 조절이 불가능하다.
그렇다면 onChange
는 어떻게 구현해야 할까? 별 거 없다! 변경되는 value값을 반영해주면 된다.
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setScore(e.target.value)
}
단, 아래와 같이 구현하면 에러가 발생한다. score
에는 숫자값이 들어와야 하나, e.target.value
는 문자열로 들어오기 때문이다.
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setScore(Number(e.target.value))
}
이렇게 문자열을 숫자로 바꿔주면 문제가 없다. 그치만 조금 더 세련된(?) 방법이 있다. .target.valueAsNumber
가 바로 그것이다.
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setScore(e.target.valueAsNumber)
}
여기까지 완료하면 기본적인 구현은 끝이 났다.
내가 구현해야 하는 범위 슬라이더는 아래의 이미지와 같다.
변경해야 하는 디자인은 다음과 같다.
track은 슬라이더가 움직이는 긴 막대기를 의미한다.
기존 디자인과 다르게 전체적으로 그라이데이션 색상을 적용해야 하고, thumb을 기준으로 왼쪽과 오른쪽의 색상 변화가 필요없다.
디자인 변경은 생각보다 어렵지 않다. 브라우저 내 기본적으로 적용되는 디자인을 무효화하고 내가 원하는 디자인 코드를 적용시키면 된다.
내가 적용한 디자인 코드는 다음과 같다. (styled-components 코드이다)
input {
// 기존 디자인 삭제
// 크로스 브라우저를 위해 webkit(구글, 사파리), moz(파이어폭스) 접두어 사용
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: none;
// 원하는 디자인 적용
width: 60%;
height: 16px;
border-radius: 15px;
background: linear-gradient(-90deg, #915afb 0%, #f4efff 100%);
}
thumb은 track위에 있는 점을 의미한다.
기존 디자인보다 전반적으로 크기가 커야하고 타원형으로 구성되어야 한다.
thumb 디자인 변경도 track과 유사하나 -webkit-slider-thumb
과 -moz-range-thumb
을 사용해야 한다는 점만 유의하면 된다.
input::-webkit-slider-thumb {
// 기존 디자인 삭제
-webkit-appearance: none;
appearance: none;
border: none;
// 원하는 디자인 적용
width: 71px;
height: 38px;
background: #915afb;
border-radius: 24px;
}
&::-moz-range-thumb {
// 기존 디자인 삭제
border: none;
// 원하는 디자인 적용
width: 71px;
height: 38px;
background: #915afb;
border-radius: 24px;
}
score 값 출력하는 건 어렵지 않으나, score 값이 thumb 위에 올라가야하는 부분에서 신경써야 할 점이 있다.
일단 score가 보이도록 구성을 추가해보겠다.
return (
<Container>
<span style={{ left: `${score}%` }}>{score}점</span>
<input type="range" value={score} min={0} max={100} step={1} onChange={handleChange} />
</Container>
);
const Container = styled.div`
position: relative;
width: 500px;
span {
position: absolute;
transform: translateY(-17%);
${({ theme }) => theme.font.body1};
color: black;
background: transparent;
text-align: center;
width: 71px;
pointer-events: none;
}
...
`;
styled-components를 이용해서 score값이 보이는 위치를 조금 조정해준 상태이다. score값이 보여지는 위치는 현 score값을 width의 %로 변환해 보여지도록 했다. 그러면 thumb을 움직일 때 이렇게 될 것이다!
score 값이 커질수록 글씨가 밖으로 탈출해버린다. 그래서 내가 머리를 쓴 방법은 이 방법이다.
return (
<Container>
<span
style={
score <= 50
? { left: `calc(${score}% - ${score * 0.71}px)` }
: { right: `calc(${100 - score}% - ${(100 - score) * 0.71}px)` }
}
>
{score}점
</span>
<input type="range" value={score} min={0} max={100} step={1} onChange={handleChange} />
</Container>
);
구현하고 나서 오랜만에 글로 작성한 것이기 때문에 기억이 잘 안나긴 하지만 아마 저기에 작성된 0.71
이라는 숫자는 내가 여러번 테스트하면서 가장 매끄럽게 보여지는 숫자라 정했을 것이다.
일단 위에서와는 다르게 제일 왼쪽이 아닌 가운데를 기준으로 좌, 우 계산을 다르게 진행했다. 우측은 영역 밖으로 빠져나가지 않도록 점점 더 큰 값을 빼는 방식으로 좌측은 우측과 반대의 계산을 할 수 있도록 구현했던 것 같다. 이렇게 하고 디자인도 완벽하게 적용해 다음과 같이 완성했다.