[React] ๐ŸŽ‰ ์ƒ‰์ข…์ด ์กฐ๊ฐ ๋‚ ๋ฆผ ํšจ๊ณผ (feat. Canvas-Confetti)

Tinubeeยท2023๋…„ 1์›” 19์ผ
14
post-thumbnail

๐ŸŽ‰ ์ƒ‰์ข…์ด ์กฐ๊ฐ ๋‚ ๋ฆผ ํšจ๊ณผ ๊ตฌํ˜„ํ•˜๊ธฐ

์ด๋ ฅ์„œ ์ง€์›์„ ํ•˜๋‹ค๋ณด๋ฉด ์ œ์ถœ ํ•  ๋•Œ ์ƒ‰์ข…์ด ์กฐ๊ฐ์ด ํญ์ฃฝ์ฒ˜๋Ÿผ ํ„ฐ์ง€๋Š” ํšจ๊ณผ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ด๊ฒƒ์„ ๋ณด๊ณ  ๊ถ๊ธˆํ•ด์„œ ๋งŒ๋“ค์–ด ๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค.

Github ๋’ค์ ๋’ค์  ํ•˜๋ฉด์„œ ์ฐพ์•„๋ณด๋‹ค๊ฐ€ 2๊ฐœ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.

1. react-canvas-confetti

์„ค์น˜ ๋ฐฉ๋ฒ•

npm i react-canvas-confetti

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

๋Œ€๋ถ€๋ถ„์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ canvas-confetti ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฏ€๋กœ ์ด ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋จผ์ € canvas-confetti ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์— ์ต์ˆ™ํ•ด์ง€๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ํ•œ๋‹ค.

๊นƒํ—ˆ๋ธŒ์— ์˜ˆ์ œ๊ฐ€ ์ž˜ ๋‚˜์™€์žˆ๊ณ , ์„ค๋ช…๋„ ์ž˜๋˜์–ด์žˆ์–ด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๋‹ˆ ์ด 4๊ฐ€์ง€ ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ๋‹ค.

  • SchoolPride
  • Frieworks
  • Snow
  • Realistic

์ œ์ผ ์ฒ˜์Œ ์ผ๋‹จ ํšจ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ Canvas ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ด ์ค€๋‹ค.

// ReactCanvasConfetti.tsx

import canvasConfetti, {
  CreateTypes,
  GlobalOptions,
  Options,
} from "canvas-confetti";
import { Component, createRef, CSSProperties, RefObject } from "react";

export interface IProps extends Options, GlobalOptions {
  fire?: any;
  reset?: any;
  width?: string | number;
  height?: string | number;
  className?: string;
  style?: CSSProperties;
  refConfetti?: (confetti: CreateTypes | null) => void;
  onDecay?: () => void;
  onFire?: () => void;
  onReset?: () => void;
}

export default class ReactCanvasConfetti extends Component<IProps> {
  private refCanvas: RefObject<HTMLCanvasElement>;

  private confetti: CreateTypes | null;

  constructor(props: IProps) {
    super(props);
    this.refCanvas = createRef();
    this.confetti = null;
  }

  componentDidMount() {
    if (!this.refCanvas.current) {
      return;
    }

    const { resize, useWorker } = this.props;
    const globalOptions: GlobalOptions = {
      resize: typeof resize === "undefined" ? true : resize,
      useWorker: typeof useWorker === "undefined" ? true : useWorker,
    };

    this.confetti = canvasConfetti.create(
      this.refCanvas.current,
      globalOptions
    );
    this.setRefConfetti();
  }

  componentDidUpdate(prevProps: Readonly<IProps>) {
    const { fire, reset } = this.props;
    const isFireTrue = !!fire;
    const isFireChanged = fire !== prevProps.fire;

    if (isFireTrue && isFireChanged) {
      this.fireConfetti();
    }

    const isResetTrue = !!reset;
    const isResetChanged = reset !== prevProps.reset;

    if (isResetTrue && isResetChanged) {
      this.resetConfetti();
    }
  }

  componentWillUnmount() {
    this.unsetRefConfetti();
  }

  private setRefConfetti() {
    const { refConfetti } = this.props;

    refConfetti && refConfetti(this.confetti);
  }

  private unsetRefConfetti() {
    const { refConfetti } = this.props;

    refConfetti && refConfetti(null);
  }

