skip navigation

Animation

Animation in CSS can be done with transitions and transforms OR using the @keyframes rule. This page is only concerned with the latter.

Animations also need a trigger to start them. Typical ways to trigger an animation are using some simple Javascript, CSS :hover pseudo class, or (as in the case of this page) the page load itself. Another way is to use the :target pseudo class

Getting Started

There are two basic steps in CSS animations. First you write and name your animation. Secondly you add it an element.

To write an animation in the CSS you use the @keyframes declaration and right after that you write the name you want to give it. I made up the name headingSize:

@keyframes heading-size {
  0% {
    transform: scale(0.1);
    opacity: 0%;
  }
  60% {
    transform: scale(1.8);
    opacity: 80%;
  }
  100% {
    transform: scale(1);
    opacity: 100%;
  }
}

Instead of using percentage values you can use the from and to keywords where from is the same as 0% and to is 100%:

@keyframes heading-size {
  from {
    scale: 0.1;
  }
  to {
    scale: 1;
  }
}

Also it’s not necessary to have a start and end definition for example:

@keyframes bg-color {
  50% {
    background-color: red;
  }
}
Changing colours..

The second thing is you need to write that animation into whichever element/s you wish to animate.

For instance to write the animation I named headingSize above into any h1 headers:

h1 {
      animation: headingSize 7s;
      animation-iteration-count: 3;
      animation-direction: alternate;
    }

Iterations

The iteration-count is how many times the animation plays. The default is 1, so the animation plays just once. You can set it to infinite if you want it to carry on indefinitely.

Animation direction

The animation-direction can have the following values:

  • normal
  • reverse
  • alternate
  • alternate-reverse

The value of reverse play it backwards.

alternate means it first goes forward then backwards. So it begins an 0% through the timeline plays to 100%, then plays from 100% back to 0%. This is very useful for any animation that plays more than once and as different start and end points.

alternate-reverse is the same as alternate except that it begins by playing backwards.

Animation timing

The rate of change does not have to be constant. The animation-timing-function takes the following values:

  • ease (the default) a slow start, then fast, before it ends slowly
  • ease-in slow start
  • ease-out slow finish
  • ease-in-out a slow start and finish
  • linear same speed throughout
  • steps() the animation unfolds in discrete steps. The number of steps is set in the brackets eg. steps(12). A second optional parameter uses key terms jump-start, jump-end, jump-none, jump-both, step-start and step-end. The jump-start and jump-end can be abbreviated to just start and end.
  • cubic-bezier(n, n, n, n) the possible values are from 0 to 1.

See cubic-bezier.com to understand and set up a cubic bezier function. It’s easier to see and play with than explain in words.

steps()

I couldn’t find what the default jump is but from below it appears to be jump-end.

All the following use transform: scaleX() from an initial setting of 0 then up to 1.

with just steps(6)

nowt

with steps(6, jump-start)

with steps(6, jump-end)

with steps(6, jump-both)

Animation delay

Delaying the animation is easy with the property animation-delay:

animation-delay: 5s

Fill Mode

animation-fill-mode is not the most intuitive name. The values in an animation only persist while the animation is running. Once it stops or before it starts the keyframe values are ignored. Changing this is what animation-fill-mode does. The default value, normal, is that an animation will not affect the styles before or after an animation.

This can be changed:

The possible values are:

  • normal - the default (above)
  • forwards - when the animation is finished it stops retaining the styles set in the final keyframe.
  • backwards - before the animation starts (during the delay stage) the styles of the initial keyframe are applied
  • both - retains both

For further info on this see the explantion by Josh Comeau.

Play State

animation-play-state is simply whether the animation is playing or paused. There are two values:

animation-play-state: playing

animation-play-state: paused

You might leave an animation in the paused state ready when someone hovers over it. Alternatively you might want the hover state to pause a running animation.

The shorthand

All these properties can go into the shorthand animation

