Masking Out Video Tags with HTML5 Canvas
Nate Hunzaker, Former Development Director
Article Category:
Posted on
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.
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:
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!