🚀   새로운 블로그로 이전했습니다.

(React) useConfettiful

snippet: react
2022.12.29
2분

hook 하나로 모든 것을 해결했다!

useConfettiful.ts
import { useEffect, useState } from 'react';

/**
 * 초당 생성할 confetti 개수
 */
const confettiFrequency = 10;
/**
 * confetti 색상
 * https://colorhunt.co/palettes
 */
const confettiColors = ['#FAF8F1', '#FAEAB1', '#E5BA73', '#C58940', '#FAF8F1'];
/**
 * 바닥까지 떨어지는 속도
 */
const confettiAnimations = ['slow', 'medium', 'fast'];
const confettiAnimationSpeeds = [4, 3, 2];

const confettiCss = `
@keyframes confetti-slow {
  0% {
    transform: translate3d(0, 0, 0) rotateX(0) rotateY(0);
  }

  100% {
    transform: translate3d(25px, 105vh, 0) rotateX(360deg) rotateY(180deg);
  }
}

@keyframes confetti-medium {
  0% {
    transform: translate3d(0, 0, 0) rotateX(0) rotateY(0);
  }

  100% {
    transform: translate3d(100px, 105vh, 0) rotateX(100deg) rotateY(360deg);
  }
}

@keyframes confetti-fast {
  0% {
    transform: translate3d(0, 0, 0) rotateX(0) rotateY(0);
  }

  100% {
    transform: translate3d(-50px, 105vh, 0) rotateX(10deg) rotateY(250deg);
  }
}

.confetti--animation-slow {
  animation: confetti-slow ${confettiAnimationSpeeds[0]}s linear 1 forwards;
}

.confetti--animation-medium {
  animation: confetti-medium ${confettiAnimationSpeeds[1]}s linear 1 forwards;
}

.confetti--animation-fast {
  animation: confetti-fast ${confettiAnimationSpeeds[2]}s linear 1 forwards;
}
`;

function Confettiful() {
  const getRandomItem = (list: string[]) => list[Math.floor(Math.random() * list.length)];

  const el = document.createElement('div');
  el.id = 'confetti';
  el.style.position = 'fixed';
  el.style.pointerEvents = 'none';
  el.style.width = '100%';
  el.style.height = '100%';

  const containerEl = document.createElement('div');
  containerEl.style.position = 'absolute';
  containerEl.style.overflow = 'hidden';
  containerEl.style.top = '0';
  containerEl.style.right = '0';
  containerEl.style.bottom = '0';
  containerEl.style.left = '0';
  el.appendChild(containerEl);

  const confettiInterval = setInterval(() => {
    const confettiEl = document.createElement('div');
    confettiEl.style.position = 'absolute';
    confettiEl.style.zIndex = '99999';
    confettiEl.style.top = '-10px';
    confettiEl.style.borderRadius = '0%';

    const confettiSize = `${Math.floor(Math.random() * 3) + 7}px`;
    const confettiLeft = `${Math.floor(Math.random() * el.offsetWidth)}px`;
    const confettiBackground = getRandomItem(confettiColors);
    const confettiAnimation = getRandomItem(confettiAnimations);

    confettiEl.classList.add('confetti', `confetti--animation-${confettiAnimation}`);
    confettiEl.style.left = confettiLeft;
    confettiEl.style.width = confettiSize;
    confettiEl.style.height = confettiSize;
    confettiEl.style.backgroundColor = confettiBackground;

    setTimeout(function () {
      confettiEl.parentNode?.removeChild(confettiEl);
    }, 3000);

    containerEl.appendChild(confettiEl);
  }, 1000 / confettiFrequency);

  const styleEl = document.createElement('style');
  styleEl.innerHTML = confettiCss;
  document.head.appendChild(styleEl);

  document.body.prepend(el);

  return () => {
    clearInterval(confettiInterval);
    setTimeout(function () {
      el.remove();
      styleEl.remove();
    }, 3000);
  };
}

export default function useConfettiful(initShow = false): [boolean, (show: boolean) => void] {
  let cleanConfetti: () => void | undefined;
  const [show, setShow] = useState(initShow);

  useEffect(() => {
    if (show) {
      cleanConfetti = Confettiful();
    } else {
      cleanConfetti?.();
    }

    return () => {
      cleanConfetti?.();
    };
  }, [show]);

  return [show, setShow];
}