AI Skill Library

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