๐ŸŒŒ HTML Canvas๋กœ GROK ์˜๊ฐ์„ ๋ฐ›์€ Starfield์™€ Shooting Stars๋ฅผ ๋งŒ๋“  ๋ฐฉ๋ฒ• โœจ

๋ฐœํ–‰: (2025๋…„ 12์›” 13์ผ ์˜คํ›„ 05:35 GMT+9)
7 min read
์›๋ฌธ: Dev.to

Source: Dev.to

HTML Canvas ๋กœ GROK ์Šคํƒ€ํ•„๋“œ & ์œ ์„ฑ ํšจ๊ณผ ์žฌํ˜„ํ•˜๊ธฐ

GROKโ€‘์Šคํƒ€์ผ ์Šคํƒ€ํ•„๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ณธ ์ ์ด ์žˆ๋‹ค๋ฉด ๊ทธ ๋ถ„์œ„๊ธฐ๋ฅผ ์•Œ ๊ฒ๋‹ˆ๋‹ค: ๋А๋ฆฌ๊ณ  ์˜ํ™” ๊ฐ™์€ ํšŒ์ „, ์€์€ํ•œ ๊นœ๋นก์ž„, ๊ทธ๋ฆฌ๊ณ  ๊ฐ€๋”์”ฉ ์ง€๋‚˜๊ฐ€๋Š” ์œ ์„ฑ. ์ด ๊ธ€์—์„œ๋Š” ์ˆœ์ˆ˜ HTMLโ€ฏ+โ€ฏCSSโ€ฏ+โ€ฏJavaScript ๋กœ ๊ทธ ํšจ๊ณผ๋ฅผ ์žฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹จ๊ณ„๋ณ„๋กœ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค โ€” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„, ํ”„๋ ˆ์ž„์›Œํฌ๋„ ์—†์ด Canvas API๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

GitHub โญโ€ƒLive Demo ๐Ÿš€


๐ŸŒŒ ์ด ํšจ๊ณผ์— ํฌํ•จ๋œ ๋‚ด์šฉ

  • ์ „์ฒด ํ™”๋ฉด HTML5 <canvas>
  • ํ™”๋ฉด ๋ฐ–์„ ๋„๋Š” ์ˆ˜๋ฐฑ ๊ฐœ์˜ ํšŒ์ „ ๋ณ„
  • ์ž์—ฐ์Šค๋Ÿฌ์šด ๊นœ๋นก์ž„ / ๋ฐœ๊ด‘ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  • ๋“œ๋ฌผ๊ฒŒ ๋‚˜ํƒ€๋‚˜๋Š” ์šฐ์•„ํ•œ ์œ ์„ฑ
  • ๋ฐ˜์‘ํ˜• ๋ฆฌ์‚ฌ์ด์ฆˆ ์ฒ˜๋ฆฌ
    ๋ชจ๋‘ requestAnimationFrame ์œผ๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

index.html   โ†’ Canvas ์ปจํ…Œ์ด๋„ˆ
style.css    โ†’ ์ „์ฒด ํ™”๋ฉด ๊ฒ€์€ ๋ฐฐ๊ฒฝ
script.js    โ†’ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋กœ์ง & ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

๋นŒ๋“œ ๋‹จ๊ณ„ ์—†์ด index.html ์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๐Ÿงฑ HTML: ์ตœ์†Œ ์บ”๋ฒ„์Šค ์„ค์ •

์บ”๋ฒ„์Šค๋Š” ๋ทฐํฌํŠธ ์ „์ฒด๋ฅผ ์ฑ„์šฐ๋ฉฐ, ์šฐ๋ฆฌ๊ฐ€ ๊ทธ๋ฆฌ๋Š” ๋ชจ๋“  ์š”์†Œ์˜ ๋ Œ๋”๋ง ํ‘œ๋ฉด ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. JavaScript ํŒŒ์ผ์€ DOM์ด ์ค€๋น„๋œ ๋’ค์— ๋กœ๋“œ๋˜๋„๋ก ํ•˜๋‹จ์— ๋ฐฐ์น˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

๐ŸŽจ CSS: ์ „์ฒด ํ™”๋ฉด, ๋ฐฉํ•ด ์š”์†Œ ์—†์Œ

