17 top CSS animation examples

You've probably noticed the number of CSS animation examples featuring on websites has been on the rise lately. Animation is already making a splash online, and is set to become one of the key web design trends of 2018. All over the web, designers are getting creative and using CSS animations to bring personality to their sites, explain complex ideas quickly and easily, and guide their users' actions.

These animations don't need to be overblown – even a subtle movement can have a big impact (most still have their roots in Disney's classic 12 principles of animation). 

In this article, we've pulled together a selection of CSS animation examples from some of the biggest websites around, and dug into the code to show you how to achieve these effects yourself. This page features in-depth tutorials, or click through to page 2 for inspiring effects (and links to their code) for you to dig into yourself.

What is CSS animation?

CSS animation is a method of animating certain HTML elements without having to use processor and memory-hungry JavaScript or Flash. There's no limit to the number or frequency of CSS properties that can be changed. CSS animations are initiated by specifying keyframes for the animation: these keyframes contain the styles that the element will have.

01. Blowing bubbles

CSS animations: bubbles

Click to see the animation in action

The CSS bubble animation that features on 7UP is a beautiful example of carrying a brand theme through into the website design.The animation consists of a few elements: the SVG ‘drawing’ of the bubbles and then two animations applied to each bubble. 

The first animation changes the opacity of the bubble and moves it vertically in the view box; the second creates the wobbling effect for added realism. The offsets are handled by targeting each bubble and applying a different animation duration and delay.

In order to create our bubbles we’ll be using SVG. In our SVG we create two layers of bubbles: one for the larger bubbles and one for the smaller bubbles. Inside the SVG we position all of our bubbles at the bottom of the view box.

<g class="bubbles-large" stroke-width="7">
  <g transform="translate(10 940)">
  <circle cx="35" cy="35" r="35"/>
  </g>
  ...
</g>
<g class="bubbles-small" stroke-width="4">
  <g transform="translate(147 984)">
  <circle cx="15" cy="15" r="15"/>
  </g>
</g>
  ...
</g>

In order to apply two separate animations to our SVGs, both utilising the transform property, we need to apply the animations to separate elements. The <g> element in SVG can be used much like a div in HTML; we need to wrap each of our bubbles (which are already in a group) in a group tag.

<g>
  <g transform="translate(10 940)">
  <circle cx="35" cy="35" r="35"/>
  </g>
</g>

CSS has a powerful animation engine and really simple code in order to produce complex animations. We’ll start with moving the bubbles up the screen and changing their opacity in order to fade them in and out at the beginning and end of the animation.

@keyframes up {
  0% {
  opacity: 0;
  }
  10%, 90% {
  opacity: 1;
  }
  100% {
  opacity: 0;
  transform: translateY(-1024px);
  }
}

In order to create a wobbling effect, we simply need to move (or translate) the bubble left and right, by just the right amount – too much will cause the animation to look too jaunting and disconnected, while too little will go mostly unnoticed. Experimentation is key with when working with animation.

@keyframes wobble {
  33% {
  transform: translateX(-50px);
  }
  66% {
  transform: translateX(50px);
  } }

In order to apply the animation to our bubbles, we’ll be using the groups we used earlier and the help of nth-of-type to identify each bubble group individually. We start by applying an opacity value to the bubbles and the will-change property in order to utilise hardware acceleration.

.bubbles-large > g {
  opacity: 0;
will-change: transform, opacity;}
.bubbles-large g:nth-of-type(1) {...}
...
.bubbles-small g:nth-of-type(10) {...}

We want to keep all the animation times and delays within a couple of seconds of each other and set them to repeat infinitely. Lastly, we apply the ease-in-out timing function to our wobble animation to make it look a little more natural.

.bubbles-large g:nth-of-type(1) {
  animation: up 6.5s infinite; }
.bubbles-large g:nth-of-type(1) circle {
  animation: wobble 3s infinite ease-in-out; }
...
bubbles-small g:nth-of-type(9) circle {
  animation: wobble 3s 275ms infinite ease-in-out; }
.bubbles-small g:nth-of-type(10) {
animation: up 6s 900ms infinite;}

02. Scrolling mouse

CSS animations: scrolling mouse

Click to see the animation in action

A subtle scrolling mouse animation can give direction to the user when they first land on a website. Although this can be accomplished using HTML elements and properties, we're going to use SVG as this is more suited to drawing.

