Canvas 2D Creative Coding
Drawing, particles, generative art, interactive canvas, OffscreenCanvas.
canvascreative-codinganimationfrontend
# Canvas 2D Creative Coding
## Setup
```ts
const canvas = document.querySelector('canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d')!
// Retina / HiDPI
const dpr = devicePixelRatio
canvas.width = innerWidth * dpr
canvas.height = innerHeight * dpr
canvas.style.width = innerWidth + 'px'
canvas.style.height = innerHeight + 'px'
ctx.scale(dpr, dpr)
window.addEventListener('resize', () => {
canvas.width = innerWidth * dpr
canvas.height = innerHeight * dpr
canvas.style.width = innerWidth + 'px'
canvas.style.height = innerHeight + 'px'
ctx.scale(dpr, dpr)
})
```
## Drawing primitives
```ts
// Clear
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Rectangle
ctx.fillStyle = '#4ecdc4'
ctx.fillRect(10, 10, 100, 50)
ctx.strokeStyle = '#000'
ctx.lineWidth = 2
ctx.strokeRect(10, 10, 100, 50)
// Path
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(150, 50)
ctx.quadraticCurveTo(200, 100, 150, 150)
ctx.bezierCurveTo(100, 200, 50, 200, 50, 150)
ctx.closePath()
ctx.fill()
// Arc / circle
ctx.beginPath()
ctx.arc(100, 100, 40, 0, Math.PI * 2)
ctx.fillStyle = '#ff6b6b'
ctx.fill()
// Text
ctx.font = 'bold 32px Inter'
ctx.fillStyle = 'white'
ctx.textAlign = 'center'
ctx.fillText('Hello', canvas.width / 2 / dpr, 80)
```
## Particle system
```ts
interface Particle {
x: number; y: number
vx: number; vy: number
life: number; maxLife: number
size: number; color: string
}
const particles: Particle[] = []
function spawn(x: number, y: number) {
for (let i = 0; i < 20; i++) {
const angle = Math.random() * Math.PI * 2
const speed = Math.random() * 4 + 1
particles.push({
x, y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1, maxLife: 1,
size: Math.random() * 6 + 2,
color: `hsl(${Math.random() * 60 + 160}, 80%, 60%)`,
})
}
}
function tick() {
ctx.clearRect(0, 0, innerWidth, innerHeight)
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i]
p.x += p.vx; p.y += p.vy
p.vy += 0.1 // gravity
p.life -= 0.02
if (p.life <= 0) { particles.splice(i, 1); continue }
ctx.globalAlpha = p.life
ctx.fillStyle = p.color
ctx.beginPath()
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2)
ctx.fill()
}
ctx.globalAlpha = 1
requestAnimationFrame(tick)
}
canvas.addEventListener('click', e => spawn(e.offsetX, e.offsetY))
tick()
```
## Noise-based generative art
```ts
// Perlin/Simplex noise via 'simplex-noise' package
import { createNoise2D } from 'simplex-noise'
const noise2D = createNoise2D()
function drawFlow() {
const t = Date.now() * 0.0005
for (let x = 0; x < innerWidth; x += 10) {
for (let y = 0; y < innerHeight; y += 10) {
const n = noise2D(x * 0.005 + t, y * 0.005)
const angle = n * Math.PI * 4
ctx.strokeStyle = `hsla(${n * 360}, 70%, 60%, 0.3)`
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + Math.cos(angle) * 8, y + Math.sin(angle) * 8)
ctx.stroke()
}
}
}
```
## OffscreenCanvas (Web Worker)
```ts
// main.ts
const offscreen = canvas.transferControlToOffscreen()
const worker = new Worker('canvas-worker.ts', { type: 'module' })
worker.postMessage({ canvas: offscreen }, [offscreen])
// canvas-worker.ts
self.onmessage = ({ data: { canvas } }) => {
const ctx = canvas.getContext('2d')
function draw() { /* heavy computation */ requestAnimationFrame(draw) }
draw()
}
```API: /api/skills/canvas-creative-coding