html, body {
  margin: 0;
  padding: 0;
  background: black;
  overflow: hidden;
  width: 100%;
  height: 100%;
}

#starfield {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
  • overflow: hidden ์€ ์Šคํฌ๋กค๋ฐ”๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • position: fixed ๋กœ ์บ”๋ฒ„์Šค๋ฅผ ํ™”๋ฉด์— ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค.
  • pointer-events: none ์€ UI ์š”์†Œ๊ฐ€ ์บ”๋ฒ„์Šค ์œ„์— ๋†“์ผ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿง  JavaScript: ํ•ต์‹ฌ ๊ฐœ๋…

Canvas & Context

const canvas = document.getElementById("starfield");
const ctx = canvas.getContext("2d");

๋ชจ๋“  ๊ทธ๋ฆฌ๊ธฐ๋Š” 2D ๋ Œ๋”๋ง ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ์ˆ˜๋™์œผ๋กœ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

โญ ๋ณ„ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ

๊ฐ ๋ณ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค:

{
  angle: Number,
  radius: Number,
  speed: Number,
  size: Number
}

๋ณ„์€ ๊ทน์ขŒํ‘œ ๋กœ ์ •์˜๋ฉ๋‹ˆ๋‹ค:

  • angle โ†’ ํšŒ์ „ ์œ„์น˜
  • radius โ†’ ์ค‘์‹ฌ์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์›ํ˜• ์›€์ง์ž„์„ ๊ฐ„๋‹จํžˆ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŒ ์บ”๋ฒ„์Šค ๋ฆฌ์‚ฌ์ด์ฆˆ ์ฒ˜๋ฆฌ

function resizeCanvas() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  initStars();
}

์œˆ๋„์šฐ ํฌ๊ธฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค:

  1. ์บ”๋ฒ„์Šค ํฌ๊ธฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
  2. ์ƒˆ ํฌ๊ธฐ์— ๋งž๊ฒŒ ๋ณ„์„ ๋‹ค์‹œ ์ƒ์„ฑํ•ด ์„ ๋ช…ํ•˜๊ณ  ๋ฐ˜์‘ํ˜•์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

โœจ ์Šคํƒ€ํ•„๋“œ ์ดˆ๊ธฐํ™”

const numStars = 360;

๋ณ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค:

stars = Array.from({ length: numStars }, () => ({
  angle: Math.random() * Math.PI * 2,
  radius: Math.random() * Math.sqrt(canvas.width ** 2 + canvas.height ** 2),
  speed: Math.random() * 0.0003 + 0.00015,
  size: Math.random() * 1.2 + 0.5,
}));
  • ๋ฌด์ž‘์œ„ ๊ฐ๋„๋กœ ๋ณ„์„ ๊ณ ๋ฅด๊ฒŒ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
  • ํฐ ๋ฐ˜๊ฒฝ์„ ์‚ฌ์šฉํ•ด ๋ณ„์ด ๋ทฐํฌํŠธ ๋ฐ–๊นŒ์ง€ ๋Œ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž‘์€ ๊ฐ์†๋„๋กœ ๋А๋ฆฌ๊ณ  ์˜ํ™” ๊ฐ™์€ ์›€์ง์ž„์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”„ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„

function animate() {
  requestAnimationFrame(animate);
  // clear, update, draw, spawn shooting stars, etc.
}
requestAnimationFrame(animate);

๊ฐ ํ”„๋ ˆ์ž„๋งˆ๋‹ค:

  1. ์บ”๋ฒ„์Šค๋ฅผ ์ง€์›๋‹ˆ๋‹ค.
  2. ๋ณ„ ์œ„์น˜๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
  3. ๋ณ„์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
  4. ์œ ์„ฑ์„ ์ƒ์„ฑํ• ์ง€ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.
  5. ๊ธฐ์กด ์œ ์„ฑ์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

๐ŸŒŸ ํšŒ์ „ ๋ณ„ ๊ทธ๋ฆฌ๊ธฐ

star.angle += star.speed;

const x = centerX + star.radius * Math.cos(star.angle);
const y = centerY + star.radius * Math.sin(star.angle);

