https://goo.gl/API3rP

Table of Contents

  1. Introduction
  2. Morphing the burger menu
  3. CSS Transitions
    1. Transition delay
  4. Sliding the sidebar
  5. CSS Animations
  6. JavaScript Events

Introduction

This tutorial will show some of the capabilities and applications of CSS Transitions and CSS Animations.

To introduce Transitions and Animations we will use this sidebar project. For now look only at the result:

See the Pen CSS sidebar toggle by Jorge C. S. Cardoso (@jorgecardoso) on CodePen.

This example has a number of characteristics where we will apply Transitions and Animations:

  • The burger menu morphs into a cross when clicked
  • The burger menu slides in from the left, one layer at a time
  • The sidebar slides in from the left when we click the burger menu and its color changes after the slide in has finished.
  • When the page is loaded, the contents only appear after the initial burger menu finishes sliding in from the left
  • When an item from the sidebar menu is clicked, the respective content is only displayed after the sidebar has finished sliding to the left.

Lets look at each of these in turn.

Morphing the burger menu

The burger menu is actually an input checkbox with a label. This allows us to know the state of the sidebar: a checked input means the sidebar is visible, an unchecked input means the sidebar is hidden. Clicking on the label of the input, checks and unchecks the input so we can actually hide the checkbox and show and style only the label for the input:

  <input type="checkbox" id="menuToggler" class="input-toggler" />
<label for="menuToggler" class="menu-toggler">
</label>

  :root {
  --toggler-size: 40px;
}

.input-toggler {
  display: none;
}

.menu-toggler {
  display: block;
  width: var(--toggler-size);
  height: var(--toggler-size);
  position: fixed;
  top: 10px;
  left: 10px;
  cursor: pointer;
}

The burger can be made with spans with a height of one fifth of the total height of the burger, and spaced vertically.

  <label for="menuToggler" class="menu-toggler">
    <span class="menu-toggler__line"></span>
    <span class="menu-toggler__line"></span>
    <span class="menu-toggler__line"></span>
  </label>

  .menu-toggler__line {
  height: calc(var(--toggler-size) / 5);
  background: Tomato;
  position: absolute;
  left: 0;
  right: 0;
}

.menu-toggler__line:nth-child(2) {
  top: calc(var(--toggler-size) / 5 * 2);
}

.menu-toggler__line:nth-child(3) {
  top: calc(var(--toggler-size) / 5 * 4);
}

To morph them into a cross, we just need to rotate the top and bottom one (their vertical position also needs to be adjusted to make a perfect cross), and hide the middle one:

  .input-toggler:checked ~ .menu-toggler .menu-toggler__line:nth-child(1) {
  transform: translateY(calc(var(--toggler-size) / 5 * 2)) rotate(45deg);
}

.input-toggler:checked ~ .menu-toggler .menu-toggler__line:nth-child(2) {
  opacity: 0;
}

.input-toggler:checked ~ .menu-toggler .menu-toggler__line:nth-child(3) {
  transform: translateY(calc(var(--toggler-size) / 5 * -2)) rotate(-45deg);
}

This results in an instantaneous "morph" of the burger into a cross:

See the Pen Burger menu morph - no transitions by Jorge C. S. Cardoso (@jorgecardoso) on CodePen.

CSS Transitions

In the previous example, the cross is made by instantaneously changing the transform property from a zero rotation to a 45ยบ rotation.

CSS Transitions allow us to say that the values of specific CSS properties should not be changed instantaneously, but over time.

We can specify which property we want the transition to affect, how long the transition should take when the property value is changed, easing function and the delay.

In our burger example, we can just change the .menu-toggler__line class and say that we want to apply a transition on the transform and opacity properties, with a duration of .5s and .25s respectively:

  .menu-toggler__line {
  height: calc(var(--toggler-size) / 5);
  background: Tomato;
  position: absolute;
  left: 0;
  right: 0;
  transition-property: transform, opacity;
  transition-duration: .5s, .25s;
}

See the Pen Burger menu morph by Jorge C. S. Cardoso (@jorgecardoso) on CodePen.

Transition delay

Transitions can also have a delay which means that they will not start immediately. For example, we could delay the transition of the rotations to start only after the middle span had completely faded away:

  .menu-toggler__line {
  height: calc(var(--toggler-size) / 5);
  background: Tomato;
  position: absolute;
  left: 0;
  right: 0;
  transition-property: transform, opacity;
  transition-duration: .5s, .25s;
  transition-delay: .25s, 0s;
}

See the Pen Burger menu morph - delay by Jorge C. S. Cardoso (@jorgecardoso) on CodePen.

Notice how this caused an issue. The delay is applied every time that property changes so it will also be applied when we "uncheck" the input.

The way to solve this is to create two rules: one that applies to the checked state and another to apply to the unchecked state. The more specific one will be applied when transitioning from unchecked->checked, and the more general one will be applied when transitioning from checked->unckecked:

  .menu-toggler__line {
  height: calc(var(--toggler-size) / 5);
  background: Tomato;
  position: absolute;
  left: 0;
  right: 0;
  transition-property: transform, opacity;
  transition-duration: .5s, .25s;
  transition-delay: 0s, .5s;
}

