Festival Lineup
View DemoA festival lineup grid pulling real artist data from PostgreSQL via Redis, with images served from CloudFront, rendered into a canvas using the HTML-in-Canvas API.
This experiment pulls a random qualifying festival lineup from a PostgreSQL database. Qualifying festivals have at least 40 artists with verified image URLs. The lineup data is cached in Redis and served via a Next.js route handler that returns a standalone HTML page with the artist data injected server-side. Artist images are served from CloudFront.
The page uses the experimental HTML-in-Canvas API, available behind a flag in Chrome Canary (chrome://flags/#canvas-draw-element). Instead of choosing between HTML's layout engine and canvas's rendering power, this API lets you have both — real DOM elements drawn directly into a canvas context.
Each artist cell is a standard HTML element with CSS transitions, filters, and transforms. The layoutsubtree attribute on the canvas tells the browser to lay out its children normally but hold off on painting them until we explicitly call drawElementImage(). That method draws a child element into the canvas at whatever position, size, and transformation we specify, then returns a matrix we apply back to the element's CSS transform so hit testing and accessibility stay in sync.
The grid adapts from 1 to 4 columns based on viewport width. Cell height is derived from the image aspect ratio (3456:2000), and rows are calculated with Math.ceil so the last row extends below the fold rather than leaving dead space. The canvas buffer is sized to the viewport multiplied by devicePixelRatio for sharp rendering on retina displays.
All images start in grayscale via CSS filter. When selected, the filter transitions to full color and a Ken Burns animation kicks in — each cell gets a randomized pan direction, zoom range, and cycle duration so the motion feels organic rather than mechanical. The animation is driven by requestAnimationFrame updating the image's inline transform, with the canvas repainting each frame via requestPaint().
The flip effect simulates a vertical axis rotation without CSS 3D transforms, which canvas flattens when snapshotting elements. We animate a progress value from 0 to 1, map it to scaleX through cosine, and apply that via the canvas CTM before drawing. At the halfway point where scaleX crosses zero, we swap between front and back content using display toggling. The result looks like a 3D flip but stays compatible with drawElementImage.
Navigation is linear — left and right arrows move through cells in reading order. Past the last cell, a column of new images shifts in. Up and down arrows scroll by row. Space swaps the entire page. Everything wraps infinitely. Sound feedback is generated via the Web Audio API with pentatonic tones that ascend across the grid.
The search prompt, triggered by the X key, filters the artist pool live by name. The grid recalculates and redraws with only matching artists. Each page refresh loads a different random festival.
Images coming soon