  private fireConfetti() {
    if (!this.confetti) {
      return;
    }

    const {
      onFire,
      onDecay,
      onReset,
      className,
      style,
      width,
      height,
      refConfetti,
      fire,
      reset,
      ...confettiProps
    } = this.props;

    onFire && onFire();

    const promise = this.confetti(confettiProps);

    promise &&
      promise.then(() => {
        onDecay && onDecay();
      });
  }

  private resetConfetti() {
    if (!this.confetti) {
      return;
    }

    this.confetti.reset();

    const { onReset } = this.props;

    onReset && onReset();
  }

  render() {
    const { style, className, width, height } = this.props;
    return (
      <canvas
        ref={this.refCanvas}
        style={style}
        className={className}
        width={width}
        height={height}
      />
    );
  }
}

GlobalStyle ์— Canvas์˜ ํฌ๊ธฐ๋ฅผ ์„ค์ •ํ•ด ์ค€๋‹ค.

.canvas {
  height: 100vh;
  left: 0px;
  pointer-events: none;
  position: fixed;
  top: 0px;
  width: 100vw;
}

๊ทธ ๋‹ค์Œ ์ด์ œ ํšจ๊ณผ๋ฅผ ๋„ฃ์–ด๋ณด์ž. Canvas-confetti ์—๋Š” ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋งŽ์€ ์˜ต์…˜๋“ค์ด ์žˆ๋‹ค.

Canvas-confetti props (more details)

  • particleCount: number (default: 50) - ๋ฐœ์‚ฌ ํ•  ์ƒ‰์ข…์ด์˜ ์ˆ˜.
  • angle: number (default: 90) - ์ƒ‰์ข…์ด ์กฐ๊ฐ์„ ๋ฐœ์‚ฌํ•˜๋Š” ๊ฐ๋„.
  • spread: number (default: 45) - ์ƒ‰์ข…์ด ์กฐ๊ฐ์ด ์ค‘์•™์—์„œ ์–ผ๋งˆ๋‚˜ ๋ฉ€๋ฆฌ ๊ฐˆ ์ˆ˜ ์žˆ๋Š”์ง€.(๋„ ๋‹จ์œ„)
  • startVelocity: number (default: 45) - ์ƒ‰์ข…์ด ์กฐ๊ฐ์ด ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ์‹œ์ž‘๋˜๋Š”์ง€ ํ”ฝ์…€ ๋‹จ์œ„๋กœ ํ‘œ์‹œ.
  • decay: number (default: 0.9) - ์ƒ‰์ข…์ด ์กฐ๊ฐ์ด ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ์†๋„๋ฅผ ์žƒ์„์ง€.(์•ˆ๋ฐ”๊พธ๋Š”๊ฒŒ ์ข‹๋‹ค๊ณ  ํ•œ๋‹ค.)
  • gravity: number (default: 1) - ์ž…์ž๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ๋‹น๊ฒจ์ง€๋Š”์ง€. 1์€ ์ „์ฒด ์ค‘๋ ฅ, 0.5๋Š” ์ ˆ๋ฐ˜ ์ค‘๋ ฅ, ๋“ฑ์ด ์žˆ์ง€๋งŒ ์ œํ•œ์€ ์—†๋‹ค. ์›ํ•˜๋Š” ๊ฒฝ์šฐ ์ž…์ž๋ฅผ ์œ„๋กœ ์˜ฌ๋ฆด ์ˆ˜๋„ ์žˆ๋‹ค.
  • drift: number (default: 0) - ์ƒ‰์ข…์ด ์กฐ๊ฐ์ด ์˜†์œผ๋กœ ์–ผ๋งˆ๋‚˜ ํ˜๋Ÿฌ๊ฐ€๋Š”์ง€๋ฅผ ์„ค์ •. ์™ผ์ชฝ์—๋Š” ์Œ์ˆ˜๋ฅผ, ์˜ค๋ฅธ์ชฝ์—๋Š” ์–‘์ˆ˜๋ฅผ ์‚ฌ์šฉ.
  • ticks: number (default: 200) - ์ƒ‰์ข…์ด ์กฐ๊ฐ์ด ๋ช‡ ๋ฒˆ์ด๋‚˜ ์›€์ง์ผ์ง€.
  • origin: { x: number, y: number } - ์ƒ‰์ข…์ด ๋ฐœ์‚ฌ๋ฅผ ์‹œ์ž‘ํ•  ์œ„์น˜. ์›ํ•˜๋Š” ๊ฒฝ์šฐ ํ™”๋ฉด ๋ฐ–์—์„œ ์‹คํ–‰ํ•ด๋„ ๋œ๋‹ค.
  • origin.x: number (default: 0.5) - ํŽ˜์ด์ง€์˜ x ์œ„์น˜, 0์€ ์™ผ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์ด๊ณ  1์€ ์˜ค๋ฅธ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์ด๋‹ค.
  • origin.y: number (default: 0.5) - ํŽ˜์ด์ง€์˜ y ์œ„์น˜. 0์€ ์œ„์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์ด๊ณ  1์€ ์•„๋ž˜์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์ด๋‹ค.
  • colors: Array<String> - HEX ํ˜•์‹์˜ ์ƒ‰์ƒ ๋ฌธ์ž์—ด ๋ฐฐ์—ด.
  • shapes: Array<String> - ์ƒ‰์ข…์ด ๋ชจ์–‘์˜ ๋ฐฐ์—ด. ๊ฐ€๋Šฅํ•œ ๊ฐ’์€ ์ •์‚ฌ๊ฐํ˜•๊ณผ ์›. ๊ธฐ๋ณธ๊ฐ’์€
    ๊ท ์ผํ•œ ํ˜ผํ•ฉ์—์„œ ๋‘ ๋ชจ์–‘์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•œ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ’์„ ์ œ๊ณตํ•˜์—ฌ ๋ฏน์Šค๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
    ['circle', 'circle', 'square']์™€ ๊ฐ™์ด 2/3 ์›๊ณผ 1/3 ์ •์‚ฌ๊ฐํ˜•์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • scalar: number (default: 1) - ๊ฐ ์ƒ‰์ข…์ด ์ž…์ž์— ๋Œ€ํ•œ ํฌ๊ธฐ. ์ƒ‰์ข…์ด ์กฐ๊ฐ์„ ์ž‘๊ฒŒ ๋งŒ๋“ค๋ ค๋ฉด ์†Œ์ˆ˜์ ์„ ์‚ฌ์šฉ.
  • zIndex: number (default: 100) - ๊ฒฐ๊ตญ ์ƒ‰์ข…์ด๊ฐ€ ๋งจ ์œ„์— ์žˆ์–ด์•ผํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์—„์ฒญ๋‚˜๊ฒŒ ๋†’์€ ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด ๋” ๋†’๊ฒŒ ์„ค์ •.
  • resize: boolean (default: true) - ์บ”๋ฒ„์Šค ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์„ค์ •์„ ํ—ˆ์šฉํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ํฌ๊ธฐ๋ฅผ ์œ ์ง€ํ• ์ง€ ์—ฌ๋ถ€. ์ฐฝ ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ(์˜ˆ: ์ฐฝ ํฌ๊ธฐ ์กฐ์ •, ๋ชจ๋ฐ”์ผ ์žฅ์น˜ ํšŒ์ „ ๋“ฑ). ๊ธฐ๋ณธ์ ์œผ๋กœ ์บ”๋ฒ„์Šค ํฌ๊ธฐ๋Š” ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค.