.input-toggler:checked ~ .menu-toggler .menu-toggler__line {
  transition-delay: .25s, 0s;
}

See the Pen Burger menu morph - delay solved by Jorge C. S. Cardoso (@jorgecardoso) on CodePen.

Sliding the sidebar

Sliding the sidebar is now easy to do with transitions. The sidebar is initally positioned outside the window with a transform translation in the X axis of minus its width. When the input is checked, we apply a transform translation of zero:

  .sidebar {
  width: var(--sidebar-width);
  transform: translateX(calc(var(--sidebar-width) * -1));
  background: linear-gradient(to bottom right, Tomato, Wheat);
  background-size: 400% 400%;
  background-position: 90% 90%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  top: 0;
  bottom: 0;
  opacity: .5;
  transition: transform .6s, background-position 1s .6s;
}

.input-toggler:checked ~ .sidebar {
  opacity: .9;
  transform: translateX(0);
  background-position: 0 0;
}

Notice the transition on the transform and background-position. The transition on the background-position has a delay of .6s which causes a slight shift in the background color of the sidebar after it has slided in.

CSS Animations

CSS Animations allow us to define complex frame-based animations that can run continuously or for a predefined number of iterations. Unlike Transitions, Animations do not run in response to a property changing. Animations are applied and run immediately when the CSS class they are defined in is assigned to an element.

See the Pen CSS sidebar toggle - burger slide in by Jorge C. S. Cardoso (@jorgecardoso) on CodePen.

Let's turn to the burger menu slide in animation to see how animations work. In this example, I've removed everyelse not needed to make the slide in animation on the burger menu work. In the .menu-toggler__line class you can see four animation-related properties being used:

  animation-name: slidein;
animation-duration: .5s;
animation-iteration-count: 1;
animation-timing-function: ease-out;

The animation-name refers to a @keyframes rule further down which defines the contents of the animation. animation-duration is obviously the duration we want for the animation in this element. animation-iteration-count is the number of times we want the animation to run (go ahead and try other values - you can also try infinite). animation-timing-function is the easing function to apply to the animation.

The animation itself is defined in a @keyframes rule:

  @keyframes slidein {
  from {
    left: -200%;
    right: 200%;
  }
  to {
    left: 0;
    right: 0;
  }
}

The name "slidein" is anything we choose. We can then define the starting point of the animation in a from keyframe selector, and the ending point in a to selector. Keyframe selector can also be percentages, and we can have as many as we want. The keyframe selectors define the css properties values we want to apply in that moment of the animation.

In our example, we are saying the the starting point is outside of the window, to the left.

Because we want the three burger slices to be slightly off-phase, we define different animation durations for the second and third slices:

  .menu-toggler__line:nth-child(2) {
  top: calc(var(--toggler-size) / 5 * 2);
  animation-duration: .6s;
}

.menu-toggler__line:nth-child(3) {
  top: calc(var(--toggler-size) / 5 * 4);
  animation-duration: .7s;
}

JavaScript Events

Although most of the times you probably will not need this, it is possible to listen to animation and transition events in JavaScript. In our example, I have used this in two ways:

  1. To show the content only after the burger menu slides in from the left
  2. To show the content associated with a given menu item, only after the sidebar slides out to the left

To do the first, we just need to get the lower slice of the burger, because that is the last one to appear and add an event listener to it. The event we want is animationend:

  // Hide the contents until the burger menu finishes sliding in from the left
var content = document.getElementsByTagName("main")[0];
content.style.visibility = "hidden";

var lowerLayerBurger = document.getElementsByClassName("menu-toggler__line")[2];
lowerLayerBurger.addEventListener("animationend", function(evt) {
  content.style.visibility = "visible";
});

To do the second one, we need to attach a click handler to the menu (ul element) and attach a transitionend event handler to the sidebar:

  // Add click listeners to the menu on the sidebar
document.getElementsByTagName("ul")[0].addEventListener("click", function(evt) {
  // when a menu item is clicked hide the sidebar by unchecking the input
  document.getElementById("menuToggler").checked = false;

  function handleTransitionEnd(transitionEvent) {
    // show the correct content based on the target of the menu item
    // first hide everything
    var contentDivs = document.querySelectorAll("main div");
    for (var i = 0; i < contentDivs.length; i++) {
      contentDivs[i].style.display = "none";
    }

    // show the correct div
    var contentId = evt.srcElement.hash;
    var contentDiv = document.getElementById(contentId.substr(1));
    contentDiv.style.display = "inherit";

    // remove listener
    sidebar.removeEventListener("transitionend", handleTransitionEnd);
  }

  sidebar.addEventListener("transitionend", handleTransitionEnd);
});

We need to be carefull with transition events because an element may have many transitions in effect and they all will trigger the event. In our case, this is solved by removing the event handler at the end of the handler function. This means that the first transition event will be handled, but all other possible transition events on that element will not - becaused the handler has since been removed.