
- React와 JavaScript를 사용한 프로젝트에서 관광지 아이템을 N개 이상 선택했을 때, Toast로 알림을 주려고 했다.
- 컴포넌트 작성 부터 해당 컴포넌트 동작을 분리하여 다양한 페이지에서 사용 가능하도록 변경하는 과정까지 담아보았다.
- 또한, CSS 애니메이션에 대해 간단하게 정리해 보고자 한다.
아직 백엔드와 연결 작업을 진행하지 않아 임시 데이터로 테스트했다.
| 애니메이션 적용 전 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
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가 보이지 않는다.
- CSS 애니메이션의 이점
- 기존 스크립트를 이용한 애니메이션보다, JS를 몰라도 간단한 애니메이션을 만들 수 있다.
- frame-skipping과 같은 여러 기술을 이용해 부드럽게 렌더링된다.
- 애니메이션의 성능을 효율적으로 최적화할 수 있다. (안 보이는 엘리먼트에 대한 애니메이션은 업데이트 주기를 줄여 부하 최소화)
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 목록을 식별하는 이름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으로 지정한다.75% {
font-size: 300%;
margin-left: 25%;
width: 150%;
}
<p> 요소에 animation-iteration-count: infinite;를 설정하여 애니메이션이 무한히 반복되게 설정할 수 있다.animation-direction 속성을 alternate로 지정한다.AnimationEvent로 나타내어지는 애니메이션 이벤트를 사용해 애니메이션의 시작, 끝, 새로운 반복의 시작 등을 감지할 수 있음import { keyframes } from "styled-components";
export const fadeInOut = keyframes`
0% {
opacity: 0;
}
10% {
opacity: 1;
}
70% {
opacity: 1;
}
100% {
opacity: 0;
}
`;
const ToastContainer = styled.section`
animation-name: ${fadeInOut};
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
`;
animation: ${fadeInOut} 2s ease-in-out forwards;
isToastVisible을 통해서 토스트를 보여줬는데, 중복 실행이 되지 않도록 코드를 변경했다.if (checkRoutes.length === 2) {
if (!isToastVisible) {
setIsToastVisible(true);
setTimeout(() => {
setIsToastVisible(false);
}, 2000);
}
console.log("지정 개수를 넘길 수 없어요");
return;
}
이렇게 변경하면, 내가 의도한 애니메이션이 완성된다!