TimePicker 제작하기

현성·2024년 9월 8일
0

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

우선 구현된 결과부터 보여드리겠습니다.
time-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>
profile
👈🏻 매일 꾸준히 성장하는 개발자 !

0개의 댓글