Transparent Video Buffer

Recently we were looking for a transparent & performant video buffer. We tried a lot of approaches along the way to find the best fitting and just wanted to share them with you. There really isn’t a best practice. It really depends on the use case (Performance / Motion Blur / Arty Artifacts).

Gif + CSS Blendmode:

Use CSS blend-modes to render all black pixels transparently.

.gif {
    width: 100%;
    height: 100%;
    background-image:  url("../img/loading_example.gif");
    mix-blend-mode: lighten;
    background-size: contain;}

This was a long time favorite, because it has real motion blur. Unfortunately we couldn’t use it in productions as mobile devices (especially Android) had sometimes rendering lags, which would show the black background for a second. In general it works perfectly, but we were changing backgrounds a lot (Videos / Pictures / Divs) which caused this lag sometimes. So we went on the search for a different solution.

SVG Animation with GSAP

//svg images
	var svgrb = document.getElementById('svgrb');
	var svgrc = document.getElementById('svgrc');
	var svgnl = document.getElementById('svgnl');

var animationPause = 0.3;

	var tl = new TimelineMax({repeat:-1});
	tl.to("#svgrc", 1, {rotation:180,  ease: Back.easeInOut.config(4)}, "partOne" );
	tl.to("#svgrc", 0.2, {opacity:0}, "partOne+=0.5");
	tl.to("#svgrb", 0, {rotation:-180}, "partOne+=0.5");
	tl.to("#svgrb", 0.5, {rotation:0,  ease: Power4.easeOut}, "partOne+=0.5");
	tl.to("#svgrb", 0.2, {opacity:1}, "partOne+=0.5");

	//pause
	tl.to("#svgrb", animationPause, {opacity:1});

	tl.to("#svgrb", 1, {rotation:180,  ease: Back.easeInOut.config(4)}, "partTwo" );
	tl.to("#svgrb", 0.2, {opacity:0}, "partTwo+=0.5");
	tl.to("#svgnl", 0, {rotation:-180}, "partTwo+=0.5");
	tl.to("#svgnl", 0.5, {rotation:0,  ease: Power4.easeOut}, "partTwo+=0.5");
	tl.to("#svgnl", 0.2, {opacity:1}, "partTwo+=0.5");

	//pause
	tl.to("#svgnl", animationPause, {opacity:1});

	tl.to("#svgnl", 1, {rotation:180,  ease: Back.easeInOut.config(4)}, "partThree" );
	tl.to("#svgnl", 0.2, {opacity:0}, "partThree+=0.5");
	tl.to("#svgrc", 0, {rotation:-180}, "partThree+=0.5");
	tl.to("#svgrc", 0.5, {rotation:0,  ease: Power4.easeOut}, "partThree+=0.5");
	tl.to("#svgrc", 0.2, {opacity:1}, "partThree+=0.5");

	//pause
	tl.to("#svgrc", animationPause, {opacity:1});

Canvas + Screen:

globalCompositeOperation = ‘screen’

var vid2 = document.getElementById('exampleVideo2');
vid2.play();

var wrapper2 = document.getElementById('wrapper2');
var canvas2 = document.getElementById('exampleCanvas2');
var ctx2 = canvas2.getContext('2d');

var ratio = window.devicePixelRatio || 1;
var vidWidth;
var vidHeight;

vid2.onloadedmetadata = function() {
	vidWidth = vid2.videoWidth;
	vidHeight = vid2.videoHeight;

	canvas2.width = vid2.offsetWidth;
	canvas2.height = vid2.offsetHeight;

	drawingLoop();
};

function drawingLoop(){
	requestId = window.requestAnimationFrame(drawingLoop)
  	ctx2.clearRect(0, 0, canvas2.width, canvas2.height);

	ctx2.fillStyle= "blue";
	ctx2.fillRect(0,0, canvas2.width, canvas2.height);

	ctx2.globalCompositeOperation = "screen";

	ctx2.drawImage(vid2, 0, 0, vidWidth, vidHeight, // source rectangle
	                0, 0, canvas2.width, canvas2.height); // destination rectangle);

	ctx2.globalCompositeOperation = "source-over";}

Canvas + Removing all black pixels

var vid = document.getElementById('exampleVideo');
vid.play();

var wrapper = document.getElementById('wrapper');
var canvas = document.getElementById('exampleCanvas');
var ctx = canvas.getContext('2d');

var ratio = window.devicePixelRatio || 1;
var vidWidth;
var vidHeight;


vid.onloadedmetadata = function() {
	vidWidth = vid.videoWidth;
	vidHeight = vid.videoHeight;

	canvas.width = vid.offsetWidth;
	canvas.height = vid.offsetHeight;

	drawingLoop();
};

function drawingLoop(){
	requestId = window.requestAnimationFrame(drawingLoop)
  	var buffer = document.createElement('canvas');
  	var bufferctx = buffer.getContext('2d');
  	buffer.width = vid.offsetWidth;
	buffer.height = vid.offsetHeight;

  	bufferctx.drawImage(vid, 0, 0);
  	var imageData = bufferctx.getImageData(0,0,buffer.width,  buffer.height);
  	var data = imageData.data;
    var removeBlack = function() {
        for (var i = 0; i < data.length; i += 4) {
            if(data[i]+ data[i + 1] + data[i + 2] < 10){ 
                data[i + 3] = 0; // alpha
            }
        }
        ctx.putImageData(imageData, 0, 0);
    };
		removeBlack();}

Video + Alphamask

To achieve this, we used seeThru by m90. It basically draws the video on a first canvas and than takes an alpha mask to draw a transparent video on a second canvas.

var transparentVideo = seeThru.create('#exampleVideo');

If you have another approach / idea, don't hesitate to tell us, so we can updated the list!