[Project] React + JavaScript Toast 컴포넌트 / CSS 애니메이션

SEONDY·2024년 10월 5일

Project

목록 보기
3/9
post-thumbnail

[Project] React + JavaScript Toast 컴포넌트 / CSS 애니메이션

  • React와 JavaScript를 사용한 프로젝트에서 관광지 아이템을 N개 이상 선택했을 때, Toast로 알림을 주려고 했다.
    • 컴포넌트 작성 부터 해당 컴포넌트 동작을 분리하여 다양한 페이지에서 사용 가능하도록 변경하는 과정까지 담아보았다.
    • 또한, CSS 애니메이션에 대해 간단하게 정리해 보고자 한다.

🖼️구현 화면

아직 백엔드와 연결 작업을 진행하지 않아 임시 데이터로 테스트했다.

애니메이션 적용 전 Toast애니메이션 적용 후 Toast

1. Toast 공통 컴포넌트 만들기

Toast 컴포넌트

import styled from "styled-components";
import { theme } from "../../../style/theme";
import { applyFontStyles } from "../../../utils/fontStyles";

// type별 toast 아이콘 지정 (default : alert)
const Toast = ({ children, type = "alert", ...props }) => {
  return (
    <ToastContainer>
      <ToastIcon src={`/images/Icon/${type}.svg`} alt={type} />
      <ToastMessage>{children}</ToastMessage>
    </ToastContainer>
  );
};

export default Toast;
  • styled-components로 디자인을 구현했다.
    ToastContainer : section
    ToastIcon : img
    ToastMessage : p
  • 사실 이번 프로젝트에서는 Toast가 한 페이지의 일부분에서만 사용되고, 다른 곳에서 사용되는 컴포넌트가 아니었기 때문에 공통 컴포넌트로 분리할 필요는 없었다.
    • 하지만, 확장 됐을 경우에 Toast 컴포넌트는 여러 type으로 사용될 가능성이 있다고 생각했기 때문에 공통 컴포넌트로 분리했다.
    • 또한, Toast의 타입 종류가 추가될 수 있다고 생각하여 type을 구분했고, 아무런 type 값이 넘어오지 않았을 때는 alert로 기본 값을 설정했다.

컴포넌트 사용

  • Toast 컴포넌트를 사용할 페이지에서 useState로 Toast 상태를 설정했다.

    const [isToastVisible, setIsToastVisible] = useState(false);
  • 그리고 해당 컴포넌트가 필요한 위치에 넣어줬다.

    {isToastVisible && <Toast>{text.ALERT_TOAST}</Toast>}

    String에 상수화 해둔 값을 넣어줬다. (장소는 최대 N개까지 선택할 수 있어요.)
    type은 default 값인 alert이기 때문에 따로 넘겨주진 않았다.

  • 기존 페이지에 추가되어 있던 루트를 체크 했을 때, 설정한 개수를 넘어가면 Toast를 띄우는 게 목표였기 때문에 다음과 같이 처리했다.

    /** 루트 아이템 체크 */
    const handleCheckChange = (id) => {
    // 이미 체크된 항목을 클릭했을 때 체크 해제
    if (checkRoutes.includes(id)) {
      setCheckRoutes((prev) => prev.filter((item) => item !== id));
      return;
    }
    
    // 지정 개수 넘길 수 없을 때 return
    if (checkRoutes.length === 2) {
      setIsToastVisible(true);
      setTimeout(() => {
        setIsToastVisible(false);
      }, 2000);
      console.log("지정 개수를 넘길 수 없어요");
      return;
    }
    
    setCheckRoutes((prev) => [...prev, id]);
    };

    최대 개수는 테스트를 위해 2개로 설정했다.
    이렇게 설정하면, setIsToastVisible(true); 로 Toast가 보이고, setTimeout으로 인해 2초 뒤에 false로 변경되어 Toast가 보이지 않는다.