Inside our SVG we need a rectangle with rounded corners and a circle for the element we’re going to animate, by using SVG we can scale the icon to any size we need.

<svg class="mouse" xmlns="..." viewBox="0 0 76 130" preserveAspectRatio="xMidYmid meet">
  <g fill="none" fill-rule="evenodd">
  <rect width="70" height="118" x="1.5" y="1.5" stroke="#FFF" stroke-width="3" rx="36"/>
  <circle cx="36.5" cy="31.5" r="4.5" fill="#FFF"/>
  </g>
</svg>

Now we’ve created our SVG, we need to apply some simple styles in order to control the size and position of the icon within our container. We’ve wrapped a link around the mouse SVG and positioned it to the bottom of the screen.

.scroll-link {
  position: absolute;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
}
.mouse {
  max-width: 2.5rem;
  width: 100%;
  height: auto;
}

Next we’ll create our animation. At 0 and 20 per cent of the way through our animation, we want to set the state of our element as it begins. By setting it to 20% of the way through, it will stay still for part of the time when repeated infinitely.

@keyframes scroll {
  0%, 20% {
  transform: translateY(0) scaleY(1);
  }
}

We need to add in the opacity start point and then transform both the Y position and the vertical scale at the 100% mark, the end of our animation. The last thing we need to do is drop the opacity in order to fade out our circle.

@keyframes scroll {
  ...
  10% {
  opacity: 1;
  }
  100% {
  transform: translateY(36px) scaleY(2);
  opacity: 0.01;
  }
}

Lastly we apply the animation to the circle, along with the ‘transform-origin’ property and the will-change property to allow hardware acceleration. The animation properties are fairly self-explanatory. The cubic-bezier timing function is used to first pull the circle back before dropping it to the bottom of our mouse shape; this adds a playful feel to the animation.

.scroll {
  animation-name: scroll;
  animation-duration: 1.5s;
  animation-timing-function: cubic-bezier(0.650, -0.550, 0.250, 1.500);
  animation-iteration-count: infinite;
  transform-origin: 50% 20.5px;
  will-change: transform;
}

03. Animated writing

CSS animations: writing

Click to see the animation in action

The Garden Eight website uses a common animation technique whereby text appears to be written out. To achieve the effect, we turn to SVG. To begin with, we’ll create the SVG. There are two approaches here: convert the text to paths in order to animate them or use SVG text. Both approaches have their pros and cons.

Start by creating our keyframe animation. The only function we need it to perform is to change the stroke-dashoffset. Now we’ve created our animation, we need to apply the values we want to animate from. We set the stroke-dasharray, which will create gaps in the stroke. We want to set our stroke to be a large enough value to cover the entire element, finally offsetting the dash by the length of the stroke.

The magic happens when we apply our animation. By animating the offset, we’re bringing the stroke into view – creating a drawing effect. We want the elements to draw one at a time, with some overlap between the end of drawing one element and beginning to draw the next. To achieve this we turn to Sass/SCSS and nth-of-type to delay each letter by half the length of the animation, multiplied by the position of that particular letter.

04. Flying birds

CSS animations: flying birds

Click to see the animation in action

We start with completely straight vector lines, drawing each frame of our animation, depicting the bird in a different state of flight. We then manipulate the vector points and round the lines and edges. Finally, we put each frame into an equally sized box and place them side-by-side. Export the file as an SVG.

The HTML setup is really simple. We just need to wrap each bird in a container in order to apply multiple animations – one to make the bird fly and the other to move it across the screen.

<div class="bird-container">
  <div class="bird"></div>
</div>

We apply our bird SVG as the background to our bird div and choose the size we want each frame to be. We use the width to roughly calculate the new background position. The SVG has 10 cells, so we multiply our width by 10 and then alter the number slightly until it looks correct.

.bird {
  background-image: url('bird.svg');
  background-size: auto 100%;
  width: 88px;
  height: 125px;
  will-change: background-position;
}
@keyframes fly-cycle {
  100% {
  background-position: -900px 0;
  } 
}

CSS animation has a couple of tricks you may not be aware of. We can use the animation-timing-function to show the image in steps – much like flicking through pages in a notebook to allude to animation.

animation-name: fly-cycle;
animation-timing-function: steps(10);
animation-iteration-count: infinite;
animation-duration: 1s;
animation-delay: -0.5s;

