CRT Screen
Put a scene behind curved glass — barrel distortion, phosphor mask, scanlines and a falling-away rim
This filter re-reads the real pixels of the scene it wraps through the experimental html-in-canvas browser API. The preview above shows the true effect in Chrome 149+ with chrome://flags/#canvas-draw-element enabled (nested canvases need 152.0.7944.0+). Every other browser — Safari, Firefox, Chrome without the flag — falls back to a CSS approximation, so the page and your video still play.
Rendering is supported through the CLI, Studio, Lambda and SSR with --gl=angle, or --gl=swangle on a machine without a GPU. Chrome may change or discontinue the API; the fallback is what keeps your composition renderable if that happens.
Installation
$ pnpm dlx shadcn@latest add @remocn/crt-screenUsage
CrtScreen is a wrapper. Put a scene inside it and every frame is re-read
through the geometry of a tube: the picture bulges outward so straight lines bow,
an RGB phosphor mask breaks the colour into stripes, scanlines follow the same
curve rather than lying flat across the frame, bright areas flood the mask around
them, and the corners run off the glass into darkness.
It has no duration of its own — it lasts exactly as long as the scene it wraps.
import { CrtScreen } from "@/components/remocn/crt-screen";
export const MyScene = () => (
<CrtScreen>
<Scene />
</CrtScreen>
);Keeping content on the glass
The warp pushes the corners of the frame outside the picture, which is what
produces the dark rim — and what will clip anything parked there. Give the scene
inside a generous margin, or drop curvature toward 0 for a flat-panel look that
keeps the mask and scanlines.
<CrtScreen curvature={0.35}>
<Scene />
</CrtScreen>Props
| Prop | Type | Default | Description |
|---|---|---|---|
curvature | number | 1 | How far the tube bulges. Bends the picture more across the vertical than the horizontal, the way a real tube is shaped. At 0 the frame stays flat and nothing is clipped at the corners. |
scanlines | number | 1 | Strength of the horizontal line structure. The lines follow the curvature rather than lying flat, so they bow with the picture. |
maskScale | number | 2 | Width in device pixels of one phosphor stripe, so a triad is three times this. Low values read as a fine aperture grille, high values as a coarse arcade tube. Measured in output pixels, so the mask gets finer relative to the frame as you render larger. |
vignette | number | 1 | How hard the picture falls off toward the rim, where the glass curves away from the viewer. |
children | React.ReactNode | — | The scene the filter wraps and re-reads every frame. |
Notes
Outside the tube the filter writes opaque black rather than nothing, so the rim is real darkness and not a hole showing the untouched scene through it. Inside the tube the scene's own transparency is preserved, so put a background within the wrapper rather than behind it.
The only thing that moves is a 1% brightness breath on a 60-frame cycle, which closes seamlessly on any loop that is a multiple of it. Everything else is fixed geometry, so two renders of the same frame produce the same pixels and the look holds for as long as the scene runs.