
React ํ๊ฒฝ์์ p5.js๋ฅผ ์ฌ์ฉํ ๋ ์๋ํํฐ ๋ํผ(Wrapper) ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ง์ํด์ผ ํฉ๋๋ค. ๋ํผ๋ p5์ ๋ด๋ถ ๋ผ์ดํ์ฌ์ดํด์ ๋ธ๋๋ฐ์คํํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ ๋ฐํ๊ณ , ๋ณต์กํ ์ธํฐ๋์ ์ด๋ ์ปค์คํ ์ ฐ์ด๋ ์ ์ฉ ์ API ์ ๊ทผ์ฑ์ ์ ํํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด ๊ธ์์๋ React์ ํ
(Hook)์ ์ด์ฉํด p5์ Instance Mode๋ฅผ ์ง์ ๋ฐ์ธ๋ฉํ๊ณ , ์ํธ์ํฌ ๋ก์ง์ sketch.js๋ก ๋ถ๋ฆฌํ๋ ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ๊ตฌ์ถํฉ๋๋ค. ๋์๊ฐ React ์ ํ๋ฆฌ์ผ์ด์
์ ์ง์
์ ๊ณผ ์ปดํฌ๋ํธ ์ํคํ
์ฒ ์ค๊ณ ์์น๊น์ง ํจ๊ป ๋ค๋ฃน๋๋ค.
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
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๊ณผ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ช
์์ ์ผ๋ก ํด์ ํ ์ ์์ต๋๋ค.
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>,
)
๊ธฐ์ ์ ์๋ฏธ:
main.jsx๋ React์๊ฒ "์ด #root div ์์ ๋ค๊ฐ ์ํ๋ ๋๋ก UI๋ฅผ ๊ทธ๋ ค์ค"๋ผ๊ณ ์ง์ํ๋ ๋ถํธ์คํธ๋ฉ ์ฝ๋์
๋๋ค.index.css)๋ ์ ์ญ ์ํ ๊ด๋ฆฌ(Redux, Zustand ๋ฑ), ๋ผ์ฐํฐ(BrowserRouter) ๋ฑ์ ์ ํ๋ฆฌ์ผ์ด์
์ต์๋จ์ ์ฃผ์
ํ๋ ๊ณณ์ด๊ธฐ๋ ํฉ๋๋ค.P5Canvas.jsx๋ฅผ components๋ก ๋ถ๋ฆฌํ๋ ์ด์ ๋จ์ํ "ํ์ผ์ ๋๋๊ธฐ ์ํจ"์ด ์๋๋ผ, ๋ค์๊ณผ ๊ฐ์ 3๊ฐ์ง ํต์ฌ ์์น์ ์งํค๊ธฐ ์ํจ์ ๋๋ค.
React์ ์ฒ ํ์ ์ ์ธํ(Declarative) ์ด๋ฉฐ, ์ปดํฌ๋ํธ๋ ๊ฐ๋ฅํ๋ฉด ์์ ํจ์(Pure Function) ์ฌ์ผ ํฉ๋๋ค. (๊ฐ์ ์ธํ์๋ ํญ์ ๊ฐ์ ์์ํ์ด ๋์์ผ ํจ).
ํ์ง๋ง p5.js๋ ๋ณธ์ง์ ์ผ๋ก ๋ช
๋ นํ(Imperative) ์ด๋ฉฐ, DOM์ ์ง์ ์กฐ์ํ๊ณ requestAnimationFrame์ด๋ผ๋ ๊ฑฐ๋ํ ๋ถ์์ฉ(Side Effects) ์ ๋ฐ์์ํต๋๋ค.
์ด๋ฌํ '๋ถ์๋ฌผ'์ App.jsx์ ์ง์ ์์ด ์ฐ๋ฉด, React์ ์ํ(State)์ p5์ ์ํ๊ฐ ๋ค์์ผ ๋๋ฒ๊น
ํ๊ธฐ ์ง์ฅ ๊ฐ์ ์ฝ๋๊ฐ ๋ฉ๋๋ค. P5Canvas.jsx๋ก ๋ถ๋ฆฌํจ์ผ๋ก์จ "์ด ์ปดํฌ๋ํธ ๋ด๋ถ์์๋ง p5์ ๋ถ์์ฉ์ด ๋ฐ์ํ๊ณ , ์ธ๋ง์ดํธ ์ ๊นจ๋ํ๊ฒ ์ฒญ์๋๋ค" ๋ ๊ฒ์ ๋ช
ํํ ์บก์ํ(Encapsulation)ํ ์ ์์ต๋๋ค.
App.jsx (Orchestrator / ์งํ์): ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์ฒด ๋ ์ด์์, ๋ผ์ฐํ
, UI ์ปดํฌ๋ํธ๋ค์ ๋ฐฐ์น, ์ ์ญ ๋ฐ์ดํฐ ํ๋ฆ์ ๋ด๋นํฉ๋๋ค.P5Canvas.jsx (Implementer / ์คํ์): ์ฃผ์ด์ง ์์ญ์ p5.js๋ฅผ ํ์ฉํด ํน์ ๋น์ฃผ์ผ์ ๋ ๋๋งํ๋ ์ญํ ๋ง ๋ด๋นํฉ๋๋ค.๋ง์ฝ App.jsx ์์ p5 ์ฝ๋๊ฐ ์ง์ ์๋ค๋ฉด, ๋์ค์ ์น์ฌ์ดํธ์ '๋ค๋น๊ฒ์ด์
๋ฐ'๋ 'ํธํฐ'๋ฅผ ์ถ๊ฐํ ๋ ๋ ์ด์์ ์ฝ๋ฉ๊ณผ ๊ทธ๋ํฝ์ค ์ฝ๋ฉ์ด ๋ค์์ฌ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์์ง๋๋ค. ์ปดํฌ๋ํธ๋ฅผ ๋ถ๋ฆฌํ๋ฉด App.jsx๋ ๊ทธ์ <P5Canvas />๋ผ๋ ํ๊ทธ๋ง ๋ณด๊ณ "์, ์ฌ๊ธฐ์ ๊ทธ๋ํฝ์ด ๋ค์ด๊ฐ๋๊ตฌ๋"๋ผ๊ณ ์ถ์ํํ ์ ์์ต๋๋ค.
์์ผ๋ก ์น์ฌ์ดํธ๊ฐ ์ปค์ ธ์ ๋ค์๊ณผ ๊ฐ์ ์ํฉ์ด ์ฌ ์ ์์ต๋๋ค.
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>
);
}
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);
};
}
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'
}}
/>
);
}
๊ธฐ๋ณธ ๋ณด์ผ๋ฌํ๋ ์ดํธ์ ๋ถํ์ํ ๋ง์ง์ ์ ๊ฑฐํ์ฌ ์บ๋ฒ์ค๊ฐ ํ๋ฉด์ ๊ฐ๋ ์ฑ์ฐ๋๋ก ํฉ๋๋ค.
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;
}
useState๋ก ์ํธ์ํฌ์ ํ๋ผ๋ฏธํฐ(์: ํํฐํด ์, ๋
ธ์ด์ฆ ์ค์ผ์ผ)๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ด๋ฅผ sketch.js์ ์ ๋ฌํด์ผ ํ๋ค๋ฉด useEffect์ ์์กด์ฑ ๋ฐฐ์ด์ ํด๋น ์ํ๋ฅผ ๋ฃ์ง ๋ง์ญ์์ค. ๋์ p5InstanceRef.current๋ฅผ ํตํด p5 ์ธ์คํด์ค์ ์ปค์คํ
ํ๋กํผํฐ์ ์ง์ ํ ๋นํ๊ฑฐ๋, React State๋ฅผ sketch.js ๋ด๋ถ์ ํด๋ก์ /๋ชจ๋ ๋ ๋ฒจ ๋ณ์์ ๋๊ธฐํํ๋ ํจํด์ ์ฌ์ฉํด์ผ ๋ถํ์ํ re-render์ p5 ์ธ์คํด์ค ์ฌ์์ฑ์ ๋ง์ ์ ์์ต๋๋ค.sketch.js ๋ด๋ถ์์ document.getElementById ๋ฑ์ ์ฌ์ฉํ๋ ๊ฒ์ ์ง์ํ์ญ์์ค. ๋์ p.canvas.parentElement๋ฅผ ํตํด React๊ฐ ์ฃผ์
ํ ์ปจํ
์ด๋ ๋
ธ๋์ ์ ๊ทผํ๊ฑฐ๋, React ์ปดํฌ๋ํธ์์ ref๋ฅผ ํตํด ๊ณ์ฐ๋ ํฌ๊ธฐ๋ฅผ sketch ํจ์์ ์ธ์๋ก ๋๊ฒจ์ฃผ๋ ๊ฒ์ด ์์์ฑ(Purity)์ ์ ์งํ๋ ๋ฐฉ๋ฒ์
๋๋ค.p5 ์ฝ์ด๋ 2D ๋ ๋๋ฌ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํฉ๋๋ค. WebGL ๊ธฐ๋ฐ์ 3D ๋๋ ๊ณ ์ฑ๋ฅ 2D ์
ฐ์ด๋๊ฐ ํ์ํ๋ค๋ฉด p.createCanvas(w, h, p.WEBGL)๋ก ์์ฑ์๋ฅผ ๋ณ๊ฒฝํ์ญ์์ค. ๋จ, WEBGL ๋ชจ๋์์๋ ์ขํ๊ณ๊ฐ ์ค์ origin(0,0)์ผ๋ก ๋ณ๊ฒฝ๋๋ฏ๋ก ๊ธฐํํ ์ฐ์ฐ ์ ์ด๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.