Playing with CSS3 Animations, Beginner’s Tutorial
I would like to share with you a personal experience I had while trying to build advanced visual animations with CSS keyframes
. The topic we’re going to address is animating several HTML elements simultaneously and taking advantage of keyframe flexibility. To explain this subject, we will create a simple fluid menu based on four squares. Clicking on one of them will display an overlay showing related content, closing the overlay will reverse the process showing the default menu. All delays will be managed directly with CSS — no javascript timeout()
here.
The example described below is the first of four demos, all of which are using the same JS file, CSS class names, and are based on the same operating principles. All demos have been tested on Chrome, Safari, Firefox, Opera, IE11 and IE10 (also on iOS). They are not intended for production; they are simply provided as tangible case studies. Additionally, you may have to implement some fallbacks for users (especially those using old IE browsers), something beyond the scope of this article.
To improve readability, CSS rules used throughout this article are without vendor prefixes. Make sure you add prefixes accordingly (e.g. -webkit-
and -moz-
). Opera and IE10+ do not need older prefixes anymore. You can read official W3C documentation for more information about vendor prefixes.
CSS3 Animation Basics
Let’s start by going over some elementary concepts. CSS3 keyframes
are powerful rules that let you control CSS styles of an element during a time scale. You can specify when a change will occur in percent where “0%” is the start of the animation and “100%” is the end. The keywords “from” and “to” are equivalent to “0%”” and “100%.” For compatibility reasons, make sure to always specify “0%” and “100%” values.
With Postcards Email Builder you can create and edit email templates online without any coding skills! Includes more than 100 components to help you create custom emails templates faster than ever before.
Free Email BuilderFree Email TemplatesCSS keyframes
syntax:
/* Percentage */ @keyframes moveTop { 0% { top: 0px; } 100% { top: 100px; } } /* From -> To */ @keyframes moveTop { from { top: 0px; } to { top: 100px; } }
Once declared, you can apply them to any element with the animation
CSS rule as well as setting up some parameters. For detailed options please check the official W3C documentation concerning CSS3 animations.
CSS animation
syntax:
.animated { animation: moveTop 2s 3; }
This rule would play “moveTop” keyframes three times on a timescale of 2 seconds.
The Markup
First we’re going to write our HTML. We start with a navigation that includes
four button
elements. Below there are four section
elements which will be used as overlays.
<!-- Top Navigation --> <div class="designmodo-nav"> <a class="button demo1 active" href="./">Demo 1</a> <a class="button demo2" href="demo2.html">Demo 2</a> <a class="button demo3" href="demo3.html">Demo 3</a> <a class="button demo4" href="demo4.html">Demo 4</a> </div> <!-- Demo Navigation --> <nav class="nav clearfix"> <button class="nav-el" id="el-topleft" data-id="ov-topleft"><span class="icon-heart"></span></button> <button class="nav-el" id="el-topright" data-id="ov-topright"><span class="icon-cloud"></span></button> <button class="nav-el" id="el-btmleft" data-id="ov-btmleft"><span class="icon-location"></span></button> <button class="nav-el" id="el-btmright" data-id="ov-btmright"><span class="icon-search"></span></button> </nav> <!-- Overlay (hidden by default) --> <section class="overlay" id="ov-topleft"> <div class="wrap"> <h1>Section 1</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <br><br> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <button class="close"><span class="mfg-cancel"></span></button> </section> <section class="overlay" id="ov-topright"> <div class="wrap"> <h1>Section 2</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <br><br> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <button class="close"><span class="mfg-cancel"></span></button> </section> <section class="overlay" id="ov-btmleft"> <div class="wrap"> <h1>Section 3</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <br><br> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <button class="close"><span class="mfg-cancel"></span></button> </section> <section class="overlay" id="ov-btmright"> <div class="wrap"> <h1>Section 4</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <br><br> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <button class="close"><span class="mfg-cancel"></span></button> </section>
As you can see, we are using a custom data attribute data-id
on our buttons to “link” them to their respective overlay containers. We will handle this later with Javascript. For now, focus on the fact that each button
gets a specific id, same thing for overlays.
Default CSS Styles
Now our HTML is ready, let’s define our layout appearance with some basic CSS rules for the nav
and each .nav-el
. Remember, we’re building four fluid squares with a big icon inside. We’re also adding rules for .active
, .active_reverse
and .inactive
, CSS classes we will use later.
/* Main Nav */ .nav { width: 80%; max-width: 420px; margin: 90px auto 90px; font-size: 33px; } /* Nav elements */ .nav-el { position:relative; display: inline-block; float: right; width: 47.5%; padding-bottom: 47.5%; color: white; background-color: #16a085; transition: background-color .3s ease-in; z-index: 10; } .nav-el:hover, .nav-el.active { background-color: #107360; } .nav-el.active_reverse { background-color: transparent; } /* Active button always on top */ .nav-el.active, .nav-el.active_reverse { z-index: 11; } /* Prevent click/hover on inactive buttons */ .nav-el.inactive { pointer-events: none; cursor: default; } /* Specific floating and margin */ .nav-el:nth-of-type(2n+1) { float: left; } .nav-el:nth-of-type(n+3) { margin-top: 5%; } /* Icons are horizontally/vertically centered */ [class^=&amp;amp;quot;icon-&amp;amp;quot;], [class*=&amp;amp;quot; icon-&amp;amp;quot;] { position: absolute; display: inline-block; top: 50%; left: 50%; line-height: 0; width: 1em; margin-left: -0.5em; }
Each overlay is set with a fixed position, and is visibly hidden by default.
With Startup App and Slides App you can build unlimited websites using the online website editor which includes ready-made designed and coded elements, templates and themes.
Try Startup App Try Slides AppOther Products/* Overlay */ .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; overflow: auto; z-index: 9999; visibility: hidden; } .close { position: absolute; top: 50px; right: 50px; font-size: 36px; }
An additional class to hide overflow on body
, you’ll understand its purpose later.
.noscroll { overflow: hidden; height: 100%; width: 100%; }
CSS3 Animations
Active States
This is the heart of our experience, we define one-time animations on a 1.6 second timescale for all needed elements of our page:
- Each button when clicked
- Each button when an other one is clicked
- Overlay related to the clicked button
When moving an element with CSS animation
or even transition
, always use translate3d
so your users will take advantage of their device’s hardware acceleration. If you move some elements with left/right and absolute positioning instead, you will probably encounter performance losses.
Let’s start with animations we add to buttons when clicked. Each button moves two times with translate3d
, then the icon inside disappears while the button scales up.
@keyframes fx-el_topleft-active { 0% {} 16% { transform: translate3d(-27.5%, -27.5%, 0); } 50% { transform: translate3d(55.1%, 55.1%, 0) scale(1); color: #FFF;} 62% { color: transparent; } 100% { transform: translate3d(55.1%, 55.1%, 0) scale(20); color: transparent; } } @keyframes fx-el_topright-active { 0% {} 16% { transform: translate3d(27.5%, -27.5%, 0); } 50% { transform: translate3d(-55.1%, 55.1%, 0) scale(1); color: #FFF;} 62% { color: transparent; } 100% { transform: translate3d(-55.1%, 55.1%, 0) scale(20); color: transparent; } } @keyframes fx-el_btmleft-active { 0% {} 16% { transform: translate3d(-27.5%, 27.5%, 0); } 50% { transform: translate3d(55.1%, -55.1%, 0) scale(1); color: #FFF;} 62% { color: transparent; } 100% { transform: translate3d(55.1%, -55.1%, 0) scale(20); color: transparent;} } @keyframes fx-el_btmright-active { 0% {} 16% { transform: translate3d(27.5%, 27.5%, 0); } 50% { transform: translate3d(-55.1%, -55.1%, 0) scale(1); color: #FFF;} 62% { color: transparent; } 100% { transform: translate3d(-55.1%, -55.1%, 0) scale(20); color: transparent; } } #el-topleft.active { animation: fx-el_topleft-active 1.6s 1 ease-in-out; } #el-topright.active { animation: fx-el_topright-active 1.6s 1 ease-in-out; } #el-btmleft.active { animation: fx-el_btmleft-active 1.6s 1 ease-in-out; } #el-btmright.active { animation: fx-el_btmright-active 1.6s 1 ease-in-out; }
We follow with inactive button animations. They are moving the same way before ultimately disappearing with opacity: 0
.
@keyframes fx-el_topleft-inactive { 0% { transform: translate3d(0%, 0%, 0); opacity: 1; } 16% { transform: translate3d(-27.5%, -27.5%, 0);opacity: 1; } 40% { opacity: 0;} 50% { transform: translate3d(55.1%, 55.1%, 0); } 100% { transform: translate3d(55.1%, 55.1%, 0); opacity: 0; } } @keyframes fx-el_topright-inactive { 0% { transform: translate3d(0%, 0%, 0); opacity: 1; } 16% { transform: translate3d(27.5%, -27.5%, 0); opacity: 1; } 40% { opacity: 0;} 50% { transform: translate3d(-55.1%, 55.1%, 0); } 100% { transform: translate3d(-55.1%, 55.1%, 0); opacity: 0; } } @keyframes fx-el_btmleft-inactive { 0% { transform: translate3d(0%, 0%, 0); opacity: 1; } 16% { transform: translate3d(-27.5%, 27.5%, 0); opacity: 1; } 40% { opacity: 0;} 50% { transform: translate3d(55.1%, -55.1%, 0); } 100% { transform: translate3d(55.1%, -55.1%, 0); opacity: 0; } } @keyframes fx-el_btmright-inactive { 0% { transform: translate3d(0%, 0%, 0); opacity: 1; } 16% { transform: translate3d(27.5%, 27.5%, 0); opacity: 1; } 40% { opacity: 0;} 50% { transform: translate3d(-55.1%, -55.1%, 0); } 100% { transform: translate3d(-55.1%, -55.1%, 0); opacity: 0; } } #el-topleft.inactive { animation: fx-el_topleft-inactive 1.6s 1 ease-in-out; } #el-topright.inactive { animation: fx-el_topright-inactive 1.6s 1 ease-in-out; } #el-btmleft.inactive { animation: fx-el_btmleft-inactive 1.6s 1 ease-in-out; } #el-btmright.inactive { animation: fx-el_btmright-inactive 1.6s 1 ease-in-out; }
Then the overlay, easy to understand.
@keyframes fx-overlay { 0% { visibility: visible; color: transparent; } 50% { background-color: #107360; color: white; } 100% { visibility: visible; background-color: #107360; color: #FFF; } } .overlay.active { animation: fx-overlay .8s 1.25s 1 ease-in-out forwards; }
We delay the overlay’s animation to 1.25 seconds, to let its content appear at the end of our button animations. We are also using forwards
, an additional parameter, which will apply the property values for the time the animation ended (in this case, keep the overlay visible).
Active_reverse States
At this point, we defined how our buttons and their respective overlays will be animated when clicking on a button
element. Now when a user clicks on the .close
icon inside the current overlay, we will repeat the same process in reverse. The current overlay will disappear and the menu will go back to its initial position.
If you read the CSS animation
specifications, you may be thinking about defining .active_reverse
classes, using previous animations with an extra reverse
parameter. reverse
allows us to start a keyframe from the end (100%) to the start (0%). The problem is that we would re-use an animation that should happen once (remember our “1” next to the “1.6s”) and that has already happened. Sadly, this would not produce any result.
To work around this problem, define new keyframes
and simply reverse previous rules within.
Active button reverse animation:
@keyframes fx-el_topleft-active_reverse { 0% { transform: translate3d(55.1%, 55.1%, 0) scale(20); color: transparent; background-color: #107360; } 38% { color: transparent;} 50% { transform: translate3d(55.1%, 55.1%, 0) scale(1); color: #FFF; background-color: #107360; } 82% { transform: translate3d(-27.5%, -27.5%, 0); background-color: #16a085;} 100% { transform: translate3d(0%, 0%, 0); } } @keyframes fx-el_topright-active_reverse { 0% { transform: translate3d(-55.1%, 55.1%, 0) scale(20); color: transparent; background-color: #107360; } 38% { color: transparent; } 50% { transform: translate3d(-55.1%, 55.1%, 0) scale(1); color: #FFF; background-color: #107360;} 82% { transform: translate3d(27.5%, -27.5%, 0); background-color: #16a085;} 100% { transform: translate3d(0%, 0%, 0); } } @keyframes fx-el_btmleft-active_reverse { 0% { transform: translate3d(55.1%, -55.1%, 0) scale(20); color: transparent; background-color: #107360; } 38% { color: transparent; } 50% { transform: translate3d(55.1%, -55.1%, 0) scale(1); color: #FFF; background-color: #107360;} 82% { transform: translate3d(-27.5%, 27.5%, 0); background-color: #16a085;} 100% { transform: translate3d(0%, 0%, 0); } } @keyframes fx-el_btmright-active_reverse { 0% { transform: translate3d(-55.1%, -55.1%, 0) scale(20); color: transparent; background-color: #107360; } 38% { color: transparent; } 50% { transform: translate3d(-55.1%, -55.1%, 0) scale(1); color: #FFF; background-color: #107360;} 82% { transform: translate3d(27.5%, 27.5%, 0); background-color: #16a085;} 100% { transform: translate3d(0%, 0%, 0); } } #el-topleft.active_reverse { animation: fx-el_topleft-active_reverse 1.6s 1 ease-in-out; } #el-topright.active_reverse { animation: fx-el_topright-active_reverse 1.6s 1 ease-in-out; } #el-btmleft.active_reverse { animation: fx-el_btmleft-active_reverse 1.6s 1 ease-in-out; } #el-btmright.active_reverse { animation: fx-el_btmright-active_reverse 1.6s 1 ease-in-out; }
Inactive button reverse animation:
@keyframes fx-el_topleft-inactive_reverse { 0% { transform: translate3d(55.1%, 55.1%, 0); opacity: 0; } 50% { transform: translate3d(55.1%, 55.1%, 0); } 82% { transform: translate3d(-27.5%, -27.5%, 0); } 45% { opacity: 0; } 100% { transform: translate3d(0%, 0%, 0); opacity: 1; } } @keyframes fx-el_topright-inactive_reverse { 0% { transform: translate3d(-55.1%, 55.1%, 0); opacity: 0; } 50% { transform: translate3d(-55.1%, 55.1%, 0); } 82% { transform: translate3d(27.5%, -27.5%, 0); } 45% { opacity: 0; } 100% { transform: translate3d(0%, 0%, 0);opacity: 1; } } @keyframes fx-el_btmleft-inactive_reverse { 0% { transform: translate3d(55.1%, -55.1%, 0); opacity: 0; } 50% { transform: translate3d(55.1%, -55.1%, 0); } 82% { transform: translate3d(-27.5%, 27.5%, 0); } 45% { opacity: 0; } 100% { transform: translate3d(0%, 0%, 0);opacity: 1; } } @keyframes fx-el_btmright-inactive_reverse { 0% { transform: translate3d(-55.1%, -55.1%, 0); opacity: 0; } 50% { transform: translate3d(-55.1%, -55.1%, 0); } 82% { transform: translate3d(27.5%, 27.5%, 0); } 45% { opacity: 0; } 100% { transform: translate3d(0%, 0%, 0); opacity: 1; } } #el-topleft.inactive_reverse { animation: fx-el_topleft-inactive_reverse 1.6s 1 ease-in-out; } #el-topright.inactive_reverse { animation: fx-el_topright-inactive_reverse 1.6s 1 ease-in-out; } #el-btmleft.inactive_reverse { animation: fx-el_btmleft-inactive_reverse 1.6s 1 ease-in-out; } #el-btmright.inactive_reverse { animation: fx-el_btmright-inactive_reverse 1.6s 1 ease-in-out; }
Overlay reverse animation:
@keyframes fx-overlay-reverse { 0% { visibility: visible; background-color: #107360; color: #FFF;} 40% { background-color: #107360; color: transparent;} 85% {background-color: transparent; } 100% {visibility: invisible; color: transparent; background-color: transparent; } } .overlay.active_reverse { animation: fx-overlay-reverse .8s 1 ease-in backwards; }
Backwards
is similar to forwards
as the animation will apply the values you defined in the first keyframe as soon as it is applied to the target.
The Javascript
All needed components are now set up and ready to use. We just need to add our previous CSS classes for defined events. I used jQuery for that but you can do it with pure javascript to keep it light. On a button
click event, we add .active
classes on the current button
and its related .overlay
, then .inactive
classes on the other buttons using siblings()
. We also hide all overflow on body
produced by our scaled up button with the .noscroll
class. When closing the overlay, we repeat the same process using our *_reverse
classes which brings back users to the default menu, then remove .noscroll
class on body
.
To link each button with its overlay, we simply store the data-id
value in a variable, then add the .active
class on the overlay with a corresponding id.
Did you know that an event is fired each time an animation start or complete? We will listen to animationend
event to remove the .noscroll
class on body, and .inactive
classes on button
elements. This prevents scrollbar from appearing when a button
scales from 20x to 1x and becomes temporarily larger than the body size, creating something messy in the browser. It also prevents a button
from being clicked when the menu isn’t in its initial state.
var AnimEnd = 'animationend webkitAnimationEnd mozAnimationEnd MSAnimationEnd oAnimationEnd'; var nav = $('.nav'); var navButton = $('.nav-el'); var overlay = $('.overlay'); $(navButton).click(function() { /* Remove old previous classes */ $(navButton).removeClass('inactive_reverse active_reverse'); $(overlay).removeClass('active active_reverse'); /* Add classes on menu elements */ $(this).siblings().addClass('inactive'); $(this).addClass('active'); /* Activate related overlay */ var o_target = $(this).data('id'); $('#'+o_target).addClass('active'); /* Prevent scrolling */ $('body').addClass('noscroll'); }); $('.close').click(function(){ /* Remove old classes and add new ones on menu elements */ $('.active', nav).removeClass('active').addClass('active_reverse'); $('.inactive', nav).addClass('inactive_reverse'); /* Bring back our overlay to its default state */ $(this).parent().addClass('active_reverse'); /* Remove .noscroll and .inactive when reverse animation is finished */ $('.inactive_reverse').bind(AnimEnd, function(){ $('body').removeClass('noscroll'); $(navButton).removeClass('inactive'); $('.inactive_reverse').unbind(AnimEnd); }); });
That’s all. You can re-use this process in a lot of different ways, with your own layouts and other kinds of effects. So be creative!
Notes
- Icons used in the demo: Linecons Free