Live video overlays from plain HTML
A scoreboard, a lower third, a stream chat: HTML and CSS composited over live video in the browser, in Chrome, Firefox and Safari. No OBS, no server.
The stream plugin keeps the overlay as a canvas that repaints only the boxes that changed; streamTexture uploads those rectangles to WebGL, where a shader composites them over the video and gives the look: clean, shine, CRT, hologram, glitch, or liquid glass refracting the footage under each panel. The output is a canvas. No browser flag.
Try it
Sports TV runs a broadcast package over a simulated match; press Goal or wait for one. Streamer uses your camera; Share screen puts your game behind the overlay, you in a circle. Change the look, drag the pieces, record a clip, or press Edit for the HTML and CSS.
How it works
The HTML is a real DOM element
Your markup goes into a 960 by 540 stage on this page, with your CSS nested under it.
stream()turns it into a canvas that repaints only what changedThe first frame is a full SnapDOM capture. After that, a change is traced to the nearest box that can repaint on its own, and only that subtree is captured. A patched frame matches a full capture pixel for pixel.
streamTexture()uploads the dirty rectanglesOn WebGL2 only the changed rectangles reach the GPU, usually a few percent of the texture per frame.
The shader composites the video and the overlay
Most looks read only the overlay texture: a light sweep, scanlines and glow, a hologram tint, glitch slices on a goal. Liquid glass also reads the layout: each frame, every
.glasspanel's box andborder-radiusbecome a rounded-rectangle distance field, joined by a smooth minimum with the drop under the pointer, so nearby shapes melt together. A spring makes each shape trail its box, so moving pieces stretch and wobble. Inside, the footage is sampled through a lens: pulled inward at the rim, frosted, colour-split, darkened over bright backgrounds so white text stays legible. The streamed HTML goes on top.The program output is a canvas
It records to MP4 or WebM with
recordCanvas(), or goes out as a WebRTC track withcaptureStream(), overlay included.
The code
The SnapDOM part is short. The rest is a full-screen fragment shader.
import { stream, streamTexture } from '@zumer/snapdom-plugins/stream'
const gl = canvas.getContext('webgl2')
const overlay = streamTexture(gl)
stream(stage, {
embedFonts: false,
onFrame: (frame) => overlay.update(frame), // uploads frame.rects only
})
// every animation frame: upload the video, pass each .glass box to the shader, draw
for (const el of stage.querySelectorAll('.glass')) {
const r = el.getBoundingClientRect() // relative to the stage
rects.push(r.left - base.left, r.top - base.top, r.width, r.height)
}
Limits
- The glass shape is the box and its
border-radius, one radius per box; other shapes need their own distance function in the shader. - The texture is pixels: links and buttons in it are not clickable.
- A box over a translucent background (
rgba()with alpha below 1) cannot repaint alone and forces a full repaint. Keep glass boxes free of background colour. - Footage from another origin needs CORS.