์ด์ œ ์ƒ‰์ข…์ด ๋‚ ๋ฆผ ํšจ๊ณผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

// Realistic.tsx

import { faFire } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CreateTypes } from "canvas-confetti";
import { Component } from "react";
import { Icon } from "../../header/Mode";
import ReactCanvasConfetti from "../ReactCanvasConfetti";

export default class Realistic extends Component {
  private isAnimationEnabled: boolean;
  private animationInstance: CreateTypes | null = null;

  constructor(props: {}) {
    super(props);
    this.isAnimationEnabled = false;
    this.fire = this.fire.bind(this);
  }

  makeShot(particleRatio: number, opts: object) {
    this.animationInstance &&
      this.animationInstance({
        ...opts,
        origin: { y: 0.8 },
        particleCount: Math.floor(200 * particleRatio),
      });
  }

  // ์ด ๋ถ€๋ถ„์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ์„ค์ •์„ ํ•˜๋ฉด ๋œ๋‹ค.
  fire() {
    this.makeShot(0.25, {
      spread: 25,
      startVelocity: 55,
    });
  }

  handlerFire = () => {
    if (!this.isAnimationEnabled) {
      this.isAnimationEnabled = true;
    }
    requestAnimationFrame(this.fire);
    this.fire();
  };

  getInstance = (instance: CreateTypes | null) => {
    this.animationInstance = instance;
  };

