๐ŸŒŸ React 19 + Vite + p5.js ํ”„๋กœ์ ํŠธ ์…‹์—… 2

BamgasiJMยท2026๋…„ 7์›” 8์ผ

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
5/67
post-thumbnail

Instance Mode์™€ ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜์˜ ํ†ตํ•ฉ

React ํ™˜๊ฒฝ์—์„œ p5.js๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์„œ๋“œํŒŒํ‹ฐ ๋ž˜ํผ(Wrapper) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ง€์–‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ž˜ํผ๋Š” p5์˜ ๋‚ด๋ถ€ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ๋ธ”๋ž™๋ฐ•์Šคํ™”ํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์œ ๋ฐœํ•˜๊ณ , ๋ณต์žกํ•œ ์ธํ„ฐ๋ž™์…˜์ด๋‚˜ ์ปค์Šคํ…€ ์…ฐ์ด๋” ์ ์šฉ ์‹œ API ์ ‘๊ทผ์„ฑ์„ ์ œํ•œํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” React์˜ ํ›…(Hook)์„ ์ด์šฉํ•ด p5์˜ Instance Mode๋ฅผ ์ง์ ‘ ๋ฐ”์ธ๋”ฉํ•˜๊ณ , ์•„ํŠธ์›Œํฌ ๋กœ์ง์„ sketch.js๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์•„๊ฐ€ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ง„์ž…์ ๊ณผ ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ์›์น™๊นŒ์ง€ ํ•จ๊ป˜ ๋‹ค๋ฃน๋‹ˆ๋‹ค.


1. ํ™˜๊ฒฝ ์„ธํŒ… ๋ฐ ํŒŒ์ผ ๊ตฌ์กฐ

ํ„ฐ๋ฏธ๋„ ๋ช…๋ น์–ด

Vite๋ฅผ ์ด์šฉํ•ด React ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , p5.js๋ฅผ ์˜์กด์„ฑ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

# Vite React ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (JavaScript ํ…œํ”Œ๋ฆฟ ์‚ฌ์šฉ)
npm create vite@latest p5-react-art -- --template react

# ๋””๋ ‰ํ† ๋ฆฌ ์ด๋™ ๋ฐ ์˜์กด์„ฑ ์„ค์น˜
cd p5-react-art
npm install

# p5.js ์„ค์น˜ (๋ž˜ํผ ์—†์ด ์ฝ”์–ด๋งŒ ์„ค์น˜)
npm install p5

# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
npm run dev

ํŒŒ์ผ ๊ตฌ์กฐ

์•„ํŠธ์›Œํฌ ๋กœ์ง๊ณผ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€์‹ฌ์‚ฌ(Separation of Concerns)๋ฅผ ๋‚˜๋ˆ•๋‹ˆ๋‹ค.

p5-react-art/
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ public/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ assets/
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ””โ”€โ”€ P5Canvas.jsx      # p5 ์ธ์Šคํ„ด์Šค๋ฅผ ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธํ•˜๋Š” React ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ sketch.js             # p5.js ์•„ํŠธ์›Œํฌ ๋กœ์ง (Instance Mode ํ•จ์ˆ˜)
โ”‚   โ”œโ”€โ”€ App.jsx               # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์•„์›ƒ ๋ฐ ์ปดํฌ๋„ŒํŠธ ์กฐํ•ฉ
โ”‚   โ”œโ”€โ”€ main.jsx              # React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ง„์ž…์ 
โ”‚   โ””โ”€โ”€ index.css             # ์ „์—ญ ์Šคํƒ€์ผ ์ดˆ๊ธฐํ™”
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ vite.config.js

2. ํ•ต์‹ฌ ์›๋ฆฌ 1: Instance Mode์™€ ์ปดํฌ๋„ŒํŠธ ๋ผ์ดํ”„์‚ฌ์ดํด

p5.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ setup()๊ณผ draw()๋ฅผ ์ „์—ญ(window) ์Šค์ฝ”ํ”„์— ์„ ์–ธํ•˜๋Š” Global Mode๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ React๋Š” ๋‹จ์ผ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(SPA)์ด๋ฉฐ, ์ปดํฌ๋„ŒํŠธ์˜ ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ๊ฐ€ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

