mcpField
G
gsap-canvas

gsap-canvas

github.com/iotronDesign & UI938 installsUpdated 31w ago
Repository

About gsap-canvas

gsap-canvas is a skill for frontend developers building GSAP animations rendered on canvas. It explains animating plain JavaScript objects and drawing their state with the Canvas 2D API, with recipes for particles, controls, resizing, and performance.

Key features

  1. 01GSAP object animation and canvas rendering
  2. 02Particle system recipe
  3. 03Playback and pause controls
  4. 04Canvas resize handling
  5. 05Rendering performance tips

Use cases

  1. 01Creating canvas particle animations
  2. 02Rendering GSAP animations on canvas
  3. 03Adding playback controls to canvas scenes
  4. 04Optimizing frame-by-frame drawing

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/iotron/gsap-cookbook --skill gsap-canvas

After installing, type/gsap-canvasin Claude Code to confirm it loaded.

Bundled filesreferences/canvas-patterns.md

Details

From the project README

GSAP Canvas — Rendering Patterns

Flow: gsap-setup → gsap-canvas → gsap-optimise → gsap-test

Companion: For GSAP core API reference, invoke gsap-core. For timeline sequencing, invoke gsap-timeline. This skill covers canvas rendering recipes only. Requires: greensock/gsap-skills


1. The Pattern

Canvas animation with GSAP is fundamentally different from DOM animation. GSAP never touches a DOM element directly. Instead:

  1. Create plain JS objects with numeric properties (x, y, scale, rotation, alpha).
  2. Animate those objects with a GSAP timeline or tween — GSAP interpolates the numbers.
  3. Attach an onUpdate callback to the timeline that reads the current object values and draws them to canvas using the Canvas 2D API.
Plain objects  →  GSAP tweens numbers  →  onUpdate draws to canvas
     ↑                                          ↓
  { x, y, scale }                      ctx.drawImage(img, x, y, w*scale, h*scale)

This means GSAP handles easing, stagger, repeat, yoyo, and timeline sequencing — while you own the rendering pipeline entirely.

// Minimal example: animate a circle across canvas
const dot = { x: 0, y: 250, radius: 20 }

gsap.to(dot, {
  x: 800, duration: 2, ease: 'power2.inOut',
  onUpdate() {
    ctx.clearRect(0, 0, cw, ch)
    ctx.beginPath()
    ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2)
    ctx.fill()
  },
})

2. Particle System Recipe

Animate an array of particles with sprite images orbiting inward. GSAP handles all motion; onUpdate redraws every frame.

Setup particle array
const particles = Array.from({ length: 99 }, (_, i) => ({
  x: 0, y: 0, scale: 0, rotate: 0,
  img: Object.assign(new Image(), {
    src: `https://assets.codepen.io/16327/flair-${2 + (i % 21)}.png`,
  }),
}))
Build the timeline
const radius = Math.max(cw, ch)

const tl = gsap.timeline({ onUpdate: draw })
  .fromTo(particles, {
    x: (i) => {
      const angle = (i / particles.length) * Math.PI * 2 - Math.PI / 2
      return Math.cos(angle * 10) * radius
    },
    y: (i) => {
      const angle = (i / particles.length) * Math.PI * 2 - Math.PI / 2
      return Math.sin(angle * 10) * radius
    },
    scale: 1.1,
    rotate: 0,
  }, {
    duration: 5, ease: 'sine',
    x: 0, y: 0, scale: 0, rotate: -3,
    stagger: { each: -0.05, repeat: -1 },
  }, 0)
  .seek(99) // jump ahead so repeating stagger is mid-flow
The draw function
function draw() {
  particles.sort((a, b) => a.scale - b.scale) // z-sort by scale
  ctx.clearRect(0, 0, cw, ch)
  particles.forEach((p) => {
    ctx.translate(cw / 2, ch / 2)
    ctx.rotate(p.rotate)
    ctx.drawImage(p.img, p.x, p.y, p.img.width * p.scale, p.img.height * p.scale)
    ctx.resetTransform()
  })
}

3. Controls & Resize

Play/pause toggle via timeScale
canvas.addEventListener('pointerup', () => {
  gsap.to(tl, {
    timeScale: tl.isActive() ? 0 : 1, // smooth ease to pause/play
  })
})
Resize handler with invalidate
window.addEventListener('resize', () => {
  cw = canvas.width = innerWidth
  ch = canvas.height = innerHeight
  radius = Math.max(cw, ch)
  tl.invalidate() // recalculates functional from-values on next render
})

invalidate() forces GSAP to re-evaluate function-based values (the trig calculations) using updated dimensions.


4. Performance Tips

TipWhy
Use gsap.ticker.add(fn) for render loopsSyncs with GSAP's internal rAF — one frame budget, no double-paints
Minimize state changesBatch translate/rotate calls; call resetTransform() once per particle
Sort sparinglyparticles.sort() every frame is O(n log n) — skip if z-order is fixed
Use will-change: transform on the <canvas>Promotes to GPU layer, reduces compositing cost
Prefer ctx.resetTransform() over save/restoreFaster — avoids stack push/pop overhead
Pre-render to offscreen canvasFor static sprites, draw once to OffscreenCanvas, then drawImage from it

References

  • references/canvas-patterns.md — Full implementations: particle orbit system, canvas morphs (MorphSVG rendered to canvas)

Comments

Markdown supported