커스텀 범위 슬라이더 (Range Slider) 구현하기

Minha Ahn·2024년 8월 14일
0
post-thumbnail

🔍범위 슬라이더 (Range Slider)란?

범위 슬라이더는 사용자가 특정 범위 내에서 최소, 최대값을 선택할 수 있는 UI 요소이다. 보통 볼륨, 밝기 조절할 때 해당 UI가 사용된다.

이건 나의 노트북의 볼륨, 밝기 조절 슬라이더이다. 이런 게 바로 범위 슬라이더이다.

💻범위 슬라이더 구현

▶ input 태그 range 타입

범위 슬라이더는 <input> 태그의 range 타입을 이용해 구현할 수 있다. 해당 타입을 사용하면 슬라이드 바를 조정하여 범위 내의 숫자를 선택할 수 있는 입력 필드가 정의된다.

참고

이 외에도 <input> 태그에는 정말 다양한 type들이 존재한다. 타입에 종류들은 여기서 확인할 수 있다. => input 태그 type 종류

▶ range 타입과 함께 쓰이는 속성

range 타입을 사용하게 되면 함께 사용 가능한 다른 속성들도 있는데, 바로 아래와 같다.

  • max : 범위의 최대값
  • min : 범위의 최소값
  • step : 입력 가능한 숫자 사이의 간격
  • value : 초기값

이 모든 속성들을 적용하면 아래와 같다.

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 구현

그렇다면 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을 타원형으로 변경
  • thumb 위에 현재 score 출력

▶ track 디자인 변경

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 디자인 변경

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 값 출력하는 건 어렵지 않으나, 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이라는 숫자는 내가 여러번 테스트하면서 가장 매끄럽게 보여지는 숫자라 정했을 것이다.

일단 위에서와는 다르게 제일 왼쪽이 아닌 가운데를 기준으로 좌, 우 계산을 다르게 진행했다. 우측은 영역 밖으로 빠져나가지 않도록 점점 더 큰 값을 빼는 방식으로 좌측은 우측과 반대의 계산을 할 수 있도록 구현했던 것 같다. 이렇게 하고 디자인도 완벽하게 적용해 다음과 같이 완성했다.

참고 링크

profile
프론트엔드를 공부하고 있는 학생입니다🐌

0개의 댓글