Global Mode๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋˜์–ด๋„ window์— ๋ฐ”์ธ๋”ฉ๋œ p5 ์ธ์Šคํ„ด์Šค์™€ requestAnimationFrame ๋ฃจํ”„๊ฐ€ ํŒŒ๊ดด๋˜์ง€ ์•Š์•„ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜(Memory Leak) ๋ฐ ์ข€๋น„ ์บ”๋ฒ„์Šค(Zombie Canvas) ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Instance Mode๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Instance Mode๋Š” p5์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ์™€ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ „์—ญ ์Šค์ฝ”ํ”„๊ฐ€ ์•„๋‹Œ, ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ์ธ์ž(p)๋กœ ์ „๋‹ฌ๋˜๋Š” ๋กœ์ปฌ ์Šค์ฝ”ํ”„๋กœ ์บก์Аํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด React์˜ useEffect cleanup ํ•จ์ˆ˜์—์„œ p.remove()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ DOM๊ณผ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ•ด์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


3. ํ•ต์‹ฌ ์›๋ฆฌ 2: ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ์™€ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„

3.1. main.jsx์˜ ์—ญํ• : ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘ (Bootstrapping)

main.jsx๋Š” C๋‚˜ Java์˜ main() ํ•จ์ˆ˜์ฒ˜๋Ÿผ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜๋Š” ๊ฐ€์žฅ ์ฒซ ๋ฒˆ์งธ ์ง„์ž…์ (Entry Point) ์ž…๋‹ˆ๋‹ค.

Vite๊ฐ€ index.html์„ ๋กœ๋“œํ•˜๋ฉด, HTML ๋‚ด๋ถ€์˜ <script type="module" src="/src/main.jsx"></script> ํƒœ๊ทธ๋ฅผ ํ†ตํ•ด ์ด ํŒŒ์ผ์ด ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. main.jsx์˜ ํ•ต์‹ฌ ์—ญํ• ์€ ๊ฐ€์ƒ DOM(Virtual DOM)๊ณผ ์‹ค์ œ ๋ธŒ๋ผ์šฐ์ € DOM์„ ์—ฐ๊ฒฐ(Bridging) ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

// src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

๊ธฐ์ˆ ์  ์˜๋ฏธ:

  • DOM ์ฃผ์ž…: React๋Š” ์ž์ฒด์ ์ธ ๋ Œ๋”๋ง ์—”์ง„์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. main.jsx๋Š” React์—๊ฒŒ "์ด #root div ์•ˆ์— ๋„ค๊ฐ€ ์›ํ•˜๋Š” ๋Œ€๋กœ UI๋ฅผ ๊ทธ๋ ค์ค˜"๋ผ๊ณ  ์ง€์‹œํ•˜๋Š” ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
  • ์ „์—ญ ์„ค์ • ์ฃผ์ž…: ์ „์—ญ CSS(index.css)๋‚˜ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ(Redux, Zustand ๋“ฑ), ๋ผ์šฐํ„ฐ(BrowserRouter) ๋“ฑ์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ตœ์ƒ๋‹จ์— ์ฃผ์ž…ํ•˜๋Š” ๊ณณ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

3.2. P5Canvas.jsx๋ฅผ components๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ์ด์œ 

๋‹จ์ˆœํžˆ "ํŒŒ์ผ์„ ๋‚˜๋ˆ„๊ธฐ ์œ„ํ•จ"์ด ์•„๋‹ˆ๋ผ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ 3๊ฐ€์ง€ ํ•ต์‹ฌ ์›์น™์„ ์ง€ํ‚ค๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

โ‘  ๋ถˆ์ˆœ๋ฌผ(Impurity)๊ณผ ๋ถ€์ž‘์šฉ(Side Effects)์˜ ์บก์Аํ™”

