-
Notifications
You must be signed in to change notification settings - Fork 0
range slider 구현하기
프로젝트 당시에 rsuite
라는 라이브러리를 사용해서 range slider
를 구현했다. rsute
를 사용한 이유는 짧은 시간 내에 내가 원하는 디자인으로 구현하기에 괜찮은 라이브러리라고 생각했기 때문이다.
하지만 rsuite
라이브러리를 설치하고 나서 메인화면의 font가 깨지는 일이 발생했고, range slider
하나 때문에 rsuite
라이브러리를 써야 한다는 게 불필요하다고 판단했다.
따라서 리팩토링 기간 중에 range slider
를 직접 구현하기로 결정했다.
rsuite
라이브러리를 사용했을 때 모습은 아래와 같다.
rsuite
라이브러리를 사용하지 않고 구현한 모습은 아래와 같다.
현재 리팩토링 중인 프로젝트는 TypeScript
와 React
, Recoil
, styled-components
을 사용했다.
앞으로 나올 파일의 구조는 아래와 같다.
📦recoil
┗ 📜SurveyState.ts
-
SurveyState.ts
: recoil을 이용해서 취향설문의 다섯가지 질문에 대한 상태를 관리하는 파일
📦question3
┣ 📜index.tsx
┗ 📜styles.ts
-
index.tsx
:ThirdQuestion
컴포넌트를 포함하고 있고,range slider
와 해당하는label
을 렌더링하는 파일 -
styles.ts
:styled-components
를 이용하여ThirdQuestion
컴포넌트에 대한 스타일을 정의하는 파일
Recoil
의 atom을 사용하여 취향설문의 다섯가지 질문에 대한 상태를 관리하는 파일이다. range slider
를 사용한 부분은 세 번째 설문이라서 해당하는 부분의 코드는 아래와 같다.
// recoil/SurveyState.ts
import { atom } from "recoil";
...
export const thirdState = atom<string>({
key: "thirdState",
default: "",
});
...
-
thirdState
: 세 번째 질문에 대한 상태를 관리 - 빈 문자열("")을 기본값으로 함
styled-components
라이브러리를 사용해서 취향설문의 세 번째 질문에 사용되는 컴포넌트의 스타일을 정의하는 파일이다.
우선 전체 코드는 아래와 같다.
import styled from "styled-components";
import * as colors from "@styles/Colors";
// slider와 label이 포함된 컨테이너의 스타일 정의
export const SliderContainer = styled.div`
border-radius: 3px;
display: block;
flex-direction: row;
align-items: center;
background-color: ${colors.white};
margin-top: 16px;
padding: 20px 70px 16px 70px;
`;
// slider의 스타일 정의
export const Slider = styled.input`
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 14px;
border-radius: 10px;
background: ${(props) => {
const percentage = ((Number(props.value) - 1) / 4) * 100;
return `linear-gradient(to right, ${colors.mainColor} 0%, ${colors.mainColor} ${percentage}%, ${colors.grey[100]} ${percentage}%, ${colors.grey[100]} 100%)`;
}};
&:focus {
outline: none;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 28px;
height: 28px;
background-color: ${colors.white};
border: 1px solid ${colors.grey[300]};
border-radius: 50%;
cursor: pointer;
}
&::-moz-range-thumb {
width: 28px;
height: 28px;
background-color: ${colors.white};
border: 1px solid ${colors.grey[300]};
border-radius: 50%;
cursor: pointer;
}
`;
// 각 label의 컨테이너 스타일 정의
export const Labels = styled.div`
display: flex;
justify-content: space-between;
padding-top: 10px;
`;
// 각 label 스타일 정의
export const Label = styled.span`
position: relative;
cursor: pointer;
display: inline-block;
text-align: center;
width: 40px;
font-family: "SUIT";
font-style: normal;
font-size: 16px;
line-height: 1.5;
`;
4개의 컴포넌트 중 Slider
컴포넌트는 input
태그를 사용하여 사용자에게 시간을 선택할 수 있도록 한다.
Slider
컴포넌트에서 background
프로퍼티가 특히 중요한데, 사용자가 현재 선택한 값에 따라 slider의 배경색이 바뀌어야 하기 때문이다.
background: ${(props) => {
const percentage = ((Number(props.value) - 1) / 4) * 100;
return `linear-gradient(to right, ${colors.mainColor} 0%, ${colors.mainColor} ${percentage}%, ${colors.grey[100]} ${percentage}%, ${colors.grey[100]} 100%)`;
- slider의 배경색을 설정하는 코드
-
props.value
에 따라 슬라이더의 배경색이 선형 그라데이션으로 변경됨-
props.value
가 3이라면 slider의 60% 부분이colors.mainColor
로 채워지고 나머지 40%는colors.grey[100]
으로 채워짐
-
추가적으로 알아야 할 것은 다음과 같다.
-
-webkit-appearance: none;
과appearance: none;
는 브라우저의 기본 스타일을 제거함 -
&:focus { outline: none; }
: slider가 포커스될 때 아웃라인 제거 -
&::-webkit-slider-thumb
: 웹킷 기반의 브라우저(Chrome, Safari)에서 slider의 thumb의 스타일을 적용 -
&::-moz-range-thumb
: 모질라 기반의 브라우저(Firefox)에서 slider의 thumb의 스타일을 적용
세 번째 취향설문을 사용자가 slider로 응답할 수 있도록 구현한 코드다.
interface ThirdQuestionProps {
isOpen: boolean;
}
const ThirdQuestion = ({ isOpen }: ThirdQuestionProps) => {
...
};
- 모든 취향설문은
Accordion
안에 있기 때문에Accordion
이 열렸는지, 닫혔는지가 중요함 - 따라서
isOpen
을 props로 받고 있음
const [mark, setMark] = useRecoilState(thirdState);
useEffect(() => {
if (isOpen && mark === "") {
setMark("1");
}
}, [setMark, isOpen, mark]);
-
useRecoilState
를 사용하여 사용자가 선택한mark
의 상태를 관리 -
useEffect
를 통해isOpen
이true
이고mark
가 빈 문자열일 경우mark
를 "1"로 설정- 즉, 취향설문 페이지가 렌더링되고 처음으로 세 번째 취향설문이 있는 accordion을 열었을 때,
thirdState
값을 "1"로 수정함
- 즉, 취향설문 페이지가 렌더링되고 처음으로 세 번째 취향설문이 있는 accordion을 열었을 때,
const handleSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setMark(event.target.value);
};
- slider의 값이 변경될 때 호출되며,
event.target.value
를 사용하여mark
상태 업데이트
const handleChange = (val: number) => {
setMark(val.toString());
};
- label이 클릭될 때 호출되며, 클릭된 label 값을 사용하여
mark
상태 업데이트
const labels = Array.from({ length: 5 }, (_, i) => i + 1).map((val) => (
<style.Label key={val} onClick={() => handleChange(val)}>
{val}시간
</style.Label>
));
- 1부터 5까지의 label을 동적으로 생성
- 클릭 시
handleChange()
를 호출하여mark
상태 변경
전체 코드는 아래와 같다.
import React, { useEffect } from "react";
import * as style from "./styles";
import { useRecoilState } from "recoil";
import { thirdState } from "@recoil/SurveyState";
interface ThirdQuestionProps {
isOpen: boolean;
}
const ThirdQuestion = ({ isOpen }: ThirdQuestionProps) => {
const [mark, setMark] = useRecoilState(thirdState);
useEffect(() => {
if (isOpen && mark === "") {
setMark("1");
}
}, [setMark, isOpen, mark]);
const handleSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setMark(event.target.value);
};
const handleChange = (val: number) => {
setMark(val.toString());
};
const labels = Array.from({ length: 5 }, (_, i) => i + 1).map((val) => (
<style.Label key={val} onClick={() => handleChange(val)}>
{val}시간
</style.Label>
));
return (
<style.SliderContainer>
<style.Slider
type="range"
min={1}
max={5}
value={parseInt(mark) || 1}
onChange={handleSliderChange}
/>
<style.Labels>{labels}</style.Labels>
</style.SliderContainer>
);
};
export default ThirdQuestion;
range slider
를 직접 구현하면서 프로젝트 당시에는 구현하지 못했던 것을 지금은 구현할 수 있게 된 모습이 뿌듯하다. 원래는 모듈화를 하려고 했으나 한 번만 사용하는데 모듈화를 하는 것은 불필요하다고 판단했다. 프로젝트에서 컴포넌트의 쓰임을 보고 모듈화 여부를 결정하는 게 좋은 방향성이라는 것을 배웠다.
Copyright 2023. SSAFY 8th Muyeochu Team All rights reserved.