Cody G. Henshaw

Hacking the WWW

Faking an Animated .gif With CSS Keyframes

Animated gifs can be pretty impactful if used correctly. They can illustrate a point without having to embed a video or a wall of text. The issue with gifs is that they are just pictures. They can’t be stopped or started (though many people fake it by swapping a static image). I ran across a question on Stack Overflow which asked for a solution to stop (by stop, I mean freeze and don’t reset) a gif on :hover and resume on mouseout. JavaScript plugins were immediately suggested, which might be the correct solution to this problem… but let’s try it with CSS animations anyway.

The first thing we will need to do is break our .gif into frames. There are tons of tools online that allow you to do this, and I’d even guess that ImageMagick will do it for you as well. Here’s the one I chose to start with:

bananananana

I broke mine into 8 individual frames. Here they are:

banana1 banana2 banana3 banana4 banana5 banana6 banana7 banana8

So, that’s it for prep work… Let’s get our html ready.

1
<div id="faux-gif"></div>

Yep, that’s it… Now, let’s add one of the images as a background-image.

#faux-gif { 
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    margin: auto;
    
    background-image: url(http://i.imgur.com/E2ee6fI.gif);
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center;
  
}

See the Pen gqxEo by Cody Henshaw (@brbcoding) on CodePen

So, the cool part about this is that we are using background-image. The background image can be animated with keyframes. Therefore, we can use keyframes to change the url of the background-image! Just make sure to call the animation from within your #faux-gif… You can read more about keyframes on the MDN Site.

A trivial example using just two images might be something like this. Note the animation: giffy 0.5s infinite linear. That’s how we make the animation actually run. 0.5s will be the duration of our animation called “giffy”, it will run indefinitely, and it we will use a linear timing function (as opposed to ease, ease-in-out, etc…) steps(1) because it makes the transition between slides much smoother (removes the fade-in/out effect):

#faux-gif {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    margin: auto;
    background-image: url(http://i.imgur.com/E2ee6fI.gif);
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center;
    width: 300px;
    height: 300px;
    animation: giffy 0.5s infinite steps(1);
}


@keyframes giffy {
    from   { background-image: url('http://i.imgur.com/E2ee6fI.gif'); } 
    to { background-image: url('http://i.imgur.com/fx5wUNY.gif'); }
}

See the Pen urHce by Cody Henshaw (@brbcoding) on CodePen

We have an almost-gif now… We just need to add the other frames. The cool part about keyframes is that you can use percentages instead of to{ } ... from{ }. Let’s see how many we will need… We have 8 frames total, and 100% duration to work with. You may first think that you’ll work in increments of 12.5%. That would be fine, but we haven’t taken into account that our first frame is already taken care of with the initial state. That’s the value we’ll use at 0%. Instead, we’ll want to do 100 / 7 (rounded to something simple to work with)… I’ll just use 14.25%. Now, we’ll create keyframes from 0% – ~100% in increments of 14.25%.

#faux-gif {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    margin: auto;
    background-image: url(http://i.imgur.com/E2ee6fI.gif);
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center;
    width: 300px;
    height: 300px;
    animation: giffy 0.5s infinite steps(1);
}

#faux-gif:hover {
    animation-play-state:paused;
}

@keyframes giffy {
    0%   { background-image: url('http://i.imgur.com/E2ee6fI.gif'); } 
    14.25%  { background-image: url('http://i.imgur.com/JIi0uul.gif'); }
    28.5%  { background-image: url('http://i.imgur.com/owNGnNN.gif');}
    42.75%  { background-image: url('http://i.imgur.com/2Ii6XOz.gif'); }
    57%  { background-image: url('http://i.imgur.com/ZmQBrQ5.gif'); }
    71.25%  { background-image: url('http://i.imgur.com/iAsfHyY.gif'); }
    85.5%  { background-image: url('http://i.imgur.com/AJwRblj.gif'); }
    99.75% { background-image: url('http://i.imgur.com/fx5wUNY.gif'); }
}

See the Pen Kiumf by Cody Henshaw (@brbcoding) on CodePen

And that’s about it. You can play with the animation timing, the duration, etc… You couldn’t do that with your .gif, now could you? You’ll see that I also added a :hover state. That was to satisfy the requirements of the Stack Overflow post I was solving with this technique. Thanks for checking it out, please let me know what mistakes I have made, and say hi @CodyHenshaw on twitter.

Update: December 9th

So, I was testing this method, and it seems that it doesn’t work in firefox. I wrote this quick n’ dirty FauxGif “class” to take care of that. This doesn’t demonstrate pre-loading of the images, which is something you will want to do if you don’t want to see blank frames.

window.onload = function() {

    function FauxGif(element, frames, speed) {
        this.currentFrame = 0,
        this.domElement   = element,
        this.frames       = frames || null,
        this.speed        = speed  || 200;
        this.interval;
        this.init();
    }

    FauxGif.prototype = {
        init: function() {
            // set the first one to the first image
            console.log(this.frames[0])
            this.domElement.style.backgroundImage = "url(" + this.frames[0] + ")";
        },
        pause: function() {
            clearInterval(this.interval);
        },
        resume: function() {
            var that = this;

            that.interval = setInterval(function(){
                console.log(that.frames[that.currentFrame])
                console.log(that.frames.length);
                that.currentFrame < that.frames.length - 1 ? that.currentFrame++ : that.currentFrame = 0;
                that.domElement.style.backgroundImage = "url(" + that.frames[that.currentFrame] + ")";
            }, 200);
        }
    }

    var frames = [
                    'http://i.imgur.com/E2ee6fI.gif',
                    'http://i.imgur.com/JIi0uul.gif',
                    'http://i.imgur.com/owNGnNN.gif',
                    'http://i.imgur.com/2Ii6XOz.gif',
                    'http://i.imgur.com/ZmQBrQ5.gif',
                    'http://i.imgur.com/iAsfHyY.gif',
                    'http://i.imgur.com/AJwRblj.gif',
                    'http://i.imgur.com/fx5wUNY.gif'
                ]

    var elem = document.querySelector('#faux-gif'),
        gif  = new FauxGif(elem, frames);


    elem.addEventListener('mouseenter', function(){
        gif.resume()
    });

    elem.addEventListener('mouseleave', function() {
        gif.pause();
    });
}