  render() {
    return (
      <>
        <Icon mode="fire" onClick={this.handlerFire}>
          <FontAwesomeIcon icon={faFire} />
        </Icon>
        <ReactCanvasConfetti
          refConfetti={this.getInstance}
          className="canvas"
        />
      </>
    );
  }
}

์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™”๋ฉด์— ๋ถˆ๋Ÿฌ์˜ค๋ฉด ๋œ๋‹ค. ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„์ด ๋œ๋‹ค.

makeShot์„ ์—ฌ๋Ÿฌ๊ฐœ ๋งŒ๋“ค์–ด ์ฃผ์–ด์„œ ํญ์ฃฝ์ด ํ„ฐ์ง€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋งŒ๋“ค์–ด ๋ณด์ž.
์œ„ ์ฝ”๋“œ์—์„œ fire() ๋ถ€๋ถ„์— makeShot์„ ๋” ์ถ”๊ฐ€ํ•˜๊ณ  ์„ค์ •์„ ๊ฐ๊ฐ ๋‹ค๋ฅด๊ฒŒ ๋ฐ”๊พธ์–ด ์ฃผ๋ฉด๋œ๋‹ค.

fire() {
    this.makeShot(0.25, {
      spread: 26,
      startVelocity: 55,
    });

    this.makeShot(0.2, {
      spread: 20,
    });

    this.makeShot(0.35, {
      spread: 100,
      decay: 0.91,
      scalar: 0.8,
    });

    this.makeShot(0.1, {
      spread: 120,
      startVelocity: 25,
      decay: 0.92,
      scalar: 1.2,
    });

    this.makeShot(0.1, {
      spread: 120,
      startVelocity: 45,
    });
  }

๋น„์Šทํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ์„ค์ •์„ ์ ์šฉ์‹œํ‚ค๋ฉด ๋‹ค์–‘ํ•œ ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

2. js-confetti

react-canvas-confetti ๋ณด๋‹ค ํ›จ์”ฌ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ƒ‰์ข…์ด ํšจ๊ณผ๋Š” ํ•œ๊ฐ€์ง€ ๊ณ ์ •์œผ๋กœ ๋”ฐ๋กœ ๋ณ€๊ฒฝ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ ๊ฐ™๊ณ , ์ƒ‰์ข…์ด์˜ ์ƒ‰์ƒ, ํ˜•ํƒœ, ์‚ฌ์ด์ฆˆ, ๊ฐœ์ˆ˜ ๋งŒ ๋ณ€๊ฒฝ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์„ค์น˜ ๋ฐฉ๋ฒ•

npm i js-confetti

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

์ œ์ผ ์ฒ˜์Œ ์ƒ‰์ข…์ด ํšจ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ผ Canvas๋ฅผ Document์— ์ƒ์„ฑ ํ•˜๊ณ , ๊ทธ์œ„์— addConfetti ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ƒ‰์ข…์ด ํญ๋ฐœ ํšจ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

//Home.tsx

import JSConfetti from "js-confetti";

function Home() {
  //HTML Canvas ์š”์†Œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํŽ˜์ด์ง€์— ์ถ”๊ฐ€
  const jsConfetti = new JSConfetti(); 

  //์ƒ‰์ข…์ด ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
  const handleClick = () => {
    jsConfetti.addConfetti({
      confettiColors: [
        "#ff0a54",
        "#ff477e",
        "#ff7096",
        "#ff85a1",
        "#fbb1bd",
        "#f9bec7",
      ],
      confettiRadius: 5,
      confettiNumber: 500,
    });
  };

  return <button onClick={handleClick}>CLICK</button>;
}

export default Home;

JSConfetti addConfetti props

confettiColors : ์ƒ‰์ข…์ด ์ƒ‰์ƒ ์„ค์ •.
confettiRadius : ์ƒ‰์ข…์ด ์กฐ๊ฐ ๋ฐ˜์ง€๋ฆ„ ๊ธธ์ด ์„ค์ •.
confettiNumber : ์ƒ‰์ข…์ด ์กฐ๊ฐ ๊ฐœ์ˆ˜ ์„ค์ •.
emojis : ์ด๋ชจํ‹ฐ์ฝ˜์œผ๋กœ ์ƒ‰์ข…์ด ์กฐ๊ฐ ์‚ฌ์šฉ.
emojiSize : ์ด๋ชจํ‹ฐ์ฝ˜ ์‚ฌ์ด์ฆˆ ์„ค์ •.