The order doesn’t matter except for animation-duration and animation-delay with both have the same values (seconds). The animation-delay MUST come after 'animation-duraction in the list:

  1. animation-name - the name given in @keyframes declaration
  2. animation-duration - in seconds
  3. animation-timing-function - ease, ease-in, ease-out, ease-in-out, linear etc.
  4. animation-delay - in seconds
  5. animation-iteration-count - how many times it plays, whole numbers or ‘infinite’
  6. animation-direction - normal, reverse, alternate, alternate-reverse
  7. animation-fill-mode - normal, forwards, backwards, both
  8. animation-play-state - playing (default) or paused

Finally you can add multiple animations to one selector simply splitting them with a commer:

div.move {
  animation: bounceIn .5s, rotate 5s;
}

What can go into an animation?

The @keyframes declaration allows the different set points to be declared separately at different points through the animation. These can be defined in either percentages or using from and to. The key word from is the same as 0% and to is the same as 100%.

@keyframes new-animation {
  from {
    left: 0px;
  }
  to {
    left: 350px;
  }
}

See the Pen This Pen by Synphod (@Synphod) on CodePen.

Prefers reduced motion

Not everyone likes too much animation and it can give some people headaches and nausea. Fortunately they can set their machines to prefers-reduced-motion. But this only works if the develper has incorporated this into the code. The works like other media queries by checking if a user has this turned on.

There are several ways this can be accomplished in CSS but the simplest is probably to wrap potentially offending animations in a media query like so:

@media (prefers-reduced-motion: no-prefereence) {
  .some-div {
    animation: enlarge 1s infinite alternate;
  }
}

You can test whether this is working in Chrome dev tools using the emulation.

The prefers-reduce-motion takes two possible values: no-prefereence and reduce.

If set to reduce you can simply write:

@media (prefers-reduced-motion) {
  /* code */
}
View the CSS for this page

@keyframes bodybg {
  0% {
    background: var(--MainBorderColor);
  }
  100% {
    background: var(--container-bg)
  }
}

.container {
  animation: bodybg 5s;
  animation-delay: 5s;
  animation-fill-mode: backwards;
}

@keyframes h1 {
   0% {
    transform: scale(0.1);
    opacity: 0%;
  }
  60% {
    transform: scale(2.8);
    opacity: 80%;
  }
  100% {
    transform: scale(1);
    opacity: 100%;
  }
}

h1 {
  animation: h1 7s;
  animation-iteration-count: 1;
  animation-direction: alternate;
  animation-delay: 6s;
  animation-fill-mode: backwards;
}

h2 {
  animation: flipInY 3s;
  animation-iteration-count: 2;
}
.resources {
  animation: lightSpeedIn 3s;
}
header, nav {
  animation: fadeInDownBig 2s;
}
code {
  animation: bounceInRight 6s;
}

@keyframes expand {
    to {
        transform: scaleX(1);
    }
}

.steps {
    height: 2em;
    position: relative;
    margin-bottom: 0.5em;
    background-color: #1113;
}

.steps::after {
    content: '';
    display: block;
    position: absolute;
    inset: 0;
    transform: scaleX(0);
    transform-origin: left;
    animation: expand 10s infinite both;
    animation-timing-function: steps(6);
    background-color: #26b;
}

.steps.jstart::after {
    animation-timing-function: steps(6, jump-start);
}

.steps.jend::after {
    animation-timing-function: steps(6, jump-end);
}

.steps.jboth::after {
    animation-timing-function: steps(6, jump-both);
}

.steps-p {
    margin-bottom: 0.1em;
}

@keyframes change-bg {
  50% {
    background-color: red;
  }
}
.color-swap {
  width: min(100%, 200px);
  aspect-ratio: 5 / 3;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  animation: change-bg 6s linear infinite;
  font-weight: bold;
  color: #fff9;
  background-color: rgb(0, 98, 255);
}