Creating the iOS Icon Jiggle / Wobble Effect in CSS

by kirupa   |   27 October 2013

When you press and hold an icon on your iOS (iPhone / iPad / iPod) device's home screen, you'll get the ability to rearrange the icons or uninstall the app associated with it. That's really boring and not the interesting part I want you to focus on. The interesting part is what these icons do as a result of you pressing and holding. They wobble and jiggle (aka wobiggle) around. It's pretty awesome.

Anyway, I thought it would be fun to re-create that effect. After about 30 minutes of fiddling around, you can see my attempt below (or in a separate window):

In my very humble opinion, I think this implementation is really REALLY close to what you see on an iOS device.

Anyway, in this deconstruction, you will learn all about how this effect works. It's going to be a lotta fun. Before you proceed, just make sure you are up-to-date on your knowledge of CSS Animations and working with nth-child selectors. Otherwise, your fun will be lessened by a great deal of confusion and possibly crying. You have been warned.

Onwards!

The Full HTML and CSS

Before we go any further, let me throw at you all of the HTML and CSS that makes this example work:

<!DOCTYPE html>
<html>

<head>
    <title>iPhone / iPad Icon Wobble Effect</title>
    <style>
        body {
            background-color: #EFEFEF;
            padding: 25px;
        }

        #main {
            width: 450px;
        }

        #main .icon {
            padding: 20px;
            border-radius: 40px;
        }

		#main .icon:nth-child(2n) {
			animation-name: keyframes1;
		    animation-iteration-count: infinite;
		    transform-origin: 50% 10%;
		}
		
		#main .icon:nth-child(2n-1) {
			animation-name: keyframes2;
		    animation-iteration-count: infinite;
		    animation-direction: alternate;
		    transform-origin: 30% 5%;
		}
		
		@keyframes keyframes1 {
		    0% {
		        transform: rotate(-1deg);
		        animation-timing-function: ease-in;
		    }
		    50% {
		        transform: rotate(1.5deg);
		        animation-timing-function: ease-out;
		    }
		}
		
		@keyframes keyframes2 {
		    0% {
		        transform: rotate(1deg);
		        animation-timing-function: ease-in;
		    }
		    50% {
		        transform: rotate(-1.5deg);
		        animation-timing-function: ease-out;
		    }
		}
    </style>
</head>

<body>
    <div id="main">
        <img class="icon" style="animation-delay: -0.75s; animation-duration: .25s" height="100" src="//www.kirupa.com/html5/examples/images/icon1.png" width="100">
        <img class="icon"style="animation-delay: -0.5s; animation-duration: .3s" height="100" src="//www.kirupa.com/html5/examples/images/icon2.png" width="100">
        <img class="icon" style="animation-delay: -0.05s; animation-duration: .27s" height="100" src="//www.kirupa.com/html5/examples/images/icon3.png" width="100">
        <img class="icon" style="animation-delay: -0.2s; animation-duration: .33s" height="100" src="//www.kirupa.com/html5/examples/images/icon4.png" width="100">
        <img class="icon" style="animation-delay: -0.31s; animation-duration: .24s" height="100" src="//www.kirupa.com/html5/examples/images/icon7.png" width="100">
        <img class="icon" style="animation-delay: -0.15s; animation-duration: .25s" height="100" src="//www.kirupa.com/html5/examples/images/icon5.png" width="100">
        <img class="icon" style="animation-delay: -0.2s; animation-duration: .22s" height="100" src="//www.kirupa.com/html5/examples/images/icon6.png" width="100">
        <img class="icon" style="animation-delay: -0.3s; animation-duration: .28s" height="100" src="//www.kirupa.com/html5/examples/images/icon8.png" width="100">
    	<img class="icon" style="animation-delay: -0.22s; animation-duration: .3s" height="100" src="//www.kirupa.com/html5/examples/images/icon9.png" width="100">
    </div>
    <script src="js/prefixfree.min.js"></script>
</body>

</html>

Take a few moments and glance through all of this. Don't worry if everything doesn't make sense yet. That's what the remaining sections are there to help you out with.

Deconstructing this Effect

Before we get to the implementation details, let's take a few steps back and look at what exactly this animation is doing. As you will see soon enough, this animation really isn't doing much, for the wobble is really nothing more than a rotation. Srsly!

The reason you may find this surprising is that we commonly think of a rotation as follows:

rotation from the center

