개발/FE

React로 달력 만들기

kkap999 2022. 8. 19. 22:46
728x90

달력을 만들어보겠습니다.

필요한 변수는 다음과 같습니다.

- date: 달력에 표시해주기 위한 날짜 정보를 담은 변수
- first: 달력이 시작할 때 앞에 남는 부분

- last: 달력 끝날 때 뒤에 남는 부분

- month: first와 last를 제외한 달력의 날짜를 표시할 부분

 

코딩 시작!

1. 변수 설정

필요한 변수들을 초기화해줍니다.
달력이 켜질 때 오늘 날짜 기준으로 표시해줄 수 있도록 date는 new Date()를 초기값으로 설정해주었습니다.

  const dayOfWeek = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
  const [date, setDate] = useState(new Date());
  const [first, setFirst] = useState([]);
  const [last, setLast] = useState([]);
  const [month, setMonth] = useState([]);

달력에 표시할 날짜가 정해졌으면 first, last, month에 들어갈 값도 설정해줘야합니다.
다음과 같은 메서드 두 개를 정의해주었습니다.

1) month값 초기화 : setDays()

  const setDays = () => {
    let arr = new Array(
      new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(),
    );

    for (let i = 0; i < arr.length; i++) {
      arr[i] = i + 1;
    }

    setMonth(arr);
  };

new Date()를 이용하여 날짜를 생성해 줄 때, 일에 들어갈 값이 0이 되면 그 이전 달의 마지막 날을 가르키게 됩니다.
월의 자리에 date.getMonth() + 1하여 월 값을 다음 달로 밀어주면
getDate()를 통해 현재 월의 마지막 날의 일을 알 수 있게 됩니다.
이렇게 얻어온 값들을 배열에 넣어 month 변수에 저장해줍시다.

2) first, last값 초기화 : drawCalendar()

  const drawCalendar = () => {
    const year = date.getFullYear();
    const month = date.getMonth();
    const diffFirst = new Date(year, month, 1).getDay(); // 매 월 1일의 요일 -> 0 : 일요일, 6: 토요일
    const lastDay = new Date(year, month + 1, 0).getDate(); // 현재 달의 마지막 일
    const diffLast = (7 - ((lastDay + diffFirst) % 7)) % 7; // 달력에 마지막 채워지는 칸들

    const first = new Array(diffFirst);
    for (let i = 0; i < diffFirst; i++) {
      first[i] = new Date(year, month, -diffFirst + i + 1).getDate();
    }
    const last = new Array(diffLast);
    for (let i = 0; i < diffLast; i++) {
      last[i] = new Date(year, month, i + 1).getDate();
    }

    setFirst([...first]);
    setLast([...last]);
  };

  - first 구하기
앞의 빈 칸을 계산하기 위해서는 1일이 무슨 요일인지 알아야 합니다.
getDay() 함수를 이용하여 요일을 구해봅시다.
반환값은 일요일의 경우 0, 토요일의 경우 6입니다.
일요일이 달력의 맨 앞에 위치하기 때문에, diffFirst의 크기를 그대로 first의 크기로 사용해줄 수 있습니다.

이제 거기에 들어갈 값을 구해봅시다.
이전 달의 최대 일 수는 new Date()로 생성할 때 일의 값을 0으로 하면 구할 수 있었습니다.
그러면 first배열의 최대 크기부터 이런 식으로 채워주면 되겠죠?

이 경우 diffFirst는 3이고 배열이 -2, -1, 0으로 채워져야 하기 때문에 배열 값은 -diffFirst + i + 1로 채워줄 수 있습니다.

- last 값 구하기
last를 제외한 달력에 채워지는 칸의 개수는 현재 달의 마지막 날 수 + First로 채워지는 칸의 수 이기 때문에 lastDay+diffFirst입니다.
이 중에서 마지막 날에 들어갈 칸 수만 구하면 (lastDay+diffFirst)%7입니다.
달력은 한 줄에 7칸씩 들어가니 7에서 이를 뺀 값을 7로 나눠주면 last값을 구할 수 있겠죠?
하지만 달력이 딱 떨어지는 경우 위의 계산결과가 0이 될 것입니다. 이 때 last가 7이 되어버리기 때문에 이러한 상황을 위하여 전체 결과를 7로 한 번 더 나눠줍니다. 

 