React์˜ ์ฒ ํ•™์€ ์„ ์–ธํ˜•(Declarative) ์ด๋ฉฐ, ์ปดํฌ๋„ŒํŠธ๋Š” ๊ฐ€๋Šฅํ•˜๋ฉด ์ˆœ์ˆ˜ ํ•จ์ˆ˜(Pure Function) ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๊ฐ™์€ ์ธํ’‹์—๋Š” ํ•ญ์ƒ ๊ฐ™์€ ์•„์›ƒํ’‹์ด ๋‚˜์™€์•ผ ํ•จ).
ํ•˜์ง€๋งŒ p5.js๋Š” ๋ณธ์งˆ์ ์œผ๋กœ ๋ช…๋ นํ˜•(Imperative) ์ด๋ฉฐ, DOM์„ ์ง์ ‘ ์กฐ์ž‘ํ•˜๊ณ  requestAnimationFrame์ด๋ผ๋Š” ๊ฑฐ๋Œ€ํ•œ ๋ถ€์ž‘์šฉ(Side Effects) ์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ '๋ถˆ์ˆœ๋ฌผ'์„ App.jsx์— ์ง์ ‘ ์„ž์–ด ์“ฐ๋ฉด, React์˜ ์ƒํƒœ(State)์™€ p5์˜ ์ƒํƒœ๊ฐ€ ๋’ค์—‰์ผœ ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์ง€์˜ฅ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. P5Canvas.jsx๋กœ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ "์ด ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ๋งŒ p5์˜ ๋ถ€์ž‘์šฉ์ด ๋ฐœ์ƒํ•˜๊ณ , ์–ธ๋งˆ์šดํŠธ ์‹œ ๊นจ๋—ํ•˜๊ฒŒ ์ฒญ์†Œ๋œ๋‹ค" ๋Š” ๊ฒƒ์„ ๋ช…ํ™•ํžˆ ์บก์Аํ™”(Encapsulation)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ‘ก ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ (Separation of Concerns)

  • App.jsx (Orchestrator / ์ง€ํœ˜์ž): ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ๋ ˆ์ด์•„์›ƒ, ๋ผ์šฐํŒ…, UI ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๋ฐฐ์น˜, ์ „์—ญ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
  • P5Canvas.jsx (Implementer / ์‹คํ–‰์ž): ์ฃผ์–ด์ง„ ์˜์—ญ์— p5.js๋ฅผ ํ™œ์šฉํ•ด ํŠน์ • ๋น„์ฃผ์–ผ์„ ๋ Œ๋”๋งํ•˜๋Š” ์—ญํ• ๋งŒ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ App.jsx ์•ˆ์— p5 ์ฝ”๋“œ๊ฐ€ ์ง์ ‘ ์žˆ๋‹ค๋ฉด, ๋‚˜์ค‘์— ์›น์‚ฌ์ดํŠธ์— '๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”'๋‚˜ 'ํ‘ธํ„ฐ'๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๋ ˆ์ด์•„์›ƒ ์ฝ”๋”ฉ๊ณผ ๊ทธ๋ž˜ํ”ฝ์Šค ์ฝ”๋”ฉ์ด ๋’ค์„ž์—ฌ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด App.jsx๋Š” ๊ทธ์ € <P5Canvas />๋ผ๋Š” ํƒœ๊ทธ๋งŒ ๋ณด๊ณ  "์•„, ์—ฌ๊ธฐ์— ๊ทธ๋ž˜ํ”ฝ์ด ๋“ค์–ด๊ฐ€๋Š”๊ตฌ๋‚˜"๋ผ๊ณ  ์ถ”์ƒํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ‘ข ์žฌ์‚ฌ์šฉ์„ฑ (Reusability)๊ณผ ์กฐํ•ฉ์„ฑ (Composability)

์•ž์œผ๋กœ ์›น์‚ฌ์ดํŠธ๊ฐ€ ์ปค์ ธ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์ด ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋žœ๋”ฉ ํŽ˜์ด์ง€์—๋Š” ํฐ ํŒŒํ‹ฐํด ๋ฐฐ๊ฒฝ์ด ํ•„์š”ํ•˜๊ณ ,
  • About ํŽ˜์ด์ง€์—๋Š” ์ž‘์€ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋กœ๊ณ ๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ,
  • 404 ํŽ˜์ด์ง€์—๋Š” ๋‹ค๋ฅธ ์—๋Ÿฌ ๊ทธ๋ž˜ํ”ฝ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