Now we’ve created our fly cycle, our bird is currently flapping her wings but isn’t going anywhere. In order to move her across the screen, we create another keyframe animation. This animation will move the bird across the screen horizontally while also changing the vertical position and the scale to allow the bird to meander across more realistically.

Once we’ve created our animations, we simply need to apply them. We can create multiple copies of our bird and apply different animation times and delays. 

.bird--one {
  animation-duration: 1s;
  animation-delay: -0.5s;
}
.bird--two {
  animation-duration: 0.9s;
  animation-delay: -0.75s;
}

05. Cross my hamburger

CSS animations: hamburger transition

Click to see the animation in action

This animation is used all over the web, turning three lines into a cross or close icon. Until fairly recently, the majority of implementations have been achieved using HTML elements, but actually SVG is much more suited to this kind of animation – there’s no longer a need to bloat your buttons code with multiple spans. 

Due to the animatable nature and SVG and its navigable DOM, the code to accomplish the animation or transition changes very little – the technique is the same. 

We start by creating four elements, be it spans inside of a div or paths inside of an SVG. If we’re using spans, we need to use CSS to position them inside the div; if we’re using SVG, this is already taken care of. We want to position lines 2 and 3 in the centre – one on top of another – while spacing lines 1 and 4 evenly above and below, making sure to centre the transform origin.

We’re going to rely on transitioning two properties: opacity and rotation. First of all, we want to fade out lines 1 and 4, which we can target using the :nth-child selector.

.menu-icon.is-active {element-type}:nth-child(1),
.menu-icon.is-active {element-type}:nth-child(4) {
  opacity: 0; }

The only thing left to do is target the two middle lines and rotate them 45 degrees in opposite directions.

.menu-icon.is-active {element-type}:nth-child(2) {
  transform: rotate(45deg); }
.menu-icon.is-active {element-type}:nth-child(3) {
  transform: rotate(-45deg); } 

06. Chasing circles

CSS animations: circles

Click to see the animation in action

The animated loading icon is made up of four circles. The circles have no fill, but have alternating stroke-colours.

<svg class="loader" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 340 340">
  <circle cx="170" cy="170" r="160" stroke="#E2007C"/>
  <circle cx="170" cy="170" r="135" stroke="#404041"/>
  <circle cx="170" cy="170" r="110" stroke="#E2007C"/>
  <circle cx="170" cy="170" r="85" stroke="#404041"/>
</svg>

In our CSS, we can set some basic properties to all of our circles and then use the :nth-of-type selector to apply a different stroke-dasharray to each circle.

circle:nth-of-type(1) {
  stroke-dasharray: 550; 
}
circle:nth-of-type(2) {
  stroke-dasharray: 500; 
}
circle:nth-of-type(3) {
  stroke-dasharray: 450;}
circle:nth-of-type(4) {
  stroke-dasharray: 300; 
}

Next, we need to create our keyframe animation. Our animation is really simple: all we need to do is to rotate the circle by 360 degrees. By placing our transformation at the 50% mark of the animation, the circle will also rotate back to its original position.

@keyframes preloader {
  50% {
  transform: rotate(360deg);
  } 
}

With our animation created, we now just need to apply it to our circles. We set the animation name; duration; iteration count and timing function. The ‘ease-in-out’ will give the animation a more playful feel. 

animation-name: preloader;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;

At the moment, we have our loader, but all of the elements are rotating together at the same time. To fix this, we’ll apply some delays. We’ll create our delays using a Sass for loop.

@for $i from 1 through 4 {
  &:nth-of-type(#{$i}) {
  animation-delay: #{$i * 0.15}s;
  } }

Due to the delays, our circle now animates in turn, creating the illusion of the circles chasing each other. The only problem with this is that when the page first loads, the circles are static, then they start to move, one at a time. We can achieve the same offset effect, but stop the unwanted pause in our animation by simply setting the delays to a negative value.

animation-delay: -#{$i * 0.15}s;

07. Retro clickable accordion

CSS animations: accordian menu

Click to see the animation in action

Belgian interface designer and CSS trickster Benjamin de Cock shows us how to use the power of CSS3 to create an animated, automatically centred clickable accordion.

I'll use some dead simple HTML5 code for this accordion. Basically, each section of the accordion is a section element (makes sense doesn't it?) and contains a title and a paragraph.

