Using CSS3 keyframe animation to create a swinging image

Created: Jan 4th, 15'

In this short tutorial, we'll see how easy it is to create an image that swings like a pendulum continuously using CSS3 keyframes animation. Such an effect has become popular with signage and "discount" images, drawing subtle attention to them. Here's an example:

Image credits: Here

The CSS and HTML code

The source code is worth a thousand words, so lets first look at the CSS and HTML that comprise the above example before explaining how it all works:

The CSS:

@-webkit-keyframes swinging{
	0%{-webkit-transform: rotate(10deg);}
	50%{-webkit-transform: rotate(-5deg)}
	100%{-webkit-transform: rotate(10deg);}

@keyframes swinging{
	0%{transform: rotate(10deg);}
	50%{transform: rotate(-5deg)}
	100%{transform: rotate(10deg);}

	-webkit-transform-origin: 50% 0;
	transform-origin: 50% 0;
	-webkit-animation: swinging 3.5s ease-in-out forwards infinite;
	animation: swinging 3.5s ease-in-out forwards infinite;


<img class="swingimage" src="wintersale.png" />

@keyframes rule and animation property syntax

To create the swinging animation, we define a @keyframes rule specifying how our animation should unfold at various points. The syntax for @keyframes is as follows:

@keyframes animation-name{
	keyframes-selector: {css styles}
	keyframes-selector: {css styles}

Here's a quick breakdown of the parts that make up this syntax:

@keyframes rule syntax
Value Description
anmation-name An arbitrary string to identify this @keyframes animation
keyframes-selector A percentage value (0% to 10%) donating the point in the animation the enclosed styles should be applied at.

You can also use the two keywords "to" and "from" to designate 0% and 100% respectively, for example:

from {background: white;}
to {background: black;}
css styles One or more CSS styles (each separated by a semi colon) specifying the style at this point in the animation.

Keyframes animations are supported in IE10+, FF5+, Chrome, Opera 12+, and Safari 4+. Within this list, the older versions of Chrome, and Safari require a vendor prefix in front of @keyframes in order to work properly, hence the two versions of the same @keyframes animation definition in our example above (@-webkit-keyframes swinging{} and @keyframes swinging{}).

Note on prefixes: There are other vendor specific prefixes you may need to add in front of your CSS3 rule or property such as keyframes or transition to cover all your bases. The major vendor prefixes are -webkit-, -moz-, -ms-, and -o-. In general however only -webkit- is needed these days to cover Safari browsers still stubbornly hanging on to prefixed versions of CSS3 properties. The important thing is to test your CSS against your target browsers to see if adding a prefix makes a difference. You can use a tool such as autoprefixer to intelligently add vendor prefixes to default CSS3 rules and properties in your CSS.

Once the @keyframes animation has been defined, we use CSS's animation property to reference that animation inside a CSS element or class selector, such as:

	-webkit-transform-origin: 50% 0; /* for Safari and older Chrome */
	transform-origin: 50% 0;
	-webkit-animation: swinging 3.5s ease-in-out forwards infinite;
	animation: swinging 3.5s ease-in-out forwards infinite;

Here we're adding the animation property inside a CSS class (and then assigning the class to the target element). The animation property is shorthand for the following animation related properties:

animation shorthand property syntax
property Description
animation-name The @keyframes animation name to be associated with this animation. Defaults to none.
animation-duration The duration (integer) of the animation suffixed with either "s" or "ms" for seconds or milliseconds. Defaults to 0s, which means the animation won't run.
animation-timing-function Sets the timing function used to animate the @keyframe animation. Valid values include "ease", "ease-in", "linear", steps() etc. Defaults to "ease".
animation-delay The delay (integer) before the animation started, suffixed with either "s" or "ms" for seconds or milliseconds. Defaults to 0s.
animation-iteration-count Sets the number of times the animation should run before stopping. A value of "infinite" means forever. Defaults to 1.
animation-direction Defines whether the animation should play as normal, in reverse, or alternate between the two. Possible values are:
  • normal: The animation plays forward as normal. After each animation cycle, the animation resets to the beginning state and starts over again. Default value.
  • reverse: Plays the animation in reverse, starting from the end state.  After each animation cycle, the animation resets to the end state and starts over again.
  • alternate: The animation alternates between normal and reverse directions. In reverse, the animation starts at the end state and plays backwards. The animation timing function is also reversed.
  • alternate-reverse: The animation alternates between reverse and normal directions, starting in reverse for the first iteration.
animation-fill-mode Defines how the animation should apply styles to its target when the animation isn't playing anymore. Possible values are:
  • none: The animation should not apply any styles to the target when it is not playing. Default value.
  • forwards: The target element will retain the computed styles defined in the last keyframe (ie: when keyframe is at 100%) when the animation isn't playing.
  • backwards: The target element will retain the computed styles defined in the first keyframe (ie: when keyframe is at 0%) when the animation isn't playing.
  • both: The target element will retain the computed styles defined in both the first and last keyframes when the animation isn't playing.
animation-play-state Defines whether the animation is initially playing or not. Possible values are "running" or "paused". Defaults to "running".

Inside the animation shorthand property, if a particular animation property value is omitted, the default value for that property will be used. Furthermore, the order of each property value inside animation in general isn't important, with the exception of the two time related properties (animation-duration and animation-delay)- if both are defined, animation-duration should always proceed animation-delay.

Revisiting the CSS for our swinging image

In our CSS for creating a swinging image, we have the following defined for our "swingimage" class:

	-webkit-transform-origin: 50% 0;
	transform-origin: 50% 0;
	-webkit-animation: swinging 3.5s ease-in-out forwards infinite;
	animation: swinging 3.5s ease-in-out forwards infinite;

Here you can see we've set the animation shorthand property to:

animation: swinging 3.5s ease-in-out forwards infinite;

That is to say, we want the swinging keyframes animation to run for 3.5 seconds for each cycle, using the "ease-in-out" timing function, and for the target element to retain the last keyframe styles afterwards. With the target element keeping the last keyframe's styles at the end of the cycle, we create a seamless transition between the last and first keyframe styles when the next cycle begins. And last by not least, we want this animation to run for an infinite number of cycles.

Now lets turn our attention to the transform-origin property inside the CSS class. It dictates the x-offset and y-offset origin where any CSS related transformation will take place, which in our swinging image will be the rotation of the image. In the demo image above, the "hinge" where the rotation should originate from is exactly midpoint horizontally inside the image, and at the top vertically:

This is why for our transform-origin property value, we have the following:

transform-origin: 50% 0;

Lets consider a different image now with a hinge that's not dead center horizontally:

In this case, we'll want to adjust the transform-origin value to 35% horizontally:

transform-origin: 35% 0;


Image credits: Here

The transform-origin property supports either an explicit length value (ie: px) or percentage as its unit, so to be even more precise, you could specify say 77px for the x-offset value instead of 35% here.

The best way to get a handle on keyframe animation and the animation property is to try out different values and see the changes. You can play around with the code for the two swinging images below:

Play With Code