P5Canvas.jsx๊ฐ€ ๋…๋ฆฝ๋œ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ณ , sketch.js์˜ ๋กœ์ง์„ ์ธ์ž๋กœ ๋ฐ›๊ฑฐ๋‚˜ Props๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ์„ค๊ณ„ํ•ด ๋‘”๋‹ค๋ฉด, ํ•˜๋‚˜์˜ ์•„ํŠธ์›Œํฌ ์—”์ง„์„ ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// App.jsx์˜ ๋ฏธ๋ž˜ ๋ชจ์Šต ์˜ˆ์‹œ
function App() {
  return (
    <div className="app-layout">
      <Header />
      <main>
        {/* ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ p5 ์ปดํฌ๋„ŒํŠธ */}
        <P5Canvas sketch={particleSketch} width={800} height={600} />
        <P5Canvas sketch={logoSketch} width={200} height={200} />
      </main>
      <Footer />
    </div>
  );
}

4. ์ฝ”๋“œ ๊ตฌํ˜„

4.1. ์•„ํŠธ์›Œํฌ ๋กœ์ง ๋ถ„๋ฆฌ (src/sketch.js)

sketch.js๋Š” p5 ์ธ์Šคํ„ด์Šค(p)๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ setup๊ณผ draw๋ฅผ ์ •์˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์—๋Š” React ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ์ „ํ˜€ ํฌํ•จ๋˜์ง€ ์•Š์•„, ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๊ทธ๋ž˜ํ”ฝ ์•Œ๊ณ ๋ฆฌ์ฆ˜์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// src/sketch.js

/**
 * p5 Instance Mode Sketch
 * @param {object} p - p5 ์ธ์Šคํ„ด์Šค ์ปจํ…์ŠคํŠธ
 */
export default function sketch(p) {
  let particles = [];
  const NUM_PARTICLES = 150;

  // p.setup: ์บ”๋ฒ„์Šค ์ดˆ๊ธฐํ™” ๋ฐ ์ƒํƒœ ์„ค์ •
  p.setup = () => {
    // React ์ปดํฌ๋„ŒํŠธ์˜ DOM ๋…ธ๋“œ ํฌ๊ธฐ์— ๋งž์ถฐ ์บ”๋ฒ„์Šค ์ƒ์„ฑ
    // ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ์˜ ํฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜๋ฏ€๋กœ ๋ฐ˜์‘ํ˜• ๋Œ€์‘์ด ์šฉ์ดํ•จ
    const container = p.canvas.parentElement;
    p.createCanvas(container.offsetWidth, container.offsetHeight);
    p.colorMode(p.HSB, 360, 100, 100, 100);
    p.background(0, 0, 10);

    // ํŒŒํ‹ฐํด ์ดˆ๊ธฐํ™”
    for (let i = 0; i < NUM_PARTICLES; i++) {
      particles.push({
        pos: p.createVector(p.random(p.width), p.random(p.height)),
        vel: p.createVector(0, 0),
        hue: p.random(180, 260),
      });
    }
  };

  // p.draw: ํ”„๋ ˆ์ž„๋ณ„ ๋ Œ๋”๋ง ๋ฃจํ”„
  p.draw = () => {
    // ์ž”์ƒ ํšจ๊ณผ๋ฅผ ์œ„ํ•œ ๋ฐ˜ํˆฌ๋ช… ๋ฐฐ๊ฒฝ (ํŠธ๋ ˆ์ผ ํšจ๊ณผ)
    p.background(0, 0, 10, 15);

    const time = p.millis() * 0.0005;

    particles.forEach((pt) => {
      // ํŽ„๋ฆฐ ๋…ธ์ด์ฆˆ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฒกํ„ฐ ํ•„๋“œ ์ƒ์„ฑ
      const angle = p.noise(pt.pos.x * 0.003, pt.pos.y * 0.003, time) * p.TWO_PI * 2;
      
      // ๋…ธ์ด์ฆˆ ๊ธฐ๋ฐ˜ ๊ฐ๋„๋ฅผ ์†๋„ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜
      pt.vel.set(p.cos(angle), p.sin(angle));
      pt.vel.mult(1.5);
      
      // ์œ„์น˜ ๊ฐฑ์‹  ๋ฐ ํ™”๋ฉด ๊ฒฝ๊ณ„ ๋ž˜ํ•‘(Wrapping)
      pt.pos.add(pt.vel);
      if (pt.pos.x < 0) pt.pos.x = p.width;
      if (pt.pos.x > p.width) pt.pos.x = 0;
      if (pt.pos.y < 0) pt.pos.y = p.height;
      if (pt.pos.y > p.height) pt.pos.y = 0;

      // ๋ Œ๋”๋ง
      p.noStroke();
      p.fill(pt.hue, 80, 90, 60);
      p.circle(pt.pos.x, pt.pos.y, 3);
    });
  };

  // p.windowResized: ๋ธŒ๋ผ์šฐ์ € ์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ ์บ”๋ฒ„์Šค ๋ฆฌ์‚ฌ์ด์ฆˆ
  // React ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ฐฝ ํฌ๊ธฐ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋„๋ก ์„ค์ •ํ•œ ๊ฒฝ์šฐ ์œ ์šฉ
  p.windowResized = () => {
    const container = p.canvas.parentElement;
    p.resizeCanvas(container.offsetWidth, container.offsetHeight);
  };
}

