프로젝트를 하던 중 시간을 지정하는 기능을 구현해야 했습니다.
디자이너분께서 전달해준 디자인은 IOS 기기에서 사용하는 시간 선택기 느낌이었습니다.
처음엔 비슷한 디자인의 라이브러리를 찾아 구현하려고 했지만, 맞는 라이브러리가 없어서 직접 구현하기로 하였습니다. 🔥
우선 구현된 결과부터 보여드리겠습니다.
이제 코드를 살펴봅시다.
우선 오전/오후, 시간, 분의 배열을 생성합니다.
const periods = [100].concat(
Array.from({ length: 2 }, (_, index) => index),
[101]
);
const hours = [100].concat(
Array.from({ length: 12 }, (_, index) => index),
[101]
);
const minutes = [101].concat(
Array.from({ length: 6 }, (_, index) => index),
[102]
);
그리고 스크롤에 맞출 높이값을 계산합니다.
const ITEM_HEIGHT =
innerWidth >= 520 ? 53.19 : ((2.7778 * innerWidth) / 100) * 3.8;
// 항목의 세로 너비를 3.8rem으로 설정했습니다.
저의 경우에는 최대 가로 너비가 520px인 rem
을 사용한 반응형 웹을 제작했기 때문에 위와 같이 세로 너비를 계산했습니다.
다음은 스크롤 이벤트 함수입니다.
const handleScroll = (
event: React.UIEvent<HTMLUListElement>,
dataArray: number[],
type: "period" | "hour" | "minute",
multiplier: number,
refElement: React.RefObject<HTMLUListElement>
) => {
const scrollTop = event.currentTarget.scrollTop;
if (!isScrolling) {
setIsScrolling(true);
}
const calculatedValue = Math.round(scrollTop / ITEM_HEIGHT);
if (dataArray.includes(calculatedValue)) {
const finalValue =
type === "hour" ? calculatedValue + 1 : calculatedValue * multiplier;
setTime(type, finalValue);
}
if (refElement.current) {
const listElement = refElement.current as HTMLUListElement & {
_scrollTimeout?: number;
};
// 스크롤 시 스크롤값을 바탕으로 가장 가까운 값에 스크롤을 위치시킵니다.
// smooth는 부드럽게 이동하기 위함입니다.
clearTimeout(listElement._scrollTimeout as number);
listElement._scrollTimeout = window.setTimeout(() => {
listElement.style.scrollBehavior = "smooth";
listElement.scrollTop = calculatedValue * ITEM_HEIGHT;
setTimeout(() => {
listElement.style.scrollBehavior = "auto";
setIsScrolling(false);
}, 300);
}, 100);
}
};
period, hour, minute 세 스크롤 타입에 모두 적용할 수 있도록 작성했습니다.
마지막으로 UI를 구성하는 jsx코드입니다.
hour의 예시입니다. period와 minute도 비슷한 방식으로 할 수 있습니다.
<ul
className="h-[11.4rem] overflow-y-scroll hide-scrollbar w-full"
onScroll={(e) => handleScroll(e, hours, "hour", 1, hoursRef)}
ref={hoursRef}
>
{hours.map((hour) => {
if (hour >= 100) {
return <li key={hour} className="h-[3.8rem]" />;
}
const realHour = hour + 1;
return (
<li
key={realHour}
className="h-[3.8rem] flex items-center justify-center"
>
<p className="text-body1">
{realHour.toString().length === 1
? "0" + realHour.toString()
: realHour}
</p>
</li>
);
})}
</ul>