Skip to content

Instantly share code, notes, and snippets.

@Anthodpnt
Last active December 14, 2022 22:51
Show Gist options
  • Save Anthodpnt/adf95a37d3a7615eb8898511c1be9565 to your computer and use it in GitHub Desktop.
Save Anthodpnt/adf95a37d3a7615eb8898511c1be9565 to your computer and use it in GitHub Desktop.
Canvas - Video Masking
/**
* This gist is for Javascript beginners.
* @author: Anthony Du Pont <[email protected]>
* @site: https://www.twitter.com/JsGists
*
* You can do really cool effects with Canvas and I am going to show you how to use a video to mask
* and image with it. To be honest I am not really sure this gist is really for beginners but I really
* wanted to do it. So if you are not comfortable with the code below, take a minute, breathe and maybe
* start by reading the basics of Canvas. You will quickly start to notice this gist is not so difficult
* to understand.
*
* So what do we need to create this effect ? An image and a video to use as a mask. An important
* thing to understand when you use an asset as a mask is that every pixels that are 100% black will show
* the image and every pixels that are 100% white will hide the image. Let's get started!
*
* Example:
* I have an image and a want to apply an animated mask.
* I have decided to used a video of ink, this could create some really cool animations.
**/
// We declare all the variables we will need.
let loop, canvas, context, width, height, image, img, video, vid;
function VideoMask(options) {
// We start by setting up our variables
image = options.image;
video = options.video;
// Then we create our canvas and we get its context
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
// We set the width and height of our canvas
width = canvas.width = options.width;
height = canvas.height = options.height;
// Finally we append our canvas to the body
document.body.appendChild(canvas);
// We can now load the image
loadImage();
}
function loadImage() {
// You can find more explanations about loading
// images with Javascript in the episode #6 of
// "Gist for Javascript Beginners".
img = new Image();
img.onload = () => {
// When our image is loaded we can load
// our video. You could do this synchronously
// but I choose to do this asynchronously.
loadVideo();
};
img.src = image;
}
function loadVideo() {
// To load a video it is basically the same
// approach than the images. We start
// by creating a video element in Javascript.
vid = document.createElement('video');
// Then we set its source.
vid.src = video;
// We can finally add more options to our video. See the documentation of the
// video tag for more options.
vid.preload = 'auto';
vid.autoplay = true;
vid.muted = true;
// We wait for our video to be able to play. This is the same logic than the `onload`
// method on images except that `oncanplay` method will be fired even if our video is
// not completely loaded.
vid.oncanplay = () => {
// We can now render our mask.
render();
};
// To improve the performances and to avoid the loop to run for no reason we stop it
// when the video has ended.
vid.onended = () => {
clear();
};
}
function render() {
// Since we are in a loop that will run the exact same code every time, we first need
// to clear our canvas. This will empty our canvas to let us draw our mask and image again.
context.clearRect(0, 0, width, height);
// If you are using transparent PNGs you may want to change the background of your canvas.
// You could do so in CSS or you can create a rectangle and fill it with a chosen color.
// Note: I am using a non-transparent PNG here but you can change the image to show.
context.rect(0, 0, width, height);
context.fillStyle = '#ffffff';
context.fill();
// Finally we can draw our image and our video to our canvas. We are not drawing the full video
// but only the current frame at this point. The trick is to add some blending mode to our
// image and video to create this effect of the video masking the image.
// Check the documentation of the `globalCompositeOperation` for more blending modes.
context.globalCompositeOperation = 'source-over';
context.drawImage(img, 0, 0, width, height);
context.globalCompositeOperation = 'lighten';
context.drawImage(vid, 0, 0, width, height);
// We finish by looping over and over again with the `requestAnimationFrame` method.
// You can find more explanations about `requestAnimationFrame` in the episode #7 of
// "Gist for Javascript Beginners".
loop = window.requestAnimationFrame(render);
}
function clear() {
// Last but not least we cancel the loop when our video has ended. This avoid the loop to
// run for no reason.
window.cancelAnimationFrame(loop);
}
// We apply our Video Mask. This function takes an object of options.
// image: The image to show
// video: The video to use as a mask. Black = image visible; White = image hidden
// width: The width of our canvas
// height: The height of our canvas
VideoMask({
image: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/972352/painting.jpg',
video: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/972352/ink.mp4',
width: 800,
height: 600
});
@polyclick
Copy link