4.2. React ๋ฐ”์ธ๋”ฉ ์ปดํฌ๋„ŒํŠธ (src/components/P5Canvas.jsx)

React ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” useRef๋กœ DOM ์š”์†Œ์™€ p5 ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•˜๊ณ , useEffect๋ฅผ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค์˜ ์ƒ์„ฑ๊ณผ ํŒŒ๊ดด๋ฅผ ํ†ต์ œํ•ฉ๋‹ˆ๋‹ค.

// src/components/P5Canvas.jsx
import { useRef, useEffect } from 'react';
import p5 from 'p5';
import sketch from '../sketch';

export default function P5Canvas() {
  // 1. DOM ์ฐธ์กฐ ๋ฐ p5 ์ธ์Šคํ„ด์Šค ์ฐธ์กฐ๋ฅผ ์œ„ํ•œ Ref ์ƒ์„ฑ
  const containerRef = useRef(null);
  const p5InstanceRef = useRef(null);

  useEffect(() => {
    // 2. ๋งˆ์šดํŠธ ์‹œ p5 ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
    // sketch ํ•จ์ˆ˜์™€ DOM ์ปจํ…Œ์ด๋„ˆ๋ฅผ p5 ์ƒ์„ฑ์ž์— ์ „๋‹ฌ
    if (containerRef.current) {
      p5InstanceRef.current = new p5(sketch, containerRef.current);
    }

    // 3. ์–ธ๋งˆ์šดํŠธ ์‹œ Cleanup (Destructor)
    return () => {
      if (p5InstanceRef.current) {
        // p5.remove()๋Š” ์บ”๋ฒ„์Šค DOM์„ ์ œ๊ฑฐํ•˜๊ณ , 
        // requestAnimationFrame ๋ฃจํ”„๋ฅผ ์ค‘๋‹จํ•˜๋ฉฐ, ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ํ•ด์ œํ•จ
        p5InstanceRef.current.remove(); 
      }
    };
  }, []); // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์‹คํ–‰

  // 4. p5๊ฐ€ ์บ”๋ฒ„์Šค๋ฅผ ์ฃผ์ž…ํ•  ๋นˆ DOM ์ปจํ…Œ์ด๋„ˆ ๋ Œ๋”๋ง
  return (
    <div 
      ref={containerRef} 
      style={{ 
        width: '100vw', 
        height: '100vh', 
        overflow: 'hidden',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#0a0a0a'
      }} 
    />
  );
}

4.3. ์•ฑ ์ง„์ž…์  ๋ฐ ์Šคํƒ€์ผ ์ดˆ๊ธฐํ™”

๊ธฐ๋ณธ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์˜ ๋ถˆํ•„์š”ํ•œ ๋งˆ์ง„์„ ์ œ๊ฑฐํ•˜์—ฌ ์บ”๋ฒ„์Šค๊ฐ€ ํ™”๋ฉด์„ ๊ฐ€๋“ ์ฑ„์šฐ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