See the Pen JhroI by Cody Henshaw (@brbcoding) on CodePen

Again, let me know if I have any issues. Thanks for stopping by.

CSS Variables in Firefox - AKA Variable Verbosity

So, I saw this tweet from Chris Heilmann.

Awesome… maybe. I grabbed a fresh copy of FF Nightly and got to playing. The MDN Docs give a couple of examples… Here’s another.

So, in this example, we don’t use any variables at all. We do, however, repeat the color #bada55 FOUR times. I’m primarily a backend developer, and this redundancy kind of gives me the chills… Thanks to CSS Variables, however, we can eliminate some of this repetition. Here’s another example, this time using variables… (Keep in mind, this will only work in Firefox 29 Nightly at the moment.)

Pretty cool, right? It’s not exactly shorter, but it is nice not having to remember hex values or color names…

The title of this post included something along the lines of “Variable Verbosity”, and that’s not to discount the addition of variable support into browsers. I think it’s awesome that the web is moving forward, just don’t go throwing away your Sass or Less install just yet (obviously these tools do WAY more than just add variables). Here’s how variables look when you use preprocessors vs. firefox’s implementation (merely to show differences in implementation).

Sass

1
2
3
4
$foo: #000000;
.bar {
  background: $foo;
}

Less

1
2
3
4
@foo: #000000;
.bar {
  background: @foo;
}

FF 29

1
2
3
4
5
6
7
/* invalid if not scoped */
:root {
  var-foo: #000000;
}
.bar {
  background: var(foo);
}

Well, that about does it for this article. As you can see, the native implementation is much longer than the preprocessing variants, but hey, at least we’re moving forward. I’ll mess around with it a bit more, and see if I can come up with some cool stuff that shows that these are actually useful (beyond the evident use). If I’ve got any errors (which is not unlikely), please shoot me a note @CodyHenshaw on twitter. Thanks for stopping by!

December 17, 2013 - Update:

So, after publishing this, something was brought to my attention. I had totally disregarded the concept of scope in this post, which is definitely one of the things that Mozilla did right with their variables. Scoped variables are helpful because they allow you to “silo” variables for use in specific parts of an application, or even declare them globally to be used throughout an application (in this case, a stylesheet). So, without further ado, let’s get to scope.

Scoping variables is trivial in both the Mozilla implementation, and with CSS preprocessors, like Sass. Here’s what it looks like with CSS (again, only works in FF 29 at the time of writing).

Here’s how you do it with Sass… (You could also define a mixin with vars, and use them wherever you need to)

Either method works. Just wanted to throw this out there to show the scope capabilities of both native CSS and Sass. If someone wants to give me a Less example, I’d be happy to put it out there too. Thanks.

Shorthand Function Queues With JavaScript

This is a short post explaining the line that blew my mind from John Resig’s Secrets of the JavaScript Ninja. It turns out that it’s a cool shorthand way of iterating over and calling multiple (anonymous) functions from a queue. The following is the function in question.

queue.js mark:3,3
1
2
3
4
5
6
7
8
function runTest() {
  if (!paused && queue.length) {
    queue.shift()();
    if (!paused) {
      resume();
    }
  }
}

Let’s go ahead and break it out of the original function so that we can see what’s actually happening (this block comes from a big test suite, so that’s not helpful in this concise example). So, this is what we will be looking at:

1
queue.shift()()

Before we get into the queue part though, let’s take a gander at the shift() function.

The shift method removes the element at the zeroeth index and shifts the values at consecutive indexes down, then returns the removed value.

1
2
3
4
5
// create an array
var arrToShift = [1, 2, 3, 4, 5]
// shift returns the value it removes, so let's log that
console.log(arrToShift.shift()) // 1
console.log(arrToShift) // [ 2, 3, 4, 5 ]

Not so useful yet? Ok… how about if we replace the array of integers with an array of functions?

1
2
3
4
5
6
var queue = [ function(){ return 'foo' },
            function(){ return 'bar' },
            function(){ return 'bat' }
          ]

console.log(queue.shift()) // [Function]

So our shift() did exactly what it did in the first example. It removed and returned the value at queue[0], which in this case, was a function. How do we run a function in javascript? By appending () to the function, of course! So, now check this out…

1
2
console.log(queue.shift()()) // foo
// woohoo! we returned that first function and ran it

You should now be able to see how we could use this as I mentioned earlier. Let’s run ALL of our functions in the queue.

1
2
3
4
5
6
7
sLen = queue.length; // starting queue length
for(var i = 0; i < sLen; i++) {
  console.log(queue.shift()())
}
// foo
// bar
// bat

That’s kind of long though, and this was about shorthand… SO, we can use shift() to our advantage here as well. Since the 0th element is REMOVED from the array, we should be able to just check if there is something at the 0th index. If so, we can keep going. This is the shorter version that I came up with.

1
2
3
4
5
while(queue[0]) { console.log(queue.shift()()); }
// while anything is at the 0th index, continue running
// foo
// bar
// bat

Pretty cool, right? We could also accomplish a queue-like system with map(), something like queue.map(function(x){ console.log(x()) })… That’s a different story though. Hopefully you took something away from this post… Feel free to catch up with me (or point out errors) on twitter @CodyHenshaw.