์ „ํ˜•์ ์ธ ์›์šด๋™ ์ˆ˜์‹์ž…๋‹ˆ๋‹ค.

๊นœ๋นก์ž„ / ๋ฐœ๊ด‘ ํšจ๊ณผ

const flicker = 0.4 + Math.abs(Math.sin(Date.now() * 0.0015 + i)) * 0.5;

๋ฐ๊ธฐ๋Š” ๋‹ค์Œ์„ ํ†ตํ•ด ํ‰๋‚ด๋ƒ…๋‹ˆ๋‹ค:

  • ์ง„๋™ํ•˜๋Š” ํˆฌ๋ช…๋„.
  • ๋ณ„๋งˆ๋‹ค ์•ฝ๊ฐ„์”ฉ ๋‹ค๋ฅธ ์œ„์ƒ ์˜คํ”„์…‹.

๋‹จ์ˆœํ•˜๋ฉด์„œ๋„ ๋น ๋ฅด๊ณ  ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

โ˜„๏ธ ์œ ์„ฑ

์œ ์„ฑ์€ ์˜๋„์ ์œผ๋กœ ๋“œ๋ฌผ๊ฒŒ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค:

if (shootingStars.length === 0 && Math.random() < 0.01) {
  // create a new shooting star
}

ํ•œ ๋ฒˆ์— ํ•˜๋‚˜๋งŒ ์กด์žฌํ•˜๋„๋ก ํ•˜์—ฌ ์ˆœ๊ฐ„์„ ํŠน๋ณ„ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ฐ ์œ ์„ฑ์€ ์œ„์น˜, ์†๋„, ์ˆ˜๋ช…์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

๐ŸŽฏ ์œ ์„ฑ ๊ผฌ๋ฆฌ

๊ผฌ๋ฆฌ๋Š” ์„ ํ˜• ๊ทธ๋ผ๋””์–ธํŠธ ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค:

const grad = ctx.createLinearGradient(
  s.x,
  s.y,
  s.x - s.vx * 35,
  s.y - s.vy * 35
);

ํˆฌ๋ช…๋„๊ฐ€ ๊ผฌ๋ฆฌ ์ชฝ์œผ๋กœ ์ ์  ๋‚ฎ์•„์ ธ ํŒŒํ‹ฐํด ์—†์ด๋„ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ŠคํŠธ๋ฆญ ํšจ๊ณผ๋ฅผ ์ค๋‹ˆ๋‹ค.

โš™๏ธ ๊ฐ„ํŽธํ•œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

๋ชจ๋“  ํŠœ๋‹ ์˜ต์…˜์€ script.js ์— ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ€๋„

const numStars = 360;

๊นœ๋นก์ž„ ๊ฐ•๋„

// base brightness
0.4
// flicker amplitude
0.5

์œ ์„ฑ ๋ฐœ์ƒ ๋นˆ๋„

Math.random() < 0.01

์œ ์„ฑ ์†๋„

vx: 3 + Math.random() * 2,
vy: 1 + Math.random() * 1.5

๐Ÿ’ก ํ™•์žฅ ์•„์ด๋””์–ด

  • ๋งˆ์šฐ์Šค ๊ธฐ๋ฐ˜ ์‹œ์ฐจ ํšจ๊ณผ.
  • ์ƒ‰์ƒ์ด ๋ณ€ํ•˜๋Š” ๋ณ„๋“ค.
  • ์†๋„๊ฐ€ ๋‹ค๋ฅธ ๊นŠ์ด ๋ ˆ์ด์–ด.
  • ๊ทธ๋ผ๋””์–ธํŠธ ์šฐ์ฃผ ๋ฐฐ๊ฒฝ.
  • ์„ฑ์šด ๋…ธ์ด์ฆˆ ์˜ค๋ฒ„๋ ˆ์ด.