polyclick commented Sep 25, 2017

Here's a variation of the same concept. I needed to be able to see through the canvas element where it masked the image.
The version with blending modes shows a white background on the masked parts. This is great when your website has a solid white background.

With this version you can have anything behind the canvas element.

It manipulates the original pixel data of the image by setting the alpha channel of individual pixels.

Important: this method uses getImageData() so make sure cross domain policy is set right, or use relative links to your files.

// We declare all the variables we will need.
let loop, canvas, context, width, height, image, img, video, vid;

function VideoMask(options) {

  // We start by setting up our variables
  image = options.image;
  video = options.video;

  // Then we create our canvas and we get its context
  canvas = document.createElement('canvas');
  context = canvas.getContext('2d');

  // We set the width and height of our canvas
  width = canvas.width = options.width;
  height = canvas.height = options.height;

  // Finally we append our canvas to the body
  document.body.appendChild(canvas);

  // We can now load the image
  loadImage();

}

function loadImage() {
  // You can find more explanations about loading
  // images with Javascript in the episode #6 of
  // "Gist for Javascript Beginners".
  img = new Image();
  img.onload = () => {

    // we want the raw pixel data of the original image
    // this way, we can always refer back to the original pixel data
    // ok, so we first create an off screen canvas
    var canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height

    // we grab the context and draw the image onto it
    // then we read the drawn pixels back into memory
    var context = canvas.getContext('2d')
    context.drawImage(img, 0, 0)
    source = context.getImageData(0, 0, img.width, img.height)

    // When our image is loaded we can load
    // our video. You could do this synchronously
    // but I choose to do this asynchronously.
    loadVideo();
  };
  img.src = image;
}

function loadVideo() {

  // To load a video it is basically the same
  // approach than the images. We start
  // by creating a video element in Javascript.
  vid = document.createElement('video');

  // Then we set its source.
  vid.src = video;

  // We can finally add more options to our video. See the documentation of the
  // video tag for more options.
  vid.preload = 'auto';
  vid.autoplay = true;
  vid.muted = true;

  // We wait for our video to be able to play. This is the same logic than the `onload`
  // method on images except that `oncanplay` method will be fired even if our video is
  // not completely loaded.
  vid.oncanplay = () => {
    // We can now render our mask.
    render();
  };

  // To improve the performances and to avoid the loop to run for no reason we stop it
  // when the video has ended.
  vid.onended = () => {
    clear();
  };
}

function render() {

  // first we draw the current video frame to the context
  // so we can grab the raw pixel (black/white) info from the canvas
  context.drawImage(vid, 0, 0, width, height);

  // get image data of current video frame
  var frame = context.getImageData(0, 0, width, height);

  // now, we're going to build our final image
  // first we start of by copying the original image pixel data
  var dest = context.createImageData(source.width, source.height);
  dest.data.set(source.data);

  // then, we run through the pixels one by one and set the alpha channel of our final image.
  // we read 1 of the r, g, b channels, so we know the gray value at the current pixel's position.
  // then we set the alpha channel of our destination image.
  for (let i = 0; i < dest.data.length / 4; i++)
    dest.data[i * 4 + 3] = 255 - frame.data[i * 4 + 0];

  // then we replace everything that was drawn before by our newly manipulated pixel data
  context.putImageData(dest, 0, 0);

  // We finish by looping over and over again with the `requestAnimationFrame` method.
  // You can find more explanations about `requestAnimationFrame` in  the episode #7 of
  // "Gist for Javascript Beginners".
  loop = window.requestAnimationFrame(render);
}

function clear() {
  // Last but not least we cancel the loop when our video has ended. This avoid the loop to
  // run for no reason.
  window.cancelAnimationFrame(loop);
}

// We apply our Video Mask. This function takes an object of options.
// image: The image to show
// video: The video to use as a mask. Black = image visible; White = image hidden
// width: The width of our canvas
// height: The height of our canvas
VideoMask({
  image: 'still_2.jpg',
  video: 'Trasition_Mask_blur.mp4',
  width: 1920,
  height: 1080
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment