연습문제 페이지를 다만들었고 다풀었을때 보여줄 완료 페이지를 만들려고 한다.
자격증 이미지를 띄우고 돋보기 추가해볼까 하다가 뭔가 연관되는게없다는 생각이 들어서
폭죽을 터뜨리는 것으로 바꾸었다.
그런데 검색해보니 정말 다양한 폭죽 라이브러리가 있었다.
내가 선택한 방법은 react-confetti 라이브러리이다.
라이브러를 사용하지않고 Vector와 Class 를 사용한 폭죽을 구현해보고싶었지만,
다른작업이 많이 남아있기에 가장 빠르게 할 수 있는걸 선택했다.
1. react-confetti
우선 confetti를 설치해주고 npm에 나온대로 코드를 작성해서 실행해보았다.
npm install react-confetti
import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";
import useWindowSize from "react-use/lib/useWindowSize";
import Confetti from "react-confetti";
const FinishPage = () => {
const { width, height } = useWindowSize();
return (
<>
<Confetti width={width} height={height} />
<FinishContainer>
<FinishTitle>축 하 합 니 다</FinishTitle>
<CertificateImage src="/images/certificate.png" />
</FinishContainer>
</>
);
};
export default FinishPage;
바로 에러 떠버리기
ERROR in ./src/pages/Practice/FinishPage.jsx 7:0-56 Module not found: Error: Can't resolve 'react-use/lib/useWindowSize' in '/Users/sj/Desktop/NaverCloutPlatform-CBT/frontend/src/pages/Practice'
react-use 라는걸 추가로 깔아야하나보다.
react-use
react 애플리케이션에서 자주 사용하는 다양한 유틸리티 훅을 제공하는 라이브러리.
1. useLocalStorage: 로컬 스토리지에 데이터를 저장하고 읽을 수 있는 hook
2. useFetch: API 호출을 쉽게 수행할 수 있는 hook
3. useDebounce: 입력값을 디바운스 처리하여 과도한 렌더링을 방지하는 hook
4. useInterval: 일정한 간격으로 작업을 수행할 수 있도록 하는 hook
5. useMedia: 미디어 쿼리를 사용하여 반응형 디자인을 구현할 수 있는 hook
npm install react-use
설치하고 브라우저에 접속하니 바로 적용되는게 정말 간단하다.
그런데 unCat이 엄청나게 움직여서 확인해보니 문제가 하나 있었다.
개발자 모드를 열때마다 cpu점유율이 최대 40% 까지 올라가는 문제
원인 : 윈도우 사이즈가 resize 되면서 이벤트가 계속 발생
해결 : Debounce 또는 Throttle 적용
디바운싱과 쓰로틀링은 이벤트가 빈번하게 발생할 때 제어하는 성능 최적화 기법이다.
resize시 이벤트가 발생하는 간격을 제어하여 사이즈가 변경될때마다 호출되지 않게 할 수 있다.
디바운싱
- 이벤트가 연속적으로 발생할때 한번만 처리하는 기법
- 지정된 시간동안 이벤트가 발생하면 다시 시간을 주고 이벤트가 완료되면 그때 마지막 이벤트를 처리한다.
ex) 실시간 검색, 사용자 입력
쓰로틀링
- 일정 간격으로 이벤트를 처리하는 기법
- 이벤트가 여러번 발생하더라도 그 간격마다 이벤트를 처리한다.
ex) 스크롤 이벤트
1. lodash 설치
npm install lodash
2. hook 생성
import { useState, useEffect } from "react";
import { debounce } from "lodash";
const useDebouncedWinSize = () => {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = debounce(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
}, 2000);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return size;
};
export default useDebouncedWinSize;
3. 받아온 사이즈를 Confetti에 인자로 넣어주기
// 폭북 보여줄 jsx
import Confetti from "react-confetti";
import useDebouncedWinSize from "../../hooks/useDebounceWinSize";
const FinishPage = () => {
const { width, height } = useDebouncedWinSize();
return (
<>
<Confetti width={width} height={height} />
<FinishContainer>
<FinishTitle>축 하 합 니 다</FinishTitle>
<CertificateImage src="/images/certificate.png" />
</FinishContainer>
</>
);
};
export default FinishPage;
최대 31 퍼까지 올라가는데 거의 24~25% 정도에서 다시 내려간다.
시도 : 파티클 개수 조절
종이가 너무 많이 떨어지는걸까?싶어서
종이수를 줄여봤는데 딱히 관계는 없는듯하다.
무수히 쏟아 내는 파티클이 cpu를 잡아먹는거같은데, 일정시간이 지나면 파티클이 나타나지 않게 해보면 어떨까?
시도 2 : 파티클 시간 제한
처음에는 단순히 useEffect와 useEffect, setTimeout으로 사라지는 효과를 만들어보았다.
그런데 시간이되면 마법처럼 전부 사라져서 멋이 없음...
자체적으로 렌더링을 멈추는 함수가 있을거같아서 Confetti docs를 읽어보았다.
onConfettiComplete를 사용하여 Confetti가 끝났을 때 Confetti의 보이는 상태를 false로 하는 함수를 호출하도록 하였다.
{isConfettiVisible && (
<Confetti
width={width}
height={height}
onConfettiComplete={handleConfettiComplete}
/>
}
안됨.
음.. 좀 더 단순하게 생각해봄
지정된 시간동안만 애니메이션이 실행되고 그 시간이 지났을때 보여줄 파티클의 개수를 0으로 만들어버리면?
시도 3 : 파티클 시간과 개수 조절
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import Confetti from "react-confetti";
import useDebouncedWinSize from "../../hooks/useDebounceWinSize";
const FinishPage = () => {
const { width, height } = useDebouncedWinSize();
const [numberOfPieces, setNumberOfPieces] = useState(50);
useEffect(() => {
const timer = setTimeout(() => {
setNumberOfPieces(0);
}, 10000);
return () => clearTimeout(timer);
}, []);
return (
<>
<Confetti
width={width}
height={height}
numberOfPieces={numberOfPieces}
opacity={0.7}
drawShape={ctx => {
const numPoints = 5;
const outerRadius = 15;
const innerRadius = 7;
const step = (Math.PI * 2) / numPoints;
ctx.beginPath();
for (let i = 0; i < numPoints * 2; i++) {
const radius = i % 2 === 0 ? outerRadius : innerRadius;
const angle = step * i;
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
}}
/>
<FinishContainer>
<FinishTitle>축 하 합 니 다</FinishTitle>
<CertificateImage src="/images/certificate.png" />
</FinishContainer>
</>
);
};
완성!
사이즈 재조정 함수가 실행됬을때는 최대 30%까지 올라가지만
1초후에 14~16% 정도로 떨어지고 이후 최대 21% 이상 올라가지않는다 .
📝 정리
Confetti 애니메이션의 동작
시작 : Confetti가 화면에 나타나고 애니메이션이 시작한다.
진행 : 설정된 numberOfPieces만큼의 조각이 화면에 떨어진다.
완료 : 모든 Confetti 조각이 화면에서 떨어지면 onConfettiComplete 콜백 함수가 호출된다.