Blog (my web musings)

Find out what's interesting me, get tips, advice, and code gems that don't fit elsewhere.

Search Blog

Play animated GIFs on-demand with this GIF player. No JavaScript dependencies. Initial loading of a GIF is delayed until the user starts playback manually.

For a recent project, I needed to display a number of animated GIFs on a web page to illustrate before and after scenarios. Now, the problem with GIFs is that they are usually displayed on a web page using the <img> element, which means that they download immediately when the page loads. Some of my animated GIFs were several MBs in size, which had a massively negative impact on page performance... and it outright killed mobile browser stability. So I decided to create a GIF player - like a simple video player, but for animated GIFs, where download doesn't begin until the user clicks 'play' - On-Demand Animated GIF Player:



Works in modern browsers and IE8+ (no loading spinner or transparent overlay in IE8).

HTML markup

The HTML markup for the GIF Player is very simple;

<span class="gif-player"><img src="/my-animation.png" alt="GIF Player" /></span>
<noscript><a href="/my-animation.gif">Watch the animated GIF</a></noscript>

An <img> tag wrapped in a <span> that holds the 'gif-player' class. A direct link to the full animated GIF is included in <noscript> tags as a fall-back for when JavaScript fails.

Note that the initial img src in the GIF Player markup is a static '.png' image - JavaScript will switch this for the (much larger) animated GIF when the player is clicked. Therefore, each GIF Player requires 2 images; a static '.png' and the animated '.gif'. Both should be called the same thing (apart from the file extension) and saved in the same location.


Next up is the CSS;

.gif-player { position:relative; display:inline-block }
.gif-player img { display:block; max-width:100% }
.js .gif-player:before, .gif-player-load.gif-player:before { content:""; position:absolute; top:0; right:0; bottom:0; left:0; background:rgba(0,0,0,0.25) }
.js .gif-player:after, .gif-player-play.gif-player:hover:after { content:""; position:absolute; top:50%; margin-top:-32px; left:50%; margin-left:-32px; height:64px; width:64px; background:#fff url(../_images/play.png) 50% no-repeat; box-shadow:0 0 0 0.25em #fff; border-radius:50%; cursor:pointer }
.gif-player-play.gif-player:hover:after { background-image:url(../_images/pause.png) }
.gif-player-load.gif-player:after { background-image: url(/../_images/loading-64x64.gif) }
.gif-player-play.gif-player:before, .gif-player-play.gif-player:after { background:none; box-shadow:none }

There are a few '.js' class selectors in there that will be added to the <html> element via JavaScript. This ensures that the GIF player only looks like a mock video player when JavaScript is available. When it isn't, it will just look like the static '.png' image with a link underneath.

There is a bit of extra CSS for IE8 but you can grab that from the source of the demo page.


Now for some JavaScript - the first notable function is the loadGif() function;

function loadGif(el) { 
    var img = el.getElementsByTagName('img')[0];
    aniGif = new Image();
    aniGif.onload = function() {
        img.src = this.src; el.className = 'gif-player gif-player-active gif-player-play';
    el.className = 'gif-player gif-player-active gif-player-load';
    aniGif.src = img.src.replace(/\.[^/.]+$/, "") + '.gif';

This code locates the clicked static '.png' image and loads a '.gif' of the same name, switching the src attribute when the '.gif' has loaded.

During load state, 2 new classes are applied to the GIF Player markup; 'gif-player-active' and 'gif-player-load'.

Once the load has completed, the classes change again; 'gif-player-load' becomes 'gif-player-play'.

These classes change the icons in the CSS; The circular 'play' icon changes to a loading spinner and then to a 'pause' icon (only when hovered) respectively.

The other notable function is the removeGif() function;

function removeGif(el) { 
    var img = el.getElementsByTagName('img')[0];
    img.src = img.src.replace(/\.[^/.]+$/, "") + '.png';
    el.className = 'gif-player';
    aniGif.onload = null;

As you'd expect, in reverse-logic to the loadGif() function, it first locates the animated '.gif' image, then switches in a static '.png' of the same name. Additional classes are also removed, reverting back to the original 'gif-player' class.

There's a bit more to the script - an event handler with if/else conditions and a hasClass() function for checking player state - so get the complete script from the source of the demo page.

Extra info - iOS 'sticky hover' fix

You'll notice in the full script that there's a bit of code that looks like this;

(function(l){var i,s={touchend:function(){}};for(i in s)l.addEventListener(i,s)})(document); // sticky hover 

This hasn't actually got anything to do with the GIF Player, but I've included it to assist with an annoying behaviour in iPad / iPhone (iOS touch devices). The annoying behaviour I speak of is "sticky hover" syndrome, where hover CSS isn't removed from the active element until another focusable element is clicked (like a button or a link). Without this extra code, the GIF Player works fine up until the point where the animated GIF is playing - but the "pause" icon then remains visible in front of the player and obscures the animation. Android allows you to tap outside the player to remove the "pause" icon, but that doesn't natively happen on iOS. However, with the addition of the extra code, it does.

You might find that you don't need it, especially if you're already using a touch gesture and "fast click" script, but I thought I'd mention what it is, and what it does, so you can decide whether or not to keep it in. Feel free to delete it if you like.

Here's that link again - On-Demand Animated GIF Player: