Interactive Canvas graphics
Builds responsive custom Canvas visuals with correct pixel density, stable animation timing and usable alternatives to bitmap-only interaction.
canvas-interactive-graphics
Team members using this skill
When is it useful?
Use it when an interactive diagram or animation needs precise drawing without excluding keyboard or assistive-technology users.
What does it help you do?
- Matches drawing resolution to display pixel density.
- Uses elapsed time and pauses rendering when offscreen.
- Maps pointer hits and supplies accessible HTML controls.
Try asking this
Build an interactive node canvas with keyboard alternatives and reduced motion. Select a node by keyboard and pointer, then move the canvas offscreen. Both inputs should identify the same node, and hidden rendering should pause.
Detailed documentation
Read the skill’s original instructions, examples and checks in their authored language.
Show less
Canvas & Interactive Graphics
Reach for HTML5 <canvas> when a scene animates hundreds of simultaneous elements (particle fields, live node-link topologies, waveform visualizers, spatial grids) where individual DOM/SVG nodes would bottleneck layout and style recalculation.
Crisp High-DPI (devicePixelRatio) Setup & Resize
A raw <canvas width="800" height="600"> looks blurry on Retina/high-DPI screens. Always decouple logical CSS coordinates from physical backing-store pixels:
function syncCanvasSize(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, cssWidth: number, cssHeight: number) {
const dpr = Math.min(window.devicePixelRatio || 1, 2); // Cap at 2x to protect fill-rate on 3x/4x mobile screens
canvas.width = Math.round(cssWidth * dpr);
canvas.height = Math.round(cssHeight * dpr);
canvas.style.width = `${cssWidth}px`;
canvas.style.height = `${cssHeight}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // Draw in logical CSS pixels everywhere else
}
- Observe the container with
ResizeObserverand re-runsyncCanvasSize+ an immediate render pass when dimensions change (because changingcanvas.widthclears the canvas buffer).
Frame-Rate Independent Loop & Offscreen Pausing
- Delta-time (
dt) integration:- Never advance positions by a fixed pixel step per
requestAnimationFrametick (x += 2), which runs twice as fast on 120Hz displays as on 60Hz displays. - Compute elapsed seconds
const dt = Math.min((now - lastTime) / 1000, 0.05);and use frame-rate independent exponential damping for smooth pointer/camera follow: $$\text{value} = \text{lerp}(\text{value}, \text{target}, 1 - e^{-\lambda \cdot dt})$$
- Never advance positions by a fixed pixel step per
- Zero-allocation render loop:
- Never allocate
new Path2D(), objects, arrays, or gradient instances inside the per-framerender()loop—GC pauses cause visible frame drops. Pre-allocate typed arrays or object pools during setup. - Cache static background grids or complex vector stamps onto an offscreen canvas (
OffscreenCanvasor hidden<canvas>) and composite viactx.drawImage().
- Never allocate
- Sleep when idle or offscreen:
- Stop the
requestAnimationFrameloop when all springs/particles have settled within $\epsilon$ of rest, and restart it on pointer/scroll/state events. - Pause rendering whenever
IntersectionObserverreports the canvas is outside the viewport ordocument.hiddenis true.
- Stop the
Pointer Interaction & Accessibility Overlay
- Coordinate mapping: Convert client pointer events (
PointerEvent) to logical canvas coordinates viaconst rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top;. - Accessible DOM twin: Because
<canvas>is a single opaque bitmap to assistive tech, never draw interactive buttons or essential text solely inside Canvas. Overlay semantic, keyboard-focusable HTML buttons/controls positioned over the canvas (or provide a synchronized accessible list/table inside the container) and honorprefers-reduced-motion: reduceby rendering a calm static or step-driven frame.
Included files 1
SKILL.md
