Masking Out Video Tags with HTML5 Canvas

For Halloween we built Haunted Hills, a neat video chat app that places users' web cameras into a spooky, ParallaxJS powered graveyard. One of our more curious challenges was to cut out video feeds so that users' faces fit perfectly into place.

Haunted Hills

This feature went through several iterations, including experiments with CSS masks, overflow: hidden, and overlayed graphics. However the best solution I could come up with was to capitalize on the <canvas> element's globalCompositeOperation option, which tells context how to draw on top of existing content.

Video tags are drawable canvas elements

2d canvas context has the ability to draw <img> elements using context.drawImage(), however this also supports the use of <video> and <canvas> tags. With a basic requestAnimationFrame loop, producing a masked video only takes a few steps. Once the images have been selected, all that is required to produce the intended effect is to draw the video feed, set the context's globalCompositeOperation to destination-in, and the mask will eat away the unwanted pixels when drawn.

There is an inverse to destination-in: destination-out. It's possible to use this setting, however the mask would need to be inverted as well. With a mask such as:

A mask

The desired effect can be achieved with the following code:

 // Get our mask image
var canvas = document.querySelector(".canvas");
var mask = document.querySelector(".mask");
var video = document.querySelector(".video");

function drawMaskedVideo() {
 ctx.save();

 // Draw the video feed
 ctx.drawImage(video, 0, 0);

 // Set the composite operation, which is responsible for masking
 // see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
 ctx.globalCompositeOperation = 'destination-in';

 // Apply the mask
 ctx.drawImage(mask, 0, 0);

 ctx.restore();
}

requestAnimationFrame(function loop() {
 requestAnimationFrame(loop.bind(this));
 drawMaskedVideo();
});

As for the original <video> tag, there are a couple of ways to hide it. However on haunted-hills.com we actually don't even attach the <video> tag to the DOM. I've demonstrated this in the following prototype. Enjoy!

Nate is a senior developer in our Durham, NC office, where he focuses on client-side application development. Most days, you can find him neck-deep in JavaScript working with clients such as The Nature Conservancy and the Wildlife Conservation Society.

More posts by Nate