[ 개인플젝 ] 홈 - dayjs로 캘린더 그리기 기록

김보람's avatar
Jan 09, 2026
[ 개인플젝 ] 홈 - dayjs로 캘린더 그리기 기록

난 개인프로젝트로 시즌랭킹 인증앱을 작업중이다.

오늘은 홈 화면을 작업한다


홈에서 요하는 기능

  • 상단 운동하기 버튼을 누르면 인증 선택 버튼이 그 위에뜨며, 다른 영역을 선택시 인증 선택 버튼이 사라진다

  • 캘린더가 그려져 있고 인증한 날짜에는 날짜 대신 사진이 온다

  • 프로그레스박스에는 시즌에 맞는 날짜를 박스로 그리고 인증한 날만 색이 칠해진다 (깃헙 잔디 따라함)


1. 버튼 모달 출력 후 다른 영역 누를시 사라짐

나는 웹을 작업할 때 이런 백그라운드에 이벤트가 있는 작업들을 할때 그냥 백에다가 전체 화면을 투명하게 띄우고 거기에 이벤트를 넣는 편이었다.

하지만 RN에서는 TouchableWithoutFeedback 을 이용하면 편하게 작업할 수 있다.

TouchableWithoutFeedback은 시각적 피드백 없이 단순히 터치 이벤트만 처리하고 싶을 때 사용하며 보통은 키보드 비활성화시 사용한다

아래의 형태로 사용했다. 버튼은 HomeTopContents 내에 있고 TouchableWithoutFeedback가 부모로서 감싸고 있는 형태로 닫는 함수를 가지고 있다

 <ScrollView showsVerticalScrollIndicator={false} bounces={false}>
      <TouchableWithoutFeedback onPress={모달닫는 함수}>
        <View >
          <HomeTopContents
            authMethodSelectOpen={authMethodSelectOpen}
          />

⚠️ 여기서 하나 기록하고 넘어가자면 TouchableWithoutFeedback 내에 ScrollView가 들어올경우 터치 이벤트가 무시되는 것처럼 보여지게 된다

둘을 부모자식 관계로 사용해야하는 관계는 되도록 피하고
관계성 없이 영역을 따로 가지던 아니면 ScrollView를 부모로 가져야 두 기능 다 수행할 수 있다.


2. dayJS로 캘린더 모양 그리기

  1. 캘린더는 일요일 → 토요일 순으로 날짜가 흘러간다

  2. [ 일, 월, 화, 수, 목, 금, 토 ] 의 7개의 컬럼을 가진다

  3. 1일은 요일에 맞는 날부터 시작한다

  4. 1일 앞에 남는 요일이 있다면 빈칸으로 채운다

  5. 마찬가지로 마지막날(31일 등)뒤에 남는 요일이 있다면 빈칸으로 채운다

위의 조건을 가지고 그리면 된다

const createCalendarDays = (year: string, month: string) => {
  const daysInMonth = dayjs(`${year}-${month}`).daysInMonth(); 
  const firstDayOfMonth = 				     
	dayjs(`${year}-${month}-01`).startOf('month'); 
  const calendarDays: CalendarDay[] = [];

  for (let i = 0; i < firstDayOfMonth.day(); i++) {
    calendarDays.push({ type: 'empty', date: null });
  }

  for (let i = 1; i <= daysInMonth; i++) {
    const date = dayjs(`${year}-${month}-${i}`);
    calendarDays.push({
      type: 'day',
      date: i,
      isCurrentMonth: true,
      year: date.year(), // 2025
      month: date.month() + 1, // 1~12 (주의)
      day: date.date(), // 1~31
      fullDate: date.format('YYYY-MM-DD'),
    });
  }

  const remainder = calendarDays.length % 7;

  if (remainder !== 0) {
    const emptyCount = 7 - remainder;
    for (let i = 0; i < emptyCount; i++) {
      calendarDays.push({ type: 'empty', date: null });
    }
  }

  return calendarDays;
};
  • daysInMonth 는 해달 달의 일 수

  • firstDayOfMonth.day() 는 1일의 요일

    • dayjs().day()는 0(일) ~ 6(토)으로 반환된다

  • calendarDays 그려질 배열을 준비하고

  • 날짜 앞쪽 빈칸 채우기

    • 0부터 1일의 요일 만큼 빈칸을 채운다

    for (let i = 0; i < firstDayOfMonth.day(); i++){}

  • 날짜를 채운다

    for (let i = 1; i <= daysInMonth; i++) {}

  • 날짜 뒷쪽 빈칸 채우기

    • remainder에 현재까지 쌓인 날짜를 7로 나눈 나머지를 할당한다

    • remainder값이 0이 아닐경우에 뒤에 영역이 남았다고 판단

    • 7 - remainder 값을 반복하여 빈칸을 채운다

     const remainder = calendarDays.length % 7;
    
     if (remainder !== 0) {
        const emptyCount = 7 - remainder;
        for (let i = 0; i < emptyCount; i++) {}
     }

  • 그려질 데이터가 쌓인 calendarDays를 리턴한다


이제 calendarDays는 빈칸과 날짜 모두를 가지고 있으므로 그대로 그리면 된다.

처음에는 FlatList를 이용해 그렸었다 하지만 홈은 ScrollView로 감싸져 있어 워닝이 발생했다. FlatList + FlatList을 사용할 수 있었지만 데이터가 많지 않아 굳이 FlatList를 고집할 이유가 없으므로 맵으로 그려줬다

{calendarDays.map((item, index) => (
    <CalendarCell
      item={item}
      index={index}
      totalLength={calendarDays.length}
      recordMap={recordMap}
      key={item.date ?? index}
     />
  ))}

셀의 사이즈는

  • 사용될 영역을 7로 나누어 7컬럼을 유지하도록 하고

  • 난 4(세로) : 3(가로)의 비율로 그려지길 원했기 때문에 아래와 같이 사용하여 셀을 그렸다

 const CELL_WIDTH = (width - 40 - inset * 6) / 7;
 const CELL_HEIGHT = CELL_WIDTH * (4 / 3);

Share article

김보람 | 930802qhfka@gmail.com