2. CSS 애니메이션

  • 애니메이션은 애니메이션을 나타내는 CSS 스타일과 애니메이션의 중간 상태를 나타내는 키프레임들로 이루어진다.
    • CSS 애니메이션의 이점
      • 기존 스크립트를 이용한 애니메이션보다, JS를 몰라도 간단한 애니메이션을 만들 수 있다.
      • frame-skipping과 같은 여러 기술을 이용해 부드럽게 렌더링된다.
      • 애니메이션의 성능을 효율적으로 최적화할 수 있다. (안 보이는 엘리먼트에 대한 애니메이션은 업데이트 주기를 줄여 부하 최소화)
  • CSS 애니메이션을 만드려면, animation 속성과 하위 속성을 지정한다.
    애니메이션의 총 시간 / 반복 여부 등 지정 가능
    • animation 속성의 하위 속성
      • animation-delay : 엘리먼트가 로드되고 나서 언제 애니메이션이 시작될지 지정
      • animation-direction : 애니메이션이 종료되고 다시 처음부터 시작할지 / 역방향으로 진행할지 지정
      • animation-duration : 한 싸이클의 애니메이션이 얼마에 걸쳐 일어날지 지정
      • animation-iteration-count : 애니메이션이 몇 번 반복될지 지정 (infinite : 무한히 반복)
      • animation-name : 애니메이션의 중간 상태를 지정
        중간 상태는 @keyframes 규칙을 이용해 기술한다.
      • animation-play-state : 애니메이션을 멈추거나 다시 시작할 수 있음
      • animation-timing-function : 중간 상태들의 전환을 어떤 시간간격으로 진행할지 지정
      • animation-fill-mode : 애니메이션이 시작되기 전이나 끝나고 난 후 어떤 값이 적용될지 지정

@keyframes 규칙

  • @keyframes 규칙은, 애니메이션 중간중간의 특정 지점들을 거칠 수 있는 키프레임들을 설정함으로써 CSS 애니메이션 과정의 중간 절차를 제어할 수 있게 한다.

  • 구문

    @keyframes slidein {
        from {
          transform: translateX(0%);
        }
    
        to {
          transform: translateX(100%);
        }
      }
    • <custom-ident> : keyframe 목록을 식별하는 이름
      CSS 구문에서 생성된 식별자와 일치해야 함
      대소문자를 구분 / 모호성을 방지하기 위해 특정 값 사용 금지
    • from : 시작 offset인 0%
    • to : 마지막 offset인 100%
    • <percentage> : 전체 애니메이션 시간 중 명시된 keyframe이 발생해야 하는 시점의 %
  • 키프레임을 사용하려면, @keyframes 룰을 애니메이션과 키프레임 리스트를 매칭시킬 animation-name 속성으로 사용할 이름과 함께 생성

  • @keyframes 룰은 키프레임 선택자의 스타일 리스트를 포함하고 있고,
    각 리스트는 각 키프레임이 생성되고 키프레임의 스타일 정보를 포함하고 있는 시점에서 사용할 %로 구성된다.

  • 키프레임 %를 순서대로 나열하지 않아도, %의 순서대로 처리된다.

  • 키프레임 규칙은 JavaScript에서 CSS 오브젝트 모델 인터페이스인 CSSKeyframesRule을 통해 접근 가능

  • 유효한 키프레임 리스트
    • 키프레임 시작 상태와 끝 애니메이션을 명시하지 않으면,
      브라우저는 처음과 마지막에 현재 존재하는 요소의 스타일을 사용
    • 키프레임 룰에 애니메이션이 되지 않는 속성을 포함하면 : 무시됨
      애니메이션을 지원하는 속성들 : 애니메이션이 됨
  • 중복 처리
    • 한 개의 이름에 대해서 여러 개의 키프레임 셋이 존재하면,
      파서가 마지막으로 마주치는 키프레임 셋만 유효함
    • 애니메이션들의 시간 offset이 중복된 경우,
      해당 %에 대한 모든 @keyframes 룰들이 해당 프레임에 적용됨
      동일한 % 값을 가진 여러 @keyframes 룰들은 종속됨
  • 키 프레임에 속성이 누락된 경우
    정의되지 않은 속성들은 중간에 가능한 곳에 삽입됨 (가능하지 않은 애니메이션들은 제외됨)

  • 키프레임에서 !important 속성을 이용한 정의는 모두 무시됨

