Create beautiful, seekable, mobile-friendly-auto-playable HTML videos (faked with sprite animations and canvas).
Demo: flicker.jonathan-brooks.co.uk
npm install flicker
I basically didn't like HTML video limitations on mobile (specifically no autoplay for hero backgrounds), so I figured a good solution would be to use sprite animations - but packing the sprites and converting the frame coords to JSON with an external tool took forever, and I wanted to play/pause/reverse/seek my sprite animations easier. So I made a tool which does both. Essentially, my pipeline was as follows:
(create video) --> video (photoshop export) --> image sequence(s) (flicker package) --> packed sprites & JSON frame coord map (flicker.min.js) --> canvas animation goodness
This step, namely converting your video into an image sequence, is at your discretion. Here are a couple possible methods:
- Using Photoshop: How To Convert A Video Into An Image Sequence
- Using online-convert.com: Online image converter to JPEG (it should be noted that when I tried this, the jpegs were in the wrong order and need to be sorted)
After running npm install flicker
, you should notice the following folders in the package directory:
. +-- sequences/ (where to put your image sequences) +-- flicker/ (where the sequences are exported to packaged sprites and frame coord map)
Before running the flicker app, you'll need to move your image sequences into the sequences/
folder.
Each sequence should be stored within it's own folder, resembling the following:
. +-- sequences/ | +-- sequence1/ | | +-- image1.jpg | | +-- image2.jpg | +-- sequence2/ | | +-- image1.jpg | | +-- image2.jpg +-- flicker/
Then run npm start
from the package directory - this process can take a little while, depending on the amount of images
being packed into sprites. After it's finished, your packed sprites and the JSON map representing the entire animation
(both sequence1 and sequence2 combined) will be inside flicker/
:
. +-- sequences/ | +-- sequence1/ | | +-- image1.jpg | | +-- image2.jpg | +-- sequence2/ | | +-- image1.jpg | | +-- image2.jpg +-- flicker/ | +-- sequence1_sprite.jpg/ | +-- sequence2_sprite.jpg/ | +-- flicker_map.json/
You can find an example by heading to flicker.jonathan-brooks.co.uk and viewing the page source, but I'll include a snippet on it's set up here:
Quick note, the canvas width
and height
attributes have been set according to the native size of each
image in the sprite (each image is the same size, as they're all derived from the same video). Make sure
to do this for your canvas too - then scaling it is as easy changing the width/height in CSS.
Additionally, the following snippet has a .controls
range slider which acts as a seek control
for the flicker. At a later date I'll document how you can replace this with your own custom implementation using the
Flicker.seek()
function.
<div id="flicker1" class="flicker"> <canvas class="canvas" width="560" height="420"></canvas> <div class="controls"> <input type="range" min="0" max="1" value="0" step="1" oninput="this.setAttribute('value', this.value);"/> </div> <div class="sprites" style="display: none;"> <img class="sprite" src="flicker/sequence001_sprite.jpg" alt=" "/> <img class="sprite" src="flicker/sequence002_sprite.jpg" alt=" "/> </div> </div> <script type="text/javascript" src="js/flicker.min.js"></script>
The js setup in this snippet is (thus far) as complicated as the config gets. I'll be adding additional features in the future, like additional event handlers, and more config options like playThrough (pausing between sequences) and show/hide default seek controls
var map = // copy flicker_map.json into a variable here, like { frames: [SUPER LONG ARRAY] }; // register the Flicker object var myFlicker = new Flicker({ animation: map, // specify the frame coordinate map rootPath: 'flicker/', // specify the root path for the sprites (defaults to flicker/) container: document.getElementById('flicker1') // specify the context for the flicker }); // flicker provides a utility called waitOnImages which waits for the source sprites to load // this is necessary when using high res images, as playing the animation before the images // have loaded results in the canvas drawing a blank image myFlicker.utils.waitOnImages(function () { console.log('done loading'); /* BACKGROUND LOOP EXAMPLE play from current frame (defaults to frame 0 (the beginning)), then register an event handler on flickerEnd to play the flicker from the beginning whenever the flicker finishes */ this.play(); this.on('flickerEnd', function(direction){ console.log('flicker ended whilst playing %s', direction); this.play(0); // play from the beginning }); /* ILLUSTRATIVE EXAMPLE play from current frame, wait for 2 seconds, pause, wait for 3 seconds, reverse from current frame, wait for one second, play from frame 0 (the beginning) */ // this.play().wait(2).pause().wait(3).reverse().wait(1).play(0); }); // naturally, event handlers can also be registered outside the // waitOnImages function, directly on the flicker object // the sequenceChange event is emitted whenever the flicker transitions // from one sequence to another myFlicker.on('sequenceChange', function(seq){ console.log('transitioned to sprite sequence: %s', seq); /* STOP PLAYTHROUGH EXAMPLE pauses between each sequence, requiring manual replaying to continue the flicker (perhaps triggered by a scroll event?) */ // this.pause(); });
It should be noted that this tool is very much in pre-production, and as such it's still pretty limited. Here are some of the issues I'll be working on henceforth:
- At the moment, because the sprite packer tries to process all the sequences at once, it falls over if the sequences are too long (and they often are - 24 images = 1 second of video after all). This fix is a priority and will come soon.
- In testing the images package (which flicker depends on for sprite packing), I found that somewhat arbitrarily it falls over when the sprite resolution roughly exceeds 10,000 x 10,000 pixels. Again, I will do some investigating into why this limit exists and see if I can work around it with some crafty image compression (or something similar).
- At the moment you have to manually separate the huge monolithic image sequences you get from the video export manually - at some point in the future I'm hoping to add custom CLI parameters/JSON config file which let you specify when to cut the sequence into sub-sequences at given points in time (specified in frames or seconds from the beginning of the video).
If you have any questions, or wish to contribute, then don't hesitate to contact me!
Happy coding everyone ♥