임통 아이콘

임통 블로그

TimePicker 제작하기

기타

2024년 08월 13일

TimePicker 제작하기의 썸네일

프로젝트를 하던 중 시간을 지정하는 기능을 구현해야 했습니다.
디자이너분께서 전달해준 디자인은 IOS 기기에서 사용하는 시간 선택기 느낌이었습니다.
처음엔 비슷한 디자인의 라이브러리를 찾아 구현하려고 했지만, 맞는 라이브러리가 없어서 직접 구현하기로 하였습니다. 🔥

우선 구현된 결과부터 보여드리겠습니다.
time-pickertime-picker

이제 코드를 살펴봅시다.

우선 오전/오후, 시간, 분의 배열을 생성합니다.

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>

해당 글은 목차가 없습니다.