예제

p {
  animation-duration: 3s;
  animation-name: slidein;
}

@keyframes slidein {
  from {
    margin-left: 100%;
    width: 300%;
  }

  to {
    margin-left: 0%;
    width: 100%;
  }
}
  • <p>에 지정한 animation-duration 속성을 통해 애니메이션의 총 길이는 3초로 지정된다.
  • 중간 상태들을 @keyframes 규칙을 사용해 기술하고, 이름을 slidein으로 정한다.
  • 그리고 <p>animation을 slidein으로 지정한다.
  • 첫 번째 중간 상태 : 애니메이션이 시작되고 0% 시점(from)에 왼쪽 마진 100%로 지정
  • 두 번째 중간 상태 : 애니메이션이 시작되고 100% 시점(to)에 왼쪽 마진 0%로 지정
75% {
  font-size: 300%;
  margin-left: 25%;
  width: 150%;
}
  • 중간 상태를 추가하여, 애니메이션의 75% 시점에서 엘리먼트의 왼쪽 마진, 너비, 글자 크기를 지정해 애니메이션을 추가할 수 있다.
  • <p> 요소에 animation-iteration-count: infinite;를 설정하여 애니메이션이 무한히 반복되게 설정할 수 있다.
  • 그리고, 애니메이션이 끝났을 때 반대방향으로 이동하도록 하려면,
    animation-direction 속성을 alternate로 지정한다.
  • 애니메이션 이벤트를 이용해 애니메이션 조종이 가능하다.
    AnimationEvent로 나타내어지는 애니메이션 이벤트를 사용해 애니메이션의 시작, 끝, 새로운 반복의 시작 등을 감지할 수 있음

3. Toast + CSS 애니메이션

CSS Animation 완벽 가이드

  • 각 속성에 대해 잘 설명되어 있어서 정말 추천하는 글!
    글로 봤을 때는 와닿지 않았는데 예시를 보니 이해가 됐다!

interaction.jsx

  • 애니메이션이 들어가는 요소는 따로 분리해서 관리하고 싶어 util 폴더에 저장했다.
import { keyframes } from "styled-components";

export const fadeInOut = keyframes`
  0% {
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  70% {
   opacity: 1;
  }
  100% {
    opacity: 0;
  }
`;
  • 애니메이션은... 직접 값을 넣어가며 정하는 게 최고인 듯...🤤
    서서히 사라지게 설정했다. (계속 확인하다 15%로 바꿈 ㅎㅎ)

Toast.jsx

const ToastContainer = styled.section`
  animation-name: ${fadeInOut};
  animation-duration: 2s;
  animation-timing-function: ease-in-out;
  animation-fill-mode: forwards;
`;
  • 그리고 Toast에 animation을 적용했다. 단축하면 아래와 같다.
    animation: ${fadeInOut} 2s ease-in-out forwards;

MyTripPage.jsx

  • 기존에 isToastVisible을 통해서 토스트를 보여줬는데, 중복 실행이 되지 않도록 코드를 변경했다.
if (checkRoutes.length === 2) {
  if (!isToastVisible) {
    setIsToastVisible(true);
    setTimeout(() => {
      setIsToastVisible(false);
    }, 2000);
  }
  console.log("지정 개수를 넘길 수 없어요");

  return;
}

이렇게 변경하면, 내가 의도한 애니메이션이 완성된다!



참고 사이트

0개의 댓글