์ƒ‰์ข…์ด ๋Œ€์‹  ์ด๋ชจํ‹ฐ์ฝ˜์„ ๋„ฃ์–ด์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

//Home.tsx

import JSConfetti from "js-confetti";

function Home() {
  //HTML Canvas ์š”์†Œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํŽ˜์ด์ง€์— ์ถ”๊ฐ€
  const jsConfetti = new JSConfetti(); 

  //์ƒ‰์ข…์ด ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
  const handleClick = () => {
    jsConfetti.addConfetti({
      emojis: ["๐Ÿ”", "๐Ÿ•", "๐Ÿบ"],
      emojiSize: 100,
      confettiNumber: 30,
    });
  };

  return <button onClick={handleClick}>CLICK</button>;
}

export default Home;

๐Ÿ˜ฑ ์ˆ˜์ • ์‚ฌํ•ญ

์œ„์™€ ๊ฐ™์ด ํ•˜๊ฒŒ๋˜๋ฉด Home ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค, new JSConfetti() ์— ์˜ํ•ด์„œ HTML์— Canvas ์š”์†Œ๊ฐ€ ๊ณ„์† ์ถ”๊ฐ€ ๋˜๊ฒŒ ๋œ๋‹ค. ๊นƒํ—ˆ๋ธŒ ์—๋„ "new JSConfetti() ๋Š” HTML Canvas ์š”์†Œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํŽ˜์ด์ง€์— ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜์‹ญ์‹œ์˜ค!" ๋ผ๊ณ  ์จ์ ธ์žˆ๋‹ค.

์œ„ ์˜์ƒ์„ ๋ณด๋ฉด Home์— ๋“ค์–ด๊ฐˆ๋•Œ ๋งˆ๋‹ค canvas ์š”์†Œ๊ฐ€ ๊ณ„์† ์ถ”๊ฐ€๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
const jsConfetti = new JSConfetti(); ์˜ ์œ„์น˜๋ฅผ App.js ( ์ฒ˜์Œ ์‹คํ–‰ ๋  ๋•Œ )๋กœ ์˜ฎ๊ธฐ๊ณ  export ์‹œ์ผœ์ฃผ์—ˆ๋‹ค.

import JSConfetti from "js-confetti";

export const conteffi = new JSConfetti();

๊ทธ๋ฆฌ๊ณ  Home ๋˜๋Š” ์‚ฌ์šฉํ•  ๊ณณ์—์„œ import ํ•˜์—ฌ ๋˜‘๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

//Home.tsx

import JSConfetti from "js-confetti";
import { conteffi } from "../App";

function Home() {
  //์ƒ‰์ข…์ด ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
  const handleClick = () => {
    conteffi.addConfetti({
      emojis: ["๐Ÿ”", "๐Ÿ•", "๐Ÿบ"],
      emojiSize: 100,
      confettiNumber: 30,
    });
  };

  return <button onClick={handleClick}>CLICK</button>;
}

export default Home;

3. ๋งˆ๋ฌด๋ฆฌ

์œ„ 2๊ฐœ ๋ง๊ณ ๋„ ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์žˆ์—ˆ๋Š”๋ฐ ์‚ฌ์šฉ๋ฒ•์ด ๋‹ค ๋น„์Šท๋น„์Šท ํ•œ ๊ฒƒ ๊ฐ™์•˜๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ
react-canvas-confetti ๊ฐ€ ์กฐ๊ธˆ ๋” ๋ณต์žกํ•˜์ง€๋งŒ ๋‹ค์–‘ํ•˜๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•  ์ˆ˜ ์žˆ์–ด์„œ, ๋‹ค์–‘ํ•œ ํšจ๊ณผ๋“ค์„ ๋„ฃ๊ธฐ์—๋Š” ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

profile
โœ๏ธ ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป๐Ÿ”ฅโžœ๐Ÿ‘๐Ÿค” ๐Ÿ˜ฑ

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2023๋…„ 7์›” 20์ผ

ReactCanvasConfetti.tsx ํŒŒ์ผ์—์„œ

import { Icon } from "../../header/Mode"; ์—์„œ

์—ฌ๊ธฐ์„œ Mode ๊ฐ€ ๋ญ˜๊นŒ์š”.... ๋ชจ๋“ˆ์„ ์ฐพ์„์ˆ˜๊ฐ€ ์—†๋‹ค๊ณ  ๋– ์„œ ๋ง์ด์—์š” ใ… ใ… 

1๊ฐœ์˜ ๋‹ต๊ธ€