The item you are rotating pivots around its vertical and horizontal center point. The key word to emphasize being center. See, the thing is, that doesn't always have to be the case. By moving the point your item rotates around to somewhere other than the center of the object, you can end up with something that looks as follows:

rotation off center

 

Notice that our object is still rotating. It is just that what it is rotating around isn't the center point anymore. To describe it more formally, the transformation point is no longer the center of the object. The transformation point is shifted up and to the left. This subtle change allows your rotation to look a bit more like the wobble effect that you see on the iOS icons.

Of course, this isn't everything. There is a little bit more to your animation like the duration and easing function, but we'll look at them shortly...in the next section!

Looking at the CSS

This effect and everything I explained so far is implemented entirely using CSS animations. The relevant CSS can be seen below:

#main .icon:nth-child(2n) {
    animation-name: keyframes1;
    animation-iteration-count: infinite;
    transform-origin: 50% 10%;
}

#main .icon:nth-child(2n-1) {
    animation-name: keyframes2;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    transform-origin: 30% 5%;
}

@keyframes keyframes1 {
    0% {
        transform: rotate(-1deg);
        animation-timing-function: ease-in;
    }
    50% {
        transform: rotate(1.5deg);
        animation-timing-function: ease-out;
    }
}

@keyframes keyframes2 {
    0% {
        transform: rotate(1deg);
        animation-timing-function: ease-in;
    }
    50% {
        transform: rotate(-1.5deg);
        animation-timing-function: ease-out;
    }
}

Take a few more moments to read through the CSS. Nothing should be too surprising, but let's look at the interesting parts of this.

The rotation is handled in the keyframes via the transform property's rotate function:

@keyframes keyframes2 {
    0% {
        transform: rotate(1deg);
        animation-timing-function: ease-in;
    }
    50% {
        transform: rotate(-1.5deg);
        animation-timing-function: ease-out;
    }
}

The easing function (aka animation-timing-function) is an ease-in as you are going through the rotation, and it is an ease-out as you return to the starting point. Keyframes by themselves aren't very useful, for they need to have the animation property declared somewhere and referring to them. That referring (among other important things) is handled by the two nearly identical #main .icon style rules - one of which you see below:

#main .icon:nth-child(2n-1) {
    animation-name: keyframes2;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    transform-origin: 30% 5%;
}

The only line to call out here is the transform-origin property. This is the very important property (VIP) that allows you to specify the center point where a transformation such as your rotate will actually take place. It is this property that offsets your icon's transformation point and turns your simple rotation into a slight wobble.

So far, so good, right?

Faking Randomness

Now, the challenge with implementing this effect entirely in CSS is that you can't really have randomness in how things move. Everything has to be predetermined. Despite this constraint, there are a few things you can do to help make each image wobble with a little uniqueness so that you don't get something that looks really mechanical and fake.

Slightly Different Variants of Style Rules + Keyframes

The first (and most obvious) of the few things is that, if you look at your CSS, you'll notice that I don't have just a single animation declaration and corresponding keyframes. Instead, I have two of each.

The style rules containing the animation declarations look as follows:

#main .icon:nth-child(2n) {
    animation-name: keyframes1;
    animation-iteration-count: infinite;
    transform-origin: 50% 10%;
}

#main .icon:nth-child(2n-1) {
    animation-name: keyframes2;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    transform-origin: 30% 5%;
}

The keyframes associated with these animation declarations are:

@keyframes keyframes1 {
    0% {
        transform: rotate(-1deg);
        animation-timing-function: ease-in;
    }
    50% {
        transform: rotate(1.5deg);
        animation-timing-function: ease-out;
    }
}

@keyframes keyframes2 {
    0% {
        transform: rotate(1deg);
        animation-timing-function: ease-in;
    }
    50% {
        transform: rotate(-1.5deg);
        animation-timing-function: ease-out;
    }
}

Notice that there are subtle differences in each of the variants. In one animation, you rotate from -1 degrees to 1.5 degrees. In the other, you rotate from 1 degree to -1.5 degrees. The transform-origin property that determines the center point of your rotation is also different, and one of the animations is set to alternate its direction.

Which element gets which animation version is handled by the nth-child pseudoselector that is happily attached to the #main .icon style rules. The nth-child selector with 2n will select every even numbered element. The nth-child selector with 2n-1 will select every odd numbered element. The end result is that every icon will alternatively get one variant of the animation declaration.

