Animating canvas to look like tv noise

Update 1/2017: I rewrote the entire answer as it started to become rather messy, and to address some of the issues pointed out in the comments. The original answer can be found here. The new answer has in essence the same code but improved, and with a couple of new techniques, one utilizes a new feature available since this answer was first posted.


For a “true” random look we would need to use pixel-level rendering. We can optimize this using 32-bit unsigned buffers instead of 8-bit, and we can also turn off the alpha-channel in more recent browsers which speeds up the entire process (for older browsers we can simply set a black opaque background for the canvas element).

We create a reusable ImageData object once outside the main loop so the main cost is only putImageData() and not both inside the loop.

var ctx = c.getContext("2d", {alpha: false});       // context without alpha channel.
var idata = ctx.createImageData(c.width, c.height); // create image data
var buffer32 = new Uint32Array(idata.data.buffer);  // get 32-bit view

(function loop() {
  noise(ctx);
  requestAnimationFrame(loop)
})()

function noise(ctx) {
  var len = buffer32.length - 1;
  while(len--) buffer32[len] = Math.random() < 0.5 ? 0 : -1>>0;
  ctx.putImageData(idata, 0, 0);
}
/* for browsers wo/2d alpha disable support */
#c {background:#000}
<canvas id=c width=640 height=320></canvas>

A very efficient way, at the cost of some memory but reduced cost on the CPU, is to pre-render a larger off-screen canvas with the noise once, then place that canvas into the main one using random integer offsets.

It require a few extra preparation steps but the loop can run entirely on the GPU.

var w = c.width;
var h = c.height;
var ocanvas = document.createElement("canvas");     // create off-screen canvas
ocanvas.width = w<<1;                               // set offscreen canvas x2 size
ocanvas.height = h<<1;

var octx = ocanvas.getContext("2d", {alpha: false});
var idata = octx.createImageData(ocanvas.width, ocanvas.height);
var buffer32 = new Uint32Array(idata.data.buffer);  // get 32-bit view

// render noise once, to the offscreen-canvas
noise(octx);

// main loop draw the offscreen canvas to random offsets
var ctx = c.getContext("2d", {alpha: false});
(function loop() {
  var x = (w * Math.random())|0;                    // force integer values for position
  var y = (h * Math.random())|0;
  
  ctx.drawImage(ocanvas, -x, -y);                   // draw static noise (pun intended)
  requestAnimationFrame(loop)
})()

function noise(ctx) {
  var len = buffer32.length - 1;
  while(len--) buffer32[len] = Math.random() < 0.5 ? 0 : -1>>0;
  ctx.putImageData(idata, 0, 0);
}
/* for browsers wo/2d alpha disable support */
#c {background:#000}
<canvas id=c width=640 height=320></canvas>

Do note though that with the latter technique you may risk getting “freezes” where the new random offset is similar to the previous one. To work around this problem, set criteria for the random position to disallow too close positions in a row.

Leave a Comment