Building Animations With Pseudo-Elements

Simon Steer
Oct 23, 2017 · 6 min read

A quick intro to ::before and ::after elements

Every element possesses a ::before and ::after pseudo-element, with the exception being elements that are self-closing. They live inside of the element; the ::before pseudo-element belongs immediately inside the opening tag before all of the enclosed content, and the ::after pseudo-element belongs immediately before the closing tag, after all of the enclosed content. Consider the following code:

<style>

h1 {
color: coral;
}

h1::before {
content: '';
width: 15px;
height: 15px;
margin: 10px;
float:left;
background: cornflowerblue;
}

h1::after {
content: 'some content';
color: white;
background: black;
margin-left: 10px;
}

</style>
<h1>pseudo-elements are awesome!</h1>

This will yield the following:

The ::before element appears before the content of the h1 tag, and “some content” appears after the content. You may have also noticed that both the ::before and ::after styling make use of the content attribute. For these pseudo-elements to display in the DOM, you will need to set this attribute to content: '';. in our example, the ::after element has a content value of 'some content'. As you may have guessed, you can put text between the ticks and it will appear as the content inside the pseudo-element! The only catch is you can’t put markup inside the content attribute — it will show up as plain text.

Building animations with pseudo-elements

What makes ::before and ::after elements so powerful, is that instead of being able to manipulate and animate only the parent element, we now have access to two additional elements which don’t affect our semantic markup, and can make our animation more exciting and complex. Combining three separate animatable elements and manipulating their z-indexes, we can build some very cool animations. Let’s say we want to build an animation that looks like a profile view of one sphere orbiting around another. We can start by building the parent element:

.one {
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}

Set the width and the height of the element, and set display: flex, and its other flex properties to justify-content: center; and align-items: center;

You’ll notice that I didn’t give .one a background color. This is because we know that in our animation, one element will have to go behind another, and neither a ::before nor a ::after element can go behind its parent element.

Next we will style .one::before and .one::after:

.one::before {
content: '';
width: 10px;
height: 10px;
background-color: lightcoral;
border-radius: 50%;
}
.one::after {
content: '';
width: 100%;
height: 100%;
background: cadetblue;
border-radius: 50%;
}

.one::before will be our smaller sphere, which will be rotating .one::after, the larger sphere. Here is what our markup and styling currently looks like:

<style>.one {
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}

.one::before {
content: '';
width: 10px;
height: 10px;
background-color: lightcoral;
border-radius: 50%;
}

.one::after {
content: '';
width: 100%;
height: 100%;
background: cadetblue;
border-radius: 50%;
}
</style><div class="one"></div>

This currently yields:

You’ll notice that we can only see .one::after. This is because .one::before has a lower z-index. This actually works in our favour, as we want .one::before to start its orbit behind .one::after, so when we hover over .one, a circle doesn’t appear from nowhere.

Next, let’s write some code for the keyframes of the animation. Let’s start by oscillating .box::before between its two outer orbit points We can use translate: (value, value); to move our element relative to where it is positioned via flexbox. We will also write some code that tells the browser to execute the animation on hover:

@keyframes oneBefore {
0% {
transform: translateX(0);
}
25% {
transform: translate(-100px, 0);
}
50% {
transform: translateX(0);
}
75% {
transform: translate(100px, 0);
}
100% {
transform: translateX(0);
}
}
.one:hover::before {
animation: oneBefore 1.5s infinite;
}

This yields:

Next, let’s manipulate the z-index of .box::before and .one::after so our pink sphere passes in front of our larger sphere every second pass around:

@keyframes oneBefore {
0% {
transform: translateX(0);
z-index: 0;
}
24.9% {
z-index: 2;
}
25% {
transform: translate(-100px,0);
}
50% {
transform: translateX(0);
}
75% {
transform: translate(100px, 0);
z-index: 2;
}
75.1% {
z-index: 0;
}
100% {
transform: translateX(0);
}
}

You’ll notice that I’ve added two extra steps in our animation, one at 24.9% and one at 74.9%. This is because we cannot animate between z-index values, so in order to avoid awkward clipping in between steps in our animation where the z-index changes, we can create a new step to change the z-index before or after that clipping would occur. The above animation yields:

You’ll notice that the animation isn’t going at a continuous pace. This is because the default animation-timing-function property is set to ease, which slow the animation down at the beginning and end, and speeds it up in-between. Let’s change the animation-timing-function for each step to a cubic-bezier value so .one::before moves faster as it is moving behind and in front of .one::after and slows down at either end of its orbit. Let’s also set the beginning and end size of .one::before to 10px wide by 10px tall, and to 40px wide by 40px tall when it is halfway done the animation. This will give it the illusion that it is growing larger as it orbits in front of .one::after and smaller as it orbits behind it:

@keyframes oneBefore {
0% {
transform: translateX(0);
z-index: 0;
animation-timing-function: cubic-bezier(.75, 0, .25, 1);
}
24.9% {
z-index: 2;
}
25% {
transform: translate(-100px, 0);
animation-timing-function: cubic-bezier(.75, .25, 1, 1);
}
50% {
transform: translateX(0);
width: 40px;
height: 40px;
animation-timing-function: cubic-bezier(.25, .75, 1, 1);
}
75% {
transform: translate(100px, 0);
z-index: 2;
animation-timing-function: cubic-bezier(.75, 0, .25, 1);
}
75.1% {
z-index: 0;
}
100% {
transform: translateX(0);
width: 10px;
height: 10px;
}
}

This will yield:

For some finishing touches, let’s change our translate values in steps 25% and 75% to transform: translate(-100px, -25px); and transform: translate(100px, 25px);, respectively. This will rotate our orbit slightly, making it appear more dynamic. We can also choose to set a darker color (say, crimson, for example ) at steps 0% and 100% (when .one::before is behind .one::after), and set the color property at step 50% to lightcoral (when .one::before is in front of .one::after). This will give the illusion that .one::before is moving further away from us as it disappears behind .one::after:

@keyframes oneBefore {
0% {
transform: translateX(0);
z-index: 0;
animation-timing-function: cubic-bezier(.75, 0, .25, 1);
background: crimson;
}
24.9% {
z-index: 2;
}
25% {
transform: translate(-100px, -25px);
animation-timing-function: cubic-bezier(.75, .25, 1, 1);
}
50% {
transform: translateX(0);
width: 40px;
height: 40px;
animation-timing-function: cubic-bezier(.25, .75, 1, 1);
background: lightcoral;
}
75% {
transform: translate(100px, 25px);
z-index: 2;
animation-timing-function: cubic-bezier(.75, 0, .25, 1);
}
75.1% {
z-index: 0;
}
100% {
transform: translateX(0);
width: 10px;
height: 10px;
background: crimson;
}
}

And, this being our final step, will yield:

This is just one of infinite ways ::before and ::after elements can be used to create beautiful CSS animations by manipulating the z-index of pseudo-elements. Thanks for reading!