Skip to content

Commit

Permalink
feat: cofetti.js 라이브러리 폭죽 효과 시간 및 중복 발생 방지 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
deipanema committed Oct 30, 2024
1 parent 3a317f3 commit f7ecbd9
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/hook/useSignup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMutation } from "@tanstack/react-query";

import { signup } from "@/api/authAPI";
import { ErrorType } from "@/api/goalAPI";
import { poof } from "@/utils/confetti";

export const useSignup = () => {
const router = useRouter();
Expand All @@ -12,6 +13,7 @@ export const useSignup = () => {
mutationFn: signup,
onSuccess: () => {
toast.success("회원가입이 완료되었습니다.");
poof();
router.push("/login");
},
onError: (error: ErrorType) => {
Expand Down
231 changes: 231 additions & 0 deletions src/utils/confetti.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// confetti.js
const createConfetti = () => {
if (typeof document === "undefined") return () => {}; // 서버 사이드 렌더링 방지

// Constants
const { random, cos, sin, PI } = Math;
const PI2 = PI * 2;

// Configuration
const config = {
particles: 500,
spread: 1000,
sizeMin: 3,
sizeMax: 12,
eccentricity: 30,
deviation: 200,
dxThetaMin: 0,
dyMin: 0.0,
dyMax: 2.18,
dThetaMin: 0.4,
dThetaMax: 0.3,
};

// Bright color palette
const colors = [
[255, 89, 149],
[255, 159, 0],
[255, 220, 0],
[0, 205, 255],
[30, 255, 140],
[255, 150, 255],
[147, 255, 255],
[255, 255, 140],
[255, 140, 255],
[140, 255, 255],
];

const getRandomColor = () => {
const baseColor = colors[Math.floor(random() * colors.length)];
return baseColor.map((value) => Math.max(0, Math.min(255, value + Math.floor(random() * 30) - 15)));
};

const color = (r, g, b) => `rgb(${r},${g},${b})`;

// Cosine interpolation
const interpolation = (a, b, t) => ((1 - cos(PI * t)) / 2) * (b - a) + a;

// Create a 1D Maximal Poisson Disc over [0, 1]
const createPoisson = () => {
const radius = 1 / config.eccentricity;
const radius2 = radius + radius;
const domain = [radius, 1 - radius];
const spline = [0, 1];
let measure = 1 - radius2;

while (measure) {
let dart = measure * random();
let interval, a, b, c, d;

for (let i = 0; i < domain.length; i += 2) {
a = domain[i];
b = domain[i + 1];
interval = b - a;
if (dart < measure + interval) {
spline.push((dart += a - measure));
break;
}
measure += interval;
}
c = dart - radius;
d = dart + radius;

for (let i = domain.length - 1; i > 0; i -= 2) {
const l = i - 1;
a = domain[l];
b = domain[i];

if (a >= c && a < d) {
if (b > d) domain[l] = d;
else domain.splice(l, 2);
} else if (a < c && b > c) {
if (b <= d) domain[i] = c;
else domain.splice(i, 0, c, d);
}
}

measure = domain.reduce((sum, val, i) => (i % 2 ? sum + val - domain[i - 1] : sum), 0);
}

return spline.sort((a, b) => a - b);
};

// Container setup
const container = document.createElement("div");
Object.assign(container.style, {
position: "fixed",
top: "0",
left: "0",
width: "100%",
height: "0",
overflow: "visible",
zIndex: "9999",
});

// Confetto class
class Confetto {
constructor() {
this.frame = 0;
this.opacity = 1;
this.outer = document.createElement("div");
this.inner = document.createElement("div");
this.outer.appendChild(this.inner);

const outerStyle = this.outer.style;
const innerStyle = this.inner.style;

const size = config.sizeMin + (config.sizeMax - config.sizeMin) * random();
Object.assign(outerStyle, {
position: "absolute",
width: `${size}px`,
height: `${size}px`,
perspective: "50px",
transform: `rotate(${360 * random()}deg)`,
});

const [r, g, b] = getRandomColor();
Object.assign(innerStyle, {
width: "100%",
height: "100%",
backgroundColor: color(r, g, b),
transform: `rotate3D(${cos(360 * random())},${cos(360 * random())},0,${this.theta}deg)`,
});

this.axis = `rotate3D(${cos(360 * random())},${cos(360 * random())},0,`;
this.theta = 360 * random();
this.dTheta = config.dThetaMin + config.dThetaMax * random();

this.x = window.innerWidth * random();
this.y = -config.deviation;
this.dx = sin(config.dxThetaMin + config.dxThetaMin * random());
this.dy = config.dyMin + config.dyMax * random();

outerStyle.left = this.x + "px";
outerStyle.top = this.y + "px";

this.splineX = createPoisson();
this.splineY = Array.from({ length: this.splineX.length }, () => config.deviation * random());
this.splineY[0] = this.splineY[this.splineY.length - 1] = config.deviation * random();
}

update(height, delta) {
this.frame += delta;
this.x += this.dx * delta;
this.y += this.dy * delta;
this.theta += this.dTheta * delta;

const phi = (this.frame % 7777) / 7777;
const i = this.splineX.findIndex((x) => phi <= x) - 1;
const j = i + 1;

const rho = interpolation(
this.splineY[i],
this.splineY[j],
(phi - this.splineX[i]) / (this.splineX[j] - this.splineX[i]),
);

const phiPI = phi * PI2;

this.outer.style.left = this.x + rho * cos(phiPI) + "px";
this.outer.style.top = this.y + rho * sin(phiPI) + "px";
this.inner.style.transform = this.axis + this.theta + "deg)";

if (this.frame > 5000) {
this.opacity -= 0.01 * delta;
this.outer.style.opacity = this.opacity;
}

return this.y > height + config.deviation || this.opacity <= 0;
}
}

let frame = null;
const confetti = [];

// Cleanup function
const cleanup = () => {
cancelAnimationFrame(frame);
frame = null;
if (container.parentNode) {
document.body.removeChild(container);
}
confetti.length = 0;
};

// Main animation function
const poof = () => {
document.body.appendChild(container);

for (let i = 0; i < config.particles; i++) {
const confetto = new Confetto();
confetti.push(confetto);
container.appendChild(confetto.outer);
}

let prev;
const loop = (timestamp) => {
const delta = prev ? timestamp - prev : 0;
prev = timestamp;
const height = window.innerHeight;

for (let i = confetti.length - 1; i >= 0; --i) {
if (confetti[i].update(height, delta)) {
container.removeChild(confetti[i].outer);
confetti.splice(i, 1);
}
}

if (confetti.length) {
frame = requestAnimationFrame(loop);
} else {
cleanup();
}
};

frame = requestAnimationFrame(loop);
};

return poof;
};

export const poof = createConfetti();

0 comments on commit f7ecbd9

Please sign in to comment.