CRT Screen

Put a scene behind curved glass — barrel distortion, phosphor mask, scanlines and a falling-away rim

Experimental — html-in-canvas

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-screen

Usage

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

PropTypeDefaultDescription
curvature
number1How 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
number1Strength of the horizontal line structure. The lines follow the curvature rather than lying flat, so they bow with the picture.
maskScale
number2Width 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
number1How hard the picture falls off toward the rim, where the glass curves away from the viewer.
children
React.ReactNodeThe 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.