Matrix Webcam Effect

by Scott Kaye

HTML

<canvas></canvas>
<video autoplay></video>

CSS

canvas {
  width: calc(532px * 1.25);
  height: calc(400px * 1.25);
  box-shadow: 0 0 10px -2px #0f0;
  border: 1px solid #090;
}

html, body {
  height: 100%;
}

body {
  background: #000;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

video {
  display: none;
}

JavaScript

const CRUSH = 0;  // float from 0 to 1
const SIZE = 12;  // Size of falling glyphs

const canvas = document.querySelector("canvas");
const video = document.querySelector("video");

const size = canvas.getBoundingClientRect();
canvas.width = size.width;
canvas.height = size.height;

const videoCanvas = canvas.cloneNode();
videoCanvas.style.display = "none";
canvas.parentNode.insertBefore(videoCanvas, canvas);

const context = canvas.getContext("2d");
const videoContext = videoCanvas.getContext("2d");
videoContext.translate(canvas.width, 0);
videoContext.scale(-1, 1);

const points = [];

let rows = canvas.height / SIZE;
let cols = canvas.width / SIZE;

for (let x = 0; x < cols; ++x) {
  let speed = rand(0.8, 1.2);
  for (let y = 0; y < rows + 2; ++y) {
    let char = randomChar();
    points.push({
      x: x * SIZE,
      y: y * SIZE,
      speed,
      char
    });
  }
}

function randomChar() {
  return String.fromCharCode(rand(12000, 12500));
}

context.font = SIZE + "px sans-serif";

function rand(min, max) {
  return Math.random() * (max - min + 1) + min;
}

function draw() {
	context.fillStyle = "#000";
  context.fillRect(0, 0, canvas.width, canvas.height);
  
  videoContext.drawImage(video, 0, 0, canvas.width, canvas.height);

  let i, l;
  for (i = 0, l = points.length; i < l; ++i) {
  	var color = videoContext.getImageData(points[i].x, points[i].y, 1, 1);
    var luma = (color.data[0] * 0.2126 + color.data[1] * 0.7152 + color.data[2] * 0.0722) / 255;
    luma = posterize(luma, CRUSH);

		context.fillStyle = "rgba(0, 255, 0, " + luma + ")";
    context.fillText(points[i].char, points[i].x, points[i].y);
    points[i].y += points[i].speed;

    if (points[i].y > canvas.height + SIZE) {
      points[i].y = SIZE * -1;
    }

    if (Math.random() < 0.005) {
      points[i].char = randomChar();
    }
  }
  
  requestAnimationFrame(draw);
}

draw();

function posterize(src, level) {
  return level ? Math.round(src / level) * level : src;
}

if...