<div>
<section>
<h1>Introduction</h1>
<p>Lorem ipsum dolor sit amet...</p>
</section>
<section>
<h1>General information</h1>
<p>Lorem ipsum dolor sit amet...</p>
</section>
<section>
<h1>Contact us</h1>
<p>Lorem ipsum dolor sit amet...</p>
</section>
</div>

The idea is to wrap the section titles in anchors that link to themselves. Since :target lets us select the targeted element, we'll also be allowed to select the element that follows it using the adjacent sibling selector. Let's try that. Here's the modified HTML markup:

<div>
<section>
<a href="#intro" id="intro">
<h1>Introduction</h1>
</a>
<p>Lorem ipsum dolor sit amet...</p>
</section>
<section>
<a href="#info" id="info">
<h1>General information</h1>
</a>
<p>Lorem ipsum dolor sit amet...</p>
</section>
<section>
<a href="#contact" id="contact">
<h1>Contact us</h1>
</a>
<p>Lorem ipsum dolor sit amet...</p>
</section>
</div>

And here's the incredibly simple CSS to get the very first toggle behaviour:

p {
display:none;
}
:target + p {
display:block;
}

I'll start by centring the accordion both horizontally and vertically. The flexbox model is the perfect positioning method for that kind of effect since it allows us to display an element always at the centre of a block (here: the page) without specifying any dimension. I'll use the standard notations for this tutorial but don't forget to use the vendor prefixes in your final code.

* {
margin:0;
padding:0;
}
html,body {
height:100%;
}
body {
display:box;
box-orient:vertical;
box-pack:center;
box-align:center;
/* For Firefox */ width:100%;
}
div {
width:250px;
}

Centred, but still pretty terrible visually. Adding more CSS sexiness:

body {
font:.7em/1.5 "lucida grande", arial, sans-serif;
background:#f3faff;
}
div {
background:#fff;
border-radius:5px;
box-shadow:0 1px 3px rgba(0,0,0,.3);
}
h1 {
font-size:1em;
}
a {
display:block;
height:23px;
line-height:23px;
background:linear-gradient(#eee, #ccc);
color:#333;
text-decoration:none;
text-align:center;
text-shadow:0 1px 0 rgba(255,255,255,.5);
border-bottom:1px solid #aaa;
}
#intro {
border-radius:2px 2px 0 0;
}
#contact {
border-radius:0 0 3px 3px;
}
a:hover, a:focus {
opacity:.9;
}
a:active {
background:linear-gradient(#ccc, #ddd);
color:#000;
}

Woot! It's looking both functional and sexy by now. Regarding my requirements, I'm almost there. Just missing the animated part actually. Bad news: adding those transitions isn't that easy.

The main problem is that you obviously can't perform transitions on a property such as display so you'll have to use numerical values. So, instead of hiding and showing the sections with display, I'll use the height property to switch from 0 to the content height.

Second bad news: you currently can't perform a transition between a numerical value and "auto". That means I'll have to specify a height for each section manually. As far as I can tell, this is something the W3C is aware of and they agree that it should be possible. I hope browser vendors will implement that: it would be so helpful. Here's the final code:

* {
margin:0;
padding:0;
}
html,body {
height:100%;
}
body {
display:box;
box-orient:vertical;
box-pack:center;
box-align:center;
font:.7em/1.5 "lucida grande", arial, sans-serif;
background:#f3faff;
/* For Firefox */ width:100%;
}
div {
width:250px;
background:#fff;
border-radius:5px;
box-shadow:0 1px 3px rgba(0,0,0,.3);
}
p {
height:0;
padding:0 15px;
overflow:hidden;
transition:height .4s ease-out, padding .4s ease-out;
}
h1 {
font-size:1em;
}
a {
display:block;
height:23px;
line-height:23px;
background:linear-gradient(#eee, #ccc);
color:#333;
text-decoration:none;
text-align:center;
text-shadow:0 1px 0 rgba(255,255,255,.5);
border-bottom:1px solid #aaa;
}
#intro {
border-radius:2px 2px 0 0;
}
#contact {
border-radius:0 0 3px 3px;
}
a:hover, a:focus {
opacity:.9;
}
a:active {
background:linear-gradient(#ccc, #ddd);
color:#000;
}
:target + p {
padding:10px 15px;
border-bottom:1px solid #ccc;
}
#intro:target + p {
height:70px;
}
#info:target + p {
height:230px;
}
#contact:target {
border-radius:0;
}
#contact:target + p {
height:180px;
}

Next page: More inspiring CSS animation examples to explore