이러한 계산결과를 월이 변경될 때 마다 바꿔줘야 하니 useEffect를 통해 date값이 변경될 때 마다 변수값을 바꿔줍니다.

  useEffect(() => {
    setDays();
    drawCalendar();
  }, [date]);

 

2. 월 바꾸기

월 바꾸기는 간단합니다.
이벤트가 일어날 때 마다 월값을 하나 앞으로 보내거나 하나 뒤로 밀면 됩니다.

  const onHandleDecreaseMonth = () => {
    // 한 달 앞으로
    setDate(new Date(date.getFullYear(), date.getMonth() - 1, date.getDate()));
  };

  const onHandleIncreaseMonth = () => {
    // 한 달 뒤로
    setDate(new Date(date.getFullYear(), date.getMonth() + 1, date.getDate()));
  };

 

3. 달력 그리기

이제 배열들을 map함수로 그려주기만 하면 끝입니다.

    <>
      <CalendarWrapper>
        <NextButton
          onClick={onHandleDecreaseMonth}
          direction="left"
          color="#3a578c"
          hoverColor="#2b4169"
          activeColor="#1a2740"
        />
        <CalendarTitle>
          {date.getFullYear()}.{" "}
          {date.getMonth() + 1 < 10
            ? "0" + (date.getMonth() + 1)
            : date.getMonth() + 1}
        </CalendarTitle>
        <NextButton
          onClick={onHandleIncreaseMonth}
          direction="right"
          color="#3a578c"
          hoverColor="#2b4169"
          activeColor="#1a2740"
        />
        <CalendarWeek className="grid date_form date_head">
          {dayOfWeek.map((item, index) => {
            return <DayOfWeek key={index}>{item}</DayOfWeek>;
          })}
        </CalendarWeek>

        <DayOfMonth>
          {first.map((index) => {
            return (
              <OffDay key={index}>
                <CalendarDate>{index}</CalendarDate>
              </OffDay>
            );
          })}
          {month.map((item, index) => {
            return (
              <OnDay {...item} key={index}>
                <CalendarDate>{item}</CalendarDate>
              </OnDay>
            );
          })}
          {last.map((index) => {
            return (
              <OffDay key={index}>
                <CalendarDate>{index}</CalendarDate>
              </OffDay>
            );
          })}
        </DayOfMonth>
      </CalendarWrapper>
    </>

 

StyledComponent로 CSS까지 적용시켜주면 아래와 같은 달력이 완성됩니다.

 

전체 소스코드

import { useEffect, useState } from "react";
import styled from "styled-components";
import "./App.css";

// 스타일 지정

const CalendarWrapper = styled.div`
  max-width: 1200px;
  margin: auto;
  padding-bottom: 60px;
`;

const CalendarTitle = styled.div`
  display: inline;
  font-size: 50px;
  font-weight: bold;
  color: #778fbd;
  font-family: Bebas Neue;
`;

const CalendarWeek = styled.div`
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin: 30px auto 0 auto;
`;

const DayOfWeek = styled.div`
  color: white;
  text-align: center;
  font-size: 22px;
  line-height: 50px;
  height: 50px;
  width: 100%;
  font-family: Bebas Neue;

  border-left: 1px solid white;

  background-color: #3a578c;
`;

const DayOfMonth = styled.div`
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  justify-content: center;
  margin: auto;
`;

const OnDay = styled.div`
  color: #495f7c;
  text-align: center;
  line-height: 40px;
  height: 126px;
  width: 100%;
  display: inline-block;
  border-collapse: collapse;

  border: 1px solid #eaf1ff;

  :nth-child(even) {
    background-color: #eaf1ff;
  }
  :nth-child(odd) {
    background-color: #fff;
  }

  &:hover {
    background-color: #2f64a3;
    color: white;
    transition: all 0.2s linear;
  }
`;

const OffDay = styled.div`
  background-color: #f7f7f7;
  color: #e3e3e3;
  line-height: 40px;
  height: 126px;
  width: 100%;
  border-collapse: collapse;
  border: 1px solid #eaf1ff;
`;

