Matrix Digital Rain Effect in JavaScript (Canvas)
Video: Matrix Digital Rain Effect in JavaScript (Canvas) by CelesteAI
The falling green code from The Matrix is one of the most recognizable effects on the web — and one of the smallest to build. No library, no shaders. About forty lines of plain Canvas 2D.
This build uses TypeScript and Vite, scaffolded from the command line and run in the browser.
Set up the project
npm create vite@latest matrix-rain -- --template vanilla-ts
cd matrix-rain
npm install
No graphics library to install — Canvas is built into the browser. Trim index.html to a full-screen canvas on a near-black background:
<canvas id="bg"></canvas>
<script type="module" src="/src/main.ts"></script>
The glyphs and the columns
Grab the canvas and its 2D context. The iconic glyphs are half-width katakana — build them from character codes so you never have to paste non-ASCII into your source. Add the digits, and a helper to pick a random glyph.
const canvas = document.getElementById("bg") as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
const FONT_SIZE = 18;
const HEAD = "#c9ffe3"; // bright leading glyph
const TAIL = "#13b074"; // green trail
const charset: string[] = [];
for (let c = 0x30a0; c <= 0x30ff; c++) charset.push(String.fromCharCode(c)); // katakana
for (let c = 48; c <= 57; c++) charset.push(String.fromCharCode(c)); // digits
const glyph = () => charset[(Math.random() * charset.length) | 0];
The screen is split into columns, one falling stream each. The only state per column is a single number: which row its head is currently on. Start each at a random negative row so they stagger in.
let columns = 0;
let drops: number[] = [];
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
columns = Math.floor(canvas.width / FONT_SIZE);
drops = Array.from({ length: columns }, () => (Math.random() * -50) | 0);
ctx.fillStyle = "#020608";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
The one trick: a translucent fill
Here’s the whole effect. Each frame, instead of clearing the canvas, paint a nearly transparent black rectangle over the entire thing. That dims whatever was already drawn by a small amount. Do it every frame and each glyph leaves a tail that fades out behind it as the canvas slowly darkens beneath it.
function frame() {
ctx.fillStyle = "rgba(2, 6, 8, 0.08)"; // ← the fading tail
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = FONT_SIZE + "px monospace";
for (let i = 0; i < columns; i++) {
const x = i * FONT_SIZE;
const y = drops[i] * FONT_SIZE;
ctx.fillStyle = TAIL;
ctx.fillText(glyph(), x, y);
ctx.fillStyle = HEAD;
ctx.fillText(glyph(), x, y + FONT_SIZE); // brighter leading edge
if (y > canvas.height && Math.random() > 0.975) drops[i] = 0;
drops[i]++;
}
requestAnimationFrame(frame);
}
window.addEventListener("resize", resize);
resize();
frame();
The random drops[i] = 0 reset is what keeps the columns out of sync — without it they’d all fall in lockstep. A higher fade alpha (say 0.15) gives short tails; a lower one (0.04) gives long, ghostly streaks.
Run it
npm run dev
Open the local URL Vite prints and the code rains. Lower the z-index and put it behind your content for a cyberpunk background.
Recap
- The fading tail is one translucent fill drawn over the canvas each frame — not a real clear. Tune its alpha to set the tail length.
- Each column tracks one number — the row its head is on — and steps down every frame.
- Random resets keep the columns out of sync, which is what makes it look organic instead of mechanical.
Want the deeper dive?
The full TypeScript project is on GitHub — clone it, npm install, npm run dev, and tune the glyphs, colors, and fade.