๐Ÿงช ์™œ ์ด ์ ‘๊ทผ๋ฒ•์ด ํšจ๊ณผ์ ์ธ๊ฐ€

  • ์˜์กด์„ฑ ์ œ๋กœ โ€“ ์ˆœ์ˆ˜ JavaScript์™€ Canvas๋งŒ ์‚ฌ์šฉ.
  • ๊ทนํžˆ ๋น ๋ฆ„ โ€“ ๊ฐ„๋‹จํ•œ ์ˆ˜ํ•™ ์—ฐ์‚ฐ + ์บ”๋ฒ„์Šค ๋ Œ๋”๋ง.
  • ์–ด๋””๋“  ์‰ฝ๊ฒŒ ์‚ฝ์ž… ๊ฐ€๋Šฅ (๋žœ๋”ฉ ํŽ˜์ด์ง€, ๋ฐฐ๊ฒฝ, ๋ฐ๋ชจ ๋“ฑ).
  • UI ๋ฐฐ๊ฒฝ์šฉ์œผ๋กœ ์ตœ์  โ€“ ์ˆœ์ˆ˜ JS + Canvas๋งŒ์œผ๋กœ๋„ ์–ผ๋งˆ๋‚˜ ๋ฉ‹์ง„ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”์ง€ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
Back to Blog

๊ด€๋ จ ๊ธ€

๋” ๋ณด๊ธฐ ยป

๐Ÿš€ ์ฒซ ์›น์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์–ด์š” โ€” ํ™•์ธํ•˜๊ณ , ํ‰๊ฐ€ํ•˜๊ณ , ๋ฆฌ๋ทฐํ•ด ์ฃผ์„ธ์š” ๐Ÿ™Œ

์•ˆ๋…• ๊ฐœ๋ฐœ์ž ์—ฌ๋Ÿฌ๋ถ„ ๐Ÿ‘‹ ์ €๋Š” BSc IT ํ•™์ƒ์ด์ž ์ดˆ๋ณด ์›น ๊ฐœ๋ฐœ์ž์ธ Tarun Mehra์ž…๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ personal website๋ฅผ ๋ง‰ ์™„์„ฑํ–ˆ์œผ๋ฉฐ, ์ด ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด t...

2026 ํ”„๋ก ํŠธ์—”๋“œ ๋กœ๋“œ๋งต: ์ทจ์—…์„ ์œ„ํ•œ 100% ๋ฌด๋ฃŒ ๋ฆฌ์†Œ์Šค

Frontend ๊ฐœ๋ฐœ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์›น์‚ฌ์ดํŠธ ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ถ€๋ถ„, ์ฆ‰ ์†Œํ”„ํŠธ์›จ์–ด์˜ clientโ€‘side ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์›น์‚ฌ์ดํŠธ๋ฅผ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”...

๐ŸŽจ ๋‚˜๋Š” 14์‚ด์— Developer Portfolio๋ฅผ ๋งŒ๋“ค์—ˆ์–ด์š” โ€” ๋ฐฐ์šด ์ 

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„! ์ €๋Š” 14์‚ด ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž์ด๊ณ , ๋ฐฉ๊ธˆ ๊ฐœ์ธ ํฌํŠธํด๋ฆฌ์˜ค ์›น์‚ฌ์ดํŠธ๋ฅผ ์™„์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•ด ๋ณด์„ธ์š”: ๐Ÿ‘‰ ์ „์ฒด๋ฅผ ์ œ๊ฐ€ ์ง์ ‘ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

์ž‘์€ ํ”„๋ก ํŠธ์—”๋“œ ์Šต๊ด€์ด ์ค‘์š”ํ•œ ์ด์œ : CSS์™€ JS Minification์˜ ์กฐ์šฉํ•œ ๊ฐ€์น˜

ํ”„๋ก ํŠธ์—”๋“œ ์—์…‹์„ ๋‹ค๋ฃจ๋‹ค ๋ณด๋ฉด performance ์ž‘์—…์ด ํ™”๋ คํ•œ optimizations์ด ์•„๋‹ˆ๋ผ ์กฐ์šฉํ•˜๊ณ  ์ผ์ƒ์ ์ธ ์Šต๊ด€ ์†์—์„œ ์ด๋ฃจ์–ด์ง„๋‹ค๋Š” ๊ฒƒ์„ ์ž์ฃผ ๋– ์˜ฌ๋ฆฌ๊ฒŒ ๋œ๋‹ค.