const NextButton = styled.button`
  background-color: inherit;
  border-color: transparent
    ${(props) => (props.direction === "left" ? props.color : "transparent")}
    transparent
    ${(props) => (props.direction === "right" ? props.color : "transparent")};
  border-width: 13px ${(props) => (props.direction === "left" ? "15px" : "0px")}
    13px ${(props) => (props.direction === "right" ? "15px" : "0px")};
  height: 0;
  width: 0;
  margin: 0 25px;
  border-style: solid;

  vertical-align: 15px;

  &: hover {
    border-color: transparent
      ${(props) =>
        props.direction === "left" ? props.hoverColor : "transparent"}
      transparent
      ${(props) =>
        props.direction === "right" ? props.hoverColor : "transparent"};

    transition: all 0.2s linear;
  }

  &:active {
    border-color: transparent
      ${(props) =>
        props.direction === "left" ? props.activeColor : "transparent"}
      transparent
      ${(props) =>
        props.direction === "right" ? props.activeColor : "transparent"};
  }
`;

const CalendarDate = styled.div`
  display: flex;
  float: right;
  margin-top: 3px;
  margin-right: 13px;
  font-family: Aboreto;
  font-weight: bold;
  font-size: 16px;

  color: ${(props) => props.color};
`;
const Calendar = () => {
  const dayOfWeek = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
  const [date, setDate] = useState(new Date());
  const [first, setFirst] = useState([]);
  const [last, setLast] = useState([]);
  const [month, setMonth] = useState([]);

  useEffect(() => {
    setDays();
    drawCalendar();
  }, [date]);

  const setDays = () => {
    let arr = new Array(
      new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(),
    );

    for (let i = 0; i < arr.length; i++) {
      arr[i] = i + 1;
    }

    setMonth(arr);
  };

  const drawCalendar = () => {
    const year = date.getFullYear();
    const month = date.getMonth();
    const diffFirst = new Date(year, month, 1).getDay(); // 매 월 1일의 요일 -> 0 : 일요일, 6: 토요일
    const lastDay = new Date(year, month + 1, 0).getDate(); // 현재 달의 마지막 일
    const diffLast = (7 - ((lastDay + diffFirst) % 7)) % 7; // 달력에 마지막 채워지는 칸들

    const first = new Array(diffFirst);
    for (let i = 0; i < diffFirst; i++) {
      first[i] = new Date(year, month, -diffFirst + i + 1).getDate();
    }
    const last = new Array(diffLast);
    for (let i = 0; i < diffLast; i++) {
      last[i] = new Date(year, month, i + 1).getDate();
    }

    setFirst([...first]);
    setLast([...last]);
  };

  const onHandleDecreaseMonth = () => {
    // 한 달 앞으로
    setDate(new Date(date.getFullYear(), date.getMonth() - 1, date.getDate()));
  };

  const onHandleIncreaseMonth = () => {
    // 한 달 뒤로
    setDate(new Date(date.getFullYear(), date.getMonth() + 1, date.getDate()));
  };

  return (
    <>
      <CalendarWrapper>
        <NextButton
          onClick={onHandleDecreaseMonth}
          direction="left"
          color="#3a578c"
          hoverColor="#2b4169"
          activeColor="#1a2740"
        />
        <CalendarTitle>
          {date.getFullYear()}.{" "}
          {date.getMonth() + 1 < 10
            ? "0" + (date.getMonth() + 1)
            : date.getMonth() + 1}
        </CalendarTitle>
        <NextButton
          onClick={onHandleIncreaseMonth}
          direction="right"
          color="#3a578c"
          hoverColor="#2b4169"
          activeColor="#1a2740"
        />
        <CalendarWeek className="grid date_form date_head">
          {dayOfWeek.map((item, index) => {
            return <DayOfWeek key={index}>{item}</DayOfWeek>;
          })}
        </CalendarWeek>

        <DayOfMonth>
          {first.map((index) => {
            return (
              <OffDay key={index}>
                <CalendarDate>{index}</CalendarDate>
              </OffDay>
            );
          })}
          {month.map((item, index) => {
            return (
              <OnDay {...item} key={index}>
                <CalendarDate>{item}</CalendarDate>
              </OnDay>
            );
          })}
          {last.map((index) => {
            return (
              <OffDay key={index}>
                <CalendarDate>{index}</CalendarDate>
              </OffDay>
            );
          })}
        </DayOfMonth>
      </CalendarWrapper>
    </>
  );
};

export default Calendar;