Skip to content

Commit

Permalink
feat: add answer color-based snowflake effect
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeong-jj committed Dec 9, 2024
1 parent af57577 commit d597fb7
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 14 deletions.
10 changes: 9 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import Routing from "@/routes";

import Snowfall from "@/components/common/snowFall";
import "./components/common/snowFall.css";

function App() {
return <Routing />;
return (
<>
<Snowfall />
<Routing />
</>
);
}

export default App;
26 changes: 26 additions & 0 deletions src/components/common/snowFall.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
body {
margin: 0;
overflow: hidden;
}

.snowflake {
position: absolute;
top: -10px;
width: 10px;
height: 10px;
background: white;
border-radius: 50%;
opacity: 0.8;
animation: fall linear infinite;
}

@keyframes fall {
0% {
transform: translateY(0) translateX(0);
opacity: 1;
}
100% {
transform: translateY(100vh) translateX(20px);
opacity: 0.5;
}
}
44 changes: 44 additions & 0 deletions src/components/common/snowFall.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { answerAPI } from "@/api/answer";
import { useEffect, useState } from "react";

const Snowfall = () => {
const snowflakes = Array.from({ length: 50 });

const [snowColorArray, setSnowColorArray] = useState<string[]>([]);

const handleSnowflakeColor = async (memberId: number) => {
await answerAPI.list({ memberId }).then((res) => {
const data = res.data.data;

const colorArr = data.list?.map((answer) => answer.colorCode);
if (colorArr?.length) {
setSnowColorArray(colorArr);
}
});
};

const userId = Number(localStorage.getItem("userId"));
useEffect(() => {
handleSnowflakeColor(userId);
}, [userId]);

return (
<>
{snowflakes.map((_, index) => (
<div
key={index}
className="snowflake"
style={{
left: `${Math.random() * 100}vw`,
animationDuration: `${Math.random() * 3 + 2}s`,
animationDelay: `${Math.random() * 5}s`,
background:
snowColorArray[Math.floor(Math.random() * snowColorArray.length)],
}}
></div>
))}
</>
);
};

export default Snowfall;
81 changes: 68 additions & 13 deletions src/lib/colorTools.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,74 @@
export function hexToRgba(hexColor: string, opacity: number = 1) {
const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
};
const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
};
const rgbToHex = (r: number, g: number, b: number): string => {
return (
"#" +
[r, g, b]
.map((x) => {
const hex = x.toString(16);
return hex.length === 1 ? "0" + hex : hex;
})
.join("")
);
};

export function hexToRgba(hexColor: string, opacity: number = 1) {
const rgb = hexToRgb(hexColor);
const backgroundColor = rgb
const convertedCode = rgb
? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`
: hexColor;

return backgroundColor;
return convertedCode;
}

export function calculateMedianColor(
hexColors: string[],
isRgba?: boolean
): string {
const calculateMedian = (numbers: number[]): number => {
const sorted = numbers.slice().sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);

if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}

return sorted[middle];
};

const rgbColors = hexColors
.map((hex) => {
const rgba = hexToRgba(hex);
const match = rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
return match
? [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
: null;
})
.filter((rgb): rgb is number[] => rgb !== null);

if (rgbColors.length === 0) {
throw new Error("No valid colors provided");
}

const rs = rgbColors.map((rgb) => rgb[0]);
const gs = rgbColors.map((rgb) => rgb[1]);
const bs = rgbColors.map((rgb) => rgb[2]);

// 각 채널의 중앙값 계산
const medianR = Math.round(calculateMedian(rs));
const medianG = Math.round(calculateMedian(gs));
const medianB = Math.round(calculateMedian(bs));

// 결과를 hex 코드로 변환하여 반환
return isRgba
? `rgb(${medianR}, ${medianG}, ${medianB})`
: rgbToHex(medianR, medianG, medianB);
}

0 comments on commit d597fb7

Please sign in to comment.