src/App.jsx

import P5Canvas from './components/P5Canvas';

function App() {
  return <P5Canvas />;
}

export default App;

src/index.css

/* p5 ์บ”๋ฒ„์Šค์˜ ๊ธฐ๋ณธ margin ๋ฐ body ์Šคํฌ๋กค ์ œ๊ฑฐ */
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: #0a0a0a;
}

/* p5๊ฐ€ ์ƒ์„ฑํ•˜๋Š” canvas ์š”์†Œ์˜ ๊ธฐ๋ณธ ์Šคํƒ€์ผ ์กฐ์ • */
canvas {
  display: block;
}

5. ์•„ํ‹ฐ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๊ธฐ์ˆ ์  ๊ฐ€์ด๋“œ

  1. ์ƒํƒœ(State) ๊ด€๋ฆฌ์˜ ๋ถ„๋ฆฌ:
    React์˜ useState๋กœ ์•„ํŠธ์›Œํฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ(์˜ˆ: ํŒŒํ‹ฐํด ์ˆ˜, ๋…ธ์ด์ฆˆ ์Šค์ผ€์ผ)๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ์ด๋ฅผ sketch.js์— ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค๋ฉด useEffect์˜ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ํ•ด๋‹น ์ƒํƒœ๋ฅผ ๋„ฃ์ง€ ๋งˆ์‹ญ์‹œ์˜ค. ๋Œ€์‹  p5InstanceRef.current๋ฅผ ํ†ตํ•ด p5 ์ธ์Šคํ„ด์Šค์˜ ์ปค์Šคํ…€ ํ”„๋กœํผํ‹ฐ์— ์ง์ ‘ ํ• ๋‹นํ•˜๊ฑฐ๋‚˜, React State๋ฅผ sketch.js ๋‚ด๋ถ€์˜ ํด๋กœ์ €/๋ชจ๋“ˆ ๋ ˆ๋ฒจ ๋ณ€์ˆ˜์™€ ๋™๊ธฐํ™”ํ•˜๋Š” ํŒจํ„ด์„ ์‚ฌ์šฉํ•ด์•ผ ๋ถˆํ•„์š”ํ•œ re-render์™€ p5 ์ธ์Šคํ„ด์Šค ์žฌ์ƒ์„ฑ์„ ๋ง‰์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. DOM ์ ‘๊ทผ:
    sketch.js ๋‚ด๋ถ€์—์„œ document.getElementById ๋“ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ง€์–‘ํ•˜์‹ญ์‹œ์˜ค. ๋Œ€์‹  p.canvas.parentElement๋ฅผ ํ†ตํ•ด React๊ฐ€ ์ฃผ์ž…ํ•œ ์ปจํ…Œ์ด๋„ˆ ๋…ธ๋“œ์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜, React ์ปดํฌ๋„ŒํŠธ์—์„œ ref๋ฅผ ํ†ตํ•ด ๊ณ„์‚ฐ๋œ ํฌ๊ธฐ๋ฅผ sketch ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด ์ˆœ์ˆ˜์„ฑ(Purity)์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
  3. ๊ณ ์„ฑ๋Šฅ ๋ Œ๋”๋ง:
    p5 ์ฝ”์–ด๋Š” 2D ๋ Œ๋”๋Ÿฌ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. WebGL ๊ธฐ๋ฐ˜์˜ 3D ๋˜๋Š” ๊ณ ์„ฑ๋Šฅ 2D ์…ฐ์ด๋”๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด p.createCanvas(w, h, p.WEBGL)๋กœ ์ƒ์„ฑ์ž๋ฅผ ๋ณ€๊ฒฝํ•˜์‹ญ์‹œ์˜ค. ๋‹จ, WEBGL ๋ชจ๋“œ์—์„œ๋Š” ์ขŒํ‘œ๊ณ„๊ฐ€ ์ค‘์•™ origin(0,0)์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋ฏ€๋กœ ๊ธฐํ•˜ํ•™ ์—ฐ์‚ฐ ์‹œ ์ด๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

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