Unfortunately, having each icon animate in one of two ways isn't different enough. You'll quickly find yourself staring at a wall of icons that move in-sync. That isn't going to look nice, and we certainly don't want to bloat our CSS with a lot of slightly different copies of what we already have right now. The two copies we have right now are a bit overkill!

Altering Some CSS Properties and their Values...Inline!

Anyway, the way to account for this monotonous movement is our second of the few things. In this "thing", I specify some key animation properties inline on the icons themselves:

<div id="main">
    <img class="icon" style="animation-delay: -0.75s; animation-duration: .25s" height="100" src="//www.kirupa.com/html5/examples/images/icon1.png" width="100">
    <img class="icon"style="animation-delay: -0.5s; animation-duration: .3s" height="100" src="//www.kirupa.com/html5/examples/images/icon2.png" width="100">
    <img class="icon" style="animation-delay: -0.05s; animation-duration: .27s" height="100" src="//www.kirupa.com/html5/examples/images/icon3.png" width="100">
    <img class="icon" style="animation-delay: -0.2s; animation-duration: .33s" height="100" src="//www.kirupa.com/html5/examples/images/icon4.png" width="100">
    <img class="icon" style="animation-delay: -0.31s; animation-duration: .24s" height="100" src="//www.kirupa.com/html5/examples/images/icon7.png" width="100">
    <img class="icon" style="animation-delay: -0.15s; animation-duration: .25s" height="100" src="//www.kirupa.com/html5/examples/images/icon5.png" width="100">
    <img class="icon" style="animation-delay: -0.2s; animation-duration: .22s" height="100" src="//www.kirupa.com/html5/examples/images/icon6.png" width="100">
    <img class="icon" style="animation-delay: -0.3s; animation-duration: .28s" height="100" src="//www.kirupa.com/html5/examples/images/icon8.png" width="100">
    <img class="icon" style="animation-delay: -0.22s; animation-duration: .3s" height="100" src="//www.kirupa.com/html5/examples/images/icon9.png" width="100">
</div>

Notice that each img element has the animation-delay property and animation-duration properties set to slightly different values.

A negative value for the animation-delay property determines how many seconds into the animation to actually start playing. It's basically an offset. The animation-duration property is more easy to comprehend. It specifies how long the animation will actually play.

By altering the values on these two properties for each element, I am able to ensure a certain level of non-uniformity in how the animation plays across all of the icons. This combined with our alternating animation variants, you get a pretty compelling simulation of random behavior entirely done using only CSS.

Conclusion

It is an off-center rotation. That's it. The iOS wobble effect, as you can see, is pretty straightforward (and little less exciting) once you simplify the animation down to its technical parts. In fact, the bulk of the markup isn't actually there to re-create the effect. Most of what you see is devoted entirely towards helping all of our icons move in a slightly unique way...aka trying to fake randomness using CSS.

For true randomness, you need to employ some JavaScript. You can make a pretty compelling case that the approach I explained is a bit inefficient. You could say that it would be better to specify some of the animation parameters in code instead of largely duplicating style rules or specifying inline styles. For example, instead of specifying inline styles for the animation-delay and animation-duration properties on each image, I could have specified them entirely in code using a single for loop and our friendly Math.random function.

Ultimately, there is no right or wrong way to tackle this. For the handful of images you see here, just doing everything in CSS seemed is fine. Now, if you are going to be applying this effect to a lot of elements, specifying inline styles for each element can get pretty tedious. In that scenario, I highly encourage you to go the JavaScript route. Just be sure to account for Vendor Prefixes in JavaScript (video).

Getting Help

If you have questions, need some assistance on this topic, or just want to chat - post in the comments below or drop by our friendly forums (where you have a lot more formatting options) and post your question. There are a lot of knowledgeable and witty people who would be happy to help you out

Share

Did you enjoy reading this and found it useful? If so, please share it with your friends:

If you didn't like it, I always like to hear how I can do better next time. Please feel free to contact me directly via e-mail, facebook, or twitter.

Brought to you by...

Kirupa Chinnathambi
I like to talk a lot - A WHOLE LOT. When I'm not talking, I've been known to write the occasional English word. You can learn more about me by going here.

Add Your Comment (or post on the Forums)

blog comments powered by Disqus

Awesome and high-performance web hosting!
BACK TO TOP
new books - yay!!!