๋ง๋๋ ๋ฉ์ด์ปค๋ ํ์ฌ๊น์ง ์ด 2.5๋ง ๋ช ์ด์์ ์ฌ์ฉ์๊ฐ ์ด์ฉํ ์๋น์ค๋ก ์ฑ์ฅํ๋ค. ํนํ, ์ฐ๋ง๊ณผ ํฌ๋ฆฌ์ค๋ง์ค ์์ฆ์ ํ์ฉํด ๋ง์ ์ ์ ๊ฐ ์ ์ ๋์๊ณ , ์์๋ณด๋ค SEO(Search Engine Optimization) ํจ๊ณผ๋ฅผ ๋๋ฌด ์ ๋ฐ์์ ํญ๋ฐ์ ์ธ ํธ๋ํฝ ์ฆ๊ฐ๋ฅผ ๊ฒฝํํ๊ธฐ๋ ํ๋ค.
ํ์ง๋ง ๊ณ์ ์ด ๋ฐ๋๋ฉด์ ์๋ก์ด ๋ณํ๊ฐ ํ์ํ๋ค. ๊ฒจ์ธ ๋์ react-snowfall์ ํ์ฉํด ๋์ด ๋ด๋ฆฌ๋ ํจ๊ณผ๋ฅผ ์ถ๊ฐํ์์ง๋ง, ์ฌ์ฌ ๋ด์ด ๋ค๊ฐ์ค๋ฉด์ ์ ํ๊ธฐ, ์๋ก์ด ์์์ด ๋ง์ 3์์ ๋ง์ถฐ ๋์์ธ์ ์
๋ฐ์ดํธํด์ผ๊ฒ ๋ค๋ ํ์์ฑ์ด ์๊ฒผ๋ค.
๋ฟ๋ง ์๋๋ผ, Firestore ์ฌ์ฉ๋ ์ฆ๊ฐ๋ก ์ธํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋น์ฉ์ด ์ ์ ์ปค์ง๋ ๋ฌธ์ ๋ ๋ฐ์ํ๋ค. ์ด์ ๋ฐ๋ผ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ์ง์ธ๋ค๊ณผ ํจ๊ป ๋น์ฉ ์ต์ ํ ๋ฐ ํธ๋ํฝ ๋ถ์ฐ์ ์ํ ๋ฐฑ์๋ ๊ฐํธ ํ๋ก์ ํธ๋ฅผ ์์ํ๊ฒ ๋์๊ณ , ๊ทธ ๊ณผ์ ์์ UX ๋ฌธ์ ๋ฅผ ๊ฐ์ ํ๋ ์ฒซ ๋ฒ์งธ ์ ๋ฐ์ดํธ๋ฅผ ์งํํ๋ค.
๐ ์ด๋ฒ ์
๋ฐ์ดํธ์ ํต์ฌ ๋ชฉํ
โ๏ธ ๋ฒ๊ฝ ์ ๋๋ฉ์ด์
๊ตฌํ โ ์ด์ 5cm์ ๋ํ ์๋๋ฅผ ์ ์ฉํ์ฌ ๋ด ๋ถ์๊ธฐ ์กฐ์ฑ
โ๏ธ UX ๊ฐ์ โ ๊ทธ๋ฃน ์์ฑ ํ ๋น๋ฐ๋ฒํธ, ๋ฆฌ๋๋ช
์ ์ฅ ๊ธฐ๋ฅ ์ถ๊ฐํ์ฌ ์ ์ ํธ์์ฑ ํฅ์
โ๏ธ ๋์์ธ ๊ฐํธ โ teal(์ฒญ๋ก์)์์ ์ฐํํฌ ํ
๋ง๋ก UI ๋ณ๊ฒฝํ์ฌ ๋ด ์ปจ์
์ ๋ง์ถค
์ด ๊ธ์์๋ ๋ฒ๊ฝ ์ ๋๋ฉ์ด์ ์ ์ง์ ๊ตฌํํ ๊ณผ์ , UX๋ฅผ ๊ฐ์ ํ ๋ชจ๋ฌ ์ถ๊ฐ, ๊ทธ๋ฆฌ๊ณ MUI ํ ๋ง๋ฅผ ํ์ฉํ ํํฌํค UI ์ ์ฉ ๊ณผ์ ์ ์์ธํ ๋ค๋ค๋ณด๋ ค๊ณ ํ๋ค.
์ด๊ธฐ์ ์ฌ์ฉํ react-snowfall
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ด๋ฆ์์๋ ๊ทธ ์ ์ฒด์ฑ์ด ๋ํ๋๋ฏ์ด ๋ ๋ชจ์์ ์ค๋ ฅ ๊ธฐ๋ฐ ๋ํ๋ง ์ง์ํ๋ค. ํ์ง๋ง ๋ฒ๊ฝ์ ๊ณต๊ธฐ ์ ํญ๊ณผ ๋ฐ๋์ ์ํฅ์ ๋ฐ์ ํฉ๋ ๋ฆฌ๋ฉด์ ํ์ ํ๋ ํน์ฑ์ด ์๋ค.
๋ํ, ๋ฆฌ์กํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ง์ ๊ตฌํํ๋ ๊ฒ์ ํ๋ ์ ๋จ์ ์ ๋๋ฉ์ด์ ์ ๋ค๋ฃฐ ์ข์ ํ์ต ๊ธฐํ๊ฐ ๋ ๊ฒ ๊ฐ์๊ณ , ์ฃผ๋ง์ ์ด์ฉํด ์ง์ ๋ฒ๊ฝ ์ ๋๋ฉ์ด์ ์ ๋ง๋ค์ด ๋ณด๊ธฐ๋ก ๊ฒฐ์ ํ๋ค! ๐
โ๏ธ ๊ธฐ์กด ๋ ํจ๊ณผ โ
โ๏ธ ๋ฒ๊ฝ ํจ๊ณผ โ
โ ์ข์ฐ๋ก ํ๋ค๋ฆฌ๋ฉฐ ํ์ ํ๋ ์์ฐ์ค๋ฌ์ด ๋ํ ๊ตฌํ
๐ ๋ชฉํ
โ
์ด์ 5cm(189px/s) ์๋๋ก ๋จ์ด์ง๋ ๋ฒ๊ฝ ๊ตฌํ
โ
๋ฐ๋์ ์ํด ํ๋ค๋ฆฌ๋ ์์ฐ์ค๋ฌ์ด ์์ง์ ์ถ๊ฐ
โ
๋ฒ๊ฝ์ด ํ์ ํ๋ฉด์ ๋ํํ๋ ํจ๊ณผ ์ ์ฉ
๐ ํต์ฌ ๊ธฐ์
โ๏ธ Canvas API โ <canvas>
๋ฅผ ์ฌ์ฉํด ์ง์ ์ ๋๋ฉ์ด์
์ ๊ทธ๋ฆผ
โ๏ธ requestAnimationFrame() โ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
์ ์ํ ํ๋ ์ ๋จ์ ๋ ๋๋ง
โ๏ธ Math.sin() ํจ์ โ ์ข์ฐ ํ๋ค๋ฆผ ํจ๊ณผ๋ฅผ ์ํ ์ํ์ ๊ณ์ฐ
โ๏ธ ๋๋ค ๊ฐ ์ ์ฉ โ ๋ฒ๊ฝ ํฌ๊ธฐ, ์๋, ํฌ๋ช
๋๋ฅผ ๋ค๋ฅด๊ฒ ์ค์ ํ์ฌ ํ์ค๊ฐ ๊ทน๋ํ
์ ๋๋ฉ์ด์
์ ๋ ๋๋งํ ์บ๋ฒ์ค(Canvas API) ๋ฅผ ์์ฑํ๊ณ ,
๋ธ๋ผ์ฐ์ ์ฐฝ ํฌ๊ธฐ์ ๋ง์ถฐ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ๋ค.
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
๐ canvas.getContext("2d")
๋ฅผ ํธ์ถํ์ฌ 2D ๋๋ก์ ์ปจํ
์คํธ๋ฅผ ๊ฐ์ ธ์ด
๐ window.innerWidth
๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฒด ํ๋ฉด ํฌ๊ธฐ์ ๋ง์ถฐ ์บ๋ฒ์ค ํฌ๊ธฐ ์ค์
๊ฐ ๋ฒ๊ฝ ์์ ์์น(x, y), ํฌ๊ธฐ(size), ํ์ (rotation), ์๋(speedX, speedY) ๋ฅผ ๊ฐ๋๋ค.
class Petal {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 4 + 2;
this.speedX = Math.random() * 1.5 - 0.75;
this.speedY = Math.random() * fallSpeed + 1.5;
this.rotation = Math.random() * 360;
this.rotationSpeed = Math.random() * 1.5 - 0.75;
this.opacity = Math.random() * 0.7 + 0.3;
}
}
โ๏ธ ๋๋คํ ํฌ๊ธฐ, ์๋, ํฌ๋ช
๋ ์ค์ โ ๋ฒ๊ฝ์ด ์์ฐ์ค๋ฝ๊ฒ ๋ณด์ด๋๋ก ๋ณํ
โ๏ธ ์ข์ฐ ํ๋ค๋ฆผ ํจ๊ณผ(speedX) โ ๋ฐ๋์ ๋ ๋ฆฌ๋ ํจ๊ณผ ๊ตฌํ
โ๏ธ ๋๋ค ํ์ (rotation, rotationSpeed) โ ์์ฐ์ค๋ฝ๊ฒ ํ์ ํ๋ฉด์ ๋ํ
๋ฐ๋์ด ๋ถ๋ฉด ๋ฒ๊ฝ์ด ์ข์ฐ๋ก ํ๋ค๋ ค์ผ ํ๋ค.
์ด๋ฅผ ์ํด Math.sin()
ํจ์๋ฅผ ์ด์ฉํ์ฌ y๊ฐ(๋ํ ๊ฑฐ๋ฆฌ)์ ๋ฐ๋ผ x๊ฐ์ ํ๋ค๋ฆฌ๊ฒ ์ค์ ํ๋ค.
update() {
this.x += this.speedX + Math.sin(this.y / 90) * windStrength;
this.y += this.speedY;
this.rotation += this.rotationSpeed;
if (this.y > canvas.height) {
this.y = -10;
this.x = Math.random() * canvas.width;
this.speedX = Math.random() * 1.5 - 0.75;
}
}
โ๏ธ Math.sin(this.y / 90) * windStrength
โ y๊ฐ(๋ํ ๊ฑฐ๋ฆฌ)์ ๋ฐ๋ผ x๊ฐ์ด ์ฃผ๊ธฐ์ ์ผ๋ก ๋ณํํ๋ฉด์ ์ข์ฐ ํ๋ค๋ฆฌ๋ ํจ๊ณผ ์ถ๊ฐ
โ๏ธ this.speedY += gravity;
โ ๋ํ ์๋๋ฅผ ํ๋ ์๋ง๋ค ์ฆ๊ฐ์ํค๋ ๋ฐฉ์(์ค๋ ฅ ํจ๊ณผ)๋ ๊ณ ๋ คํ์ง๋ง, ๋ฒ๊ฝ์๋ ์ค๋ ฅ์ด ๊ฑฐ์ ์ํฅ์ ์ฃผ์ง ์๊ธฐ ๋๋ฌธ์ ์๋ต
๊ฐ ๋ฒ๊ฝ ๊ฐ์ฒด์ ์์น์ ๋ถ๋๋ฌ์ด ๊ณก์ ์ ๊ฐ์ง ๋ํ์ ๊ทธ๋ ค์ค
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate((this.rotation * Math.PI) / 180);
ctx.beginPath();
ctx.moveTo(0, -this.size);
ctx.quadraticCurveTo(this.size, -this.size, this.size, 0);
ctx.quadraticCurveTo(this.size, this.size, 0, this.size);
ctx.quadraticCurveTo(-this.size, this.size, -this.size, 0);
ctx.quadraticCurveTo(-this.size, -this.size, 0, -this.size);
ctx.fillStyle = `rgba(255, 182, 193, ${this.opacity})`;
ctx.fill();
ctx.restore();
}
โ๏ธ ctx.translate(this.x, this.y)
โ ๊ฐ ๋ฒ๊ฝ์ ์์น๋ก ์ด๋
โ๏ธ ctx.rotate((this.rotation * Math.PI) / 180)
โ ํ์ ํจ๊ณผ ์ ์ฉ
โ๏ธ ctx.fillStyle = rgba(255, 182, 193, this.opacity)
โ ์ฐํํฌ ๊ณ์ด ์์ ์ ์ฉ
์ ๋๋ฉ์ด์ ์ ์ง์์ ์ผ๋ก ์คํํ๊ธฐ ์ํด ์ฌ๊ท์ ์ผ๋ก animate()๋ฅผ ํธ์ถ
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
petals.forEach((petal) => {
petal.update();
petal.draw();
});
requestAnimationFrame(animate);
}
โ๏ธ ctx.clearRect(0, 0, canvas.width, canvas.height)
โ ๋งค ํ๋ ์๋ง๋ค ์บ๋ฒ์ค๋ฅผ ์ง์ฐ๊ณ ๋ค์ ๊ทธ๋ฆผ
โ๏ธ requestAnimationFrame(animate)
โ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
์คํ
๊ทธ ๊ฒฐ๊ณผ ์ด์ 5cm๋ฅผ ์ค์ํ ๋ง์ ๋๋ ๋ฒ๊ฝ ํจ๊ณผ๊ฐ ์๊ฒผ๋ค!
โ ๊ธฐ์กด ํฌ๋ฆฌ์ค๋ง์ค์ ๋ง์ถ teal(์ฒญ๋ก์) + red
์์์ด ๋ด ํ
๋ง์ ์ด์ธ๋ฆฌ์ง ์์
โ ๋ฒํผ ์์์ด ์ผ๊ด๋์ง ์์ ๊ฐ๋
์ฑ์ด ๋จ์ด์ง
โ UI ์์ ๊ฐ๊ฒฉ๊ณผ ์์ ์กฐํฉ์ด ์ด์ํ์ฌ ๋์์ธ ํ๋ฆฌํฐ ์ ํ
const theme = createTheme({
palette: {
primary: {
main: "#FF85A2", // ์ฐํํฌ ํ
๋ง
},
secondary: {
main: "#FF5C8A", // ๋ณด์กฐ ์์
},
},
});
๐ ThemeProvider๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ ์ ๋ฐ์ ๊ฑธ์ณ ํํฌ ํ ๋ง ์ ์ฉ
<Button
variant="outlined"
sx={{
color: "#D81B60", // ํํฌ ํฌ์ธํธ
borderColor: "#D81B60",
"&:hover": { backgroundColor: "#FFE3E3" }, // ์ฐํํฌ ํจ๊ณผ
}}
>
๋ฉ์ธ์ผ๋ก
</Button>
๐ ๊ธฐ์กด teal์ ๋ฅํํฌ(#D81B60) ๋ก ๋ณ๊ฒฝํ์ฌ ๋ด์ ์ด์ธ๋ฆฌ๋ UI ์์ฑ
๋ฐฑ์๋ ๊ฐ๋ฐ์๋ค๊ณผ ๋ ผ์ํ๋ ๊ณผ์ ์์, ์ ์ ๊ฐ ๊ทธ๋ฃน์ ์์ฑํ ํ ๋น๋ฐ๋ฒํธ, ๋ฆฌ๋๋ช , ๊ทธ๋ฃน๋ช ์ ์์ด๋ฒ๋ฆฌ๊ณ ๋ค์ ์์ฑํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค๋ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ๋ค.
โ๏ธ ๋์ผํ ๋ฆฌ๋๋ช
+ ์ด์ง ๋ค๋ฅธ ๊ทธ๋ฃน๋ช
์ ์ ์ฉํด ๋ค์ ์์ฑํ๋ ๊ฒฝ์ฐ๊ฐ ๋น๋ฒ
โ๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ค๋ณต๋ ๊ทธ๋ฃน์ด ์ง์์ ์ผ๋ก ์ถ๊ฐ๋๋ฉด์ ๋น์ฉ ์ฆ๊ฐ
โ
๊ทธ๋ฃน ์์ฑ ํ "์ ๋ณด ํ์ธ ๋ชจ๋ฌ" ์ถ๊ฐ
โ
ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ ๊ธฐ๋ฅ & ์นด์นด์คํก ๊ณต์ ๊ธฐ๋ฅ ์ ์ฉ
โ
์ ์ ๊ฐ ์ ๋ณด๋ฅผ ์ฝ๊ฒ ์ ์ฅํ ์ ์๋๋ก UX ๊ฐ์
<Button onClick={onCopy} sx={{ backgroundColor: "#FF85A2" }}>
ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ
</Button>
<Button onClick={onShare} sx={{ backgroundColor: "#FFD700" }}>
์นด์นด์คํก ๊ณต์
</Button>
๐ ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ ๋ฒํผ๊ณผ ์นด์นด์คํก ๊ณต์ ๋ฒํผ์ ์ถ๊ฐํ์ฌ, ์ ์ ๊ฐ ์ ๋ณด๋ฅผ ์ฝ๊ฒ ์ ์ฅํ ์ ์๋๋ก ํ๋ค.
๋ํ ์นดํก ๋์๊ฒ ์ ์ก ๊ธฐ๋ฅ์ ํตํด ์ด์ ๋ณด๋ค ๋ ํธํ๊ฒ ๋์ ์ ๋ณด๋ฅผ ๋ณด๊ดํ ์ ์๊ฒ ๋์๋ค.
์ด๋ฒ ์
๋ฐ์ดํธ๋ฅผ ํตํด,
โ๏ธ Canvas API๋ฅผ ํ์ฉํ ์์ฐ์ค๋ฌ์ด ๋ฒ๊ฝ ์ ๋๋ฉ์ด์
๊ตฌํ
โ๏ธ ๋ฐฑ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ต์ ํ๋ฅผ ์ํ UX ๊ฐ์
โ๏ธ MUI ํ
๋ง ์ค์ ์ ํ์ฉํ UI ๋์์ธ ๊ฐ์
๋ฅผ ์์ํ๋ค :D
๊ธฐ๋ฅ๋ฟ๋ง ์๋๋ผ, ์ฌ์ฉ์๊ฐ ์ค์ ๋ก ํธ๋ฆฌํ๊ฒ ์ด์ฉํ ์ ์๋๋ก UI/UX๋ฅผ ๊ณ ๋ฏผํ๋ ๊ณผ์ ์ด ์ค์ํ๋ค๋ ์ ์ ๋ค์ ํ๋ฒ ๊นจ๋ฌ์๋ค.
์ด์ ๋ง๋๋ ๋ฉ์ด์ปค๋ ๋ ๊ฐ์ฑ์ ์ธ ๋ด ์๋น์ค๊ฐ ๋์๋ค!
์์ง 2์ ์ค์์ด๋ค