Skip to content

An Introduction to CSS Animations

Introduction

Pseudo elements are an extremely useful tool for web development. I want to share how I used the ::before and ::after elements to style some button/links with a sliding animation.

If you want to take a look at how this all works out, take a look at this codepen. Embedded content: https://codepen.io/adamjberkowitz/pen/oJrOZz

But first, I think it would be useful to define what these elements do and how they work.

::before and ::after

I think about pseudo-elements as extra helpers you can use to add styles or additional content to your markup. Let's imagine a link.

<a href="#">
  I'm a link!
</a>

When the markup is rendered, what you would see is the text of the link. But what if you want your link to look like this?

Link style button link Hover/focus link style button link hovered

Your first thought might be to make the link a rectangle with padding and then set the background color to your choice. Then you might think - "ok... now I need to slide the background from left to right, and make it look like the border has expanded across." That's exactly what I thought at first! Then I figured out it was ridiculously difficult if not impossible. That's when I turned to the pseudo-elements.

When you declare them in css like this...

a::before { /* styles here... */ }
a::after { /* styles here... */ }

the markup ends up being like this...

<a href="#">
  ::before
  I'm a link!
  ::after
</a>

Amazing! What? You don't think so? Trust me it is. This solves the whole problem for us.

Preparing the link

First things first... I want to style the link to take best advantage of the pseudo-elements. The link needs to have:

  • A thick left hand border with a contrasting color
  • A rectangular shape
  • No underline below the text
  • Text centered in the styled element
  • White text to contrast the dark background

Based on those requirements I styled the link like this:

a {
  border-left: 6px solid red;
  color: white;
  padding: 1em;
  text-align: center;
  text-decoration: none;
}

Now I can move on to styling the pseudo-elements.

Styling pseudo-elements

The first thing to understand about pseudo-elements is that they're only created when they're declared in your stylesheet. The second thing to understand is that they always require a content property to be available in the browser window. Even if the value of the property is an empty string, it needs to be declared.

a { /* no pseudo-elements here... */ }

a::before { /* no content property? no element! */ }

a::before {
  /*  Pseudo-element ready to go! */
  content: '';
  /* more styles go here... */
}

Once that content property exists, we can work towards solving the problems.

Positioning

The goal is to make the ::before and ::after elements appear like a background to the link. In order to do that, they need to take up the entire visual space of the link. But, I don't want to explicitly declare the width and height properties. It's important to me that the elements can have different sizes (or shapes...) but still work.

Using position: absolute on the ::before and ::after elements will help with that. Because they're children of the anchor tag, I can add position: relative to it so that the pseudo-elements styles are constrained by the size and shape of the anchor.

a {
  /* other styles... */
  position: relative;
}

/*
  Both elements take up the full
  area of the link.
*/
a::before,
a::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Backgrounds

Now that each element is positioned, you can add background colors to them.

a::before {
  background-color: darkblue;
}

a::after {
  background-color: red;
}

Stacking

Oh no! All I can see is a red rectangle! What happened!?

Don't worry! We've just seen the effects of a cool thing called the stacking context.

Usually when we think about web pages, we think about things being next to each other in 2 dimensions like on a sheet of paper. In reality, elements can also be arranged so that one appears in front or behind another in a third dimension. There are a bunch of rules for how this works, but the one we need to be concerned with at the moment is this:

A stacking context is formed, anywhere in the document, by any element in the following scenarios... Element with a position value "absolute" or "relative" and z-index value other than "auto".

The z-index is the property that sets the "behind" and "in front of" order of positioned elements. Most often this property is set with positive numbers which pushes one element in front of another. But, it can also be set with negative numbers to push an element behind.

I know that what I want to see in the "default" state of the link is the blue background and on hover or focus a red background. In that case all that needs to happen is that

  • the ::before element needs a lower z-index than the link tag
  • the ::after element needs a lower z-index than the ::before tag.

No problem!

a::before {
  background-color: darkblue;
  z-index: -1;
}

a::after {
  background-color: red;
  z-index: -2;
}

You should now be able to see the white text on top what appears to be a blue background.

Sliding

The last thing that needs to happen is that the red border on the left needs to appear to expand when the link is hovered or focused. To do that, you can use CSS transition property which allows you "to control the animation speed when changing CSS properties". There are two tricks here

  • One is to figure out what property to transition.
  • The other is to remember which state (e.g. hovered/non-hovered) the transition property gets placed on.

The transition property can take a lot of values but right now I only need three.

  • Which value to transition
  • How long should the transition should take
  • The "shape" of the transition (does it start slow and end quickly etc...)

There's one other thing to consider which is

  • When should the transition happen?

Let's take the first one first.

You might think you want to transition the red background color. But that won't work because it's behind the blue in the stacking context. What you want to do is push the left edge of the blue all the way to the right side of the link.

Next, the transition needs to go fairly quickly so definitely under a second. I think 0.3 seconds is good for this.

The last value is the hardest to understand because it describes a timing curve. Since the transition is so quick the value ease-in-out works fine. Now it's time to apply these ideas to the CSS.

Finally, we want to let the browser know that in advance that the left value will change. Fortunately, there's a will-change property! This property does three things.

  • It defines the values that will change and gives the browser a heads up about them.
  • It creates a new graphical layer for the browser.
  • It moves the animation in that layer off to the GPU to reduce the load on the browser itself.
a::before {
  background-color: darkblue;
  transition: left 0.3s ease-in-out;
  will-change: left;
  z-index: -1;
}

a:hover::before,
a:focus::before {
  left: 100%;
}

That's it!

Now, the whole style for the links looks like this

/* Style the basic link */
a {
  border-left: 6px solid red;
  color: white;
  padding: 1em;
  postion: relative;
  text-align: center;
  text-decoration: none;
}

/* Position the pseudo-elements and set content */
a::before,
a::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

/* Style the ::before element */
a::before {
  background-color: darkblue;
  transition: left 0.3s ease-in-out;
  z-index: -1;
}

/* Style the ::after element */
a::after {
  background-color: red;
  z-index: -2;
}

/* Style the hover/focus effect */
a:hover::before,
a:focus::before {
  left: 100%;
}

Conclusion

I think this is a really nice demonstration of how nicely CSS properties can be combined to create a simple visual effect. Instead of adding additional markup to the link itself, using the ::before and ::after pseudo-elements allows for additional flexibility when planning out styles.