Examples of Pseudo-Elements Animations and Transitions

Some creative experiments that use animations and transitions on pseudo-elements to create interesting effects.

AnimatingTransitioningPseudoElements

Today we are going to experiment with animations and transitions on pseudo-elements (:before and :after) and we are going to discover their potential. We will talk a bit about animating pseudo-elements and look at four examples that use some special techniques to achieve a variety of effects.

Let’s first have a look at the advantages and disadvantages that come along with using animations and transitions together with pseudo-elements.

Advantages

  • Simplification and optimization of HTML markup
  • Taking advantage of CSS3 capacities
  • Contributing to design

Disadvantages

It is clear that there are more disadvantages than advantages, but I think that we should be excited about the future and give it a try!

The following four examples were created for this specific topic. It is clear that there are other ways for reaching the same visual effects, but for the sake of this experiment, we’ll of course use pseudo-elements so be aware that it only works in browsers that support their animations and transitions.

Please note:

  • In this tutorial we will omit vendor prefixes, but you will find them in the download files.
  • For both examples we will primarily use the box-shadow property and EM units.

A very interesting fact: pseudo-elements inherit the properties from their parent. In the case of the animations all the transformations affect them directly. This can come in handy if we want to maximize the support. Check out Use CSS transitions for pseudo-elements right now by Roman Komarov for more on that matter.

Let’s start!

Example 1

AnimatingTransitioningPseudoElements01

First, we’re going to do a fun thing: an animation of a drop of water falling into a rounded container (based on the Codrops logo)

The Markup

<div class="drop"></div>

We only have one element, although it could have another container that helps the change of scale.

The CSS

*,
*:before,
*:after {
    box-sizing: border-box;
}

.drop {
	background: rgba(255, 255, 245, 1);
	border: 4px solid rgba(255, 245, 235, 1);
	border-radius: 100%;
	box-shadow: inset -0.1em 0 2em 0.5em rgba(255, 255, 255, 0.5), 
	            inset -0.1em 0 0.5em 0 rgba(0, 0, 0, 0.8);
	position: relative;
	margin: 0 auto;
	width: 15em; 
	height: 15em;
	overflow: hidden;
}

.drop:before,
.drop:after {
	content:"";
	display:block;
	position:absolute;
}

/* Drop */

.drop:before {
	background: rgba(167, 217, 234, 1);
	border-radius: 100%;
	
	/* Drop start */
	
	box-shadow: 0 0 0 0.1em rgba(167, 217, 234, 0.8), 
	            0 0 0 0.15em rgba(167, 217, 234, 0.8), 
	            0 0 0 0.2em rgba(167, 227, 234, 0.8), 
	            0 0 0 0.25em rgba(167, 227, 234, 0.8), 
	            0 0 0 0.3em rgba(167, 227, 234, 0.8), 
	            0 0 0 0.35em rgba(167, 227, 234, 0.8), 
	            0 0 0 0.4em rgba(167, 227, 234, 0.8), 
	            0 0 0 0.45em rgba(167, 227, 234, 0.8), 
	            0 0 0 0.5em rgba(167, 227, 234, 0.8);
	top: 0%; left: 50%;
	
	/* The "width" and "height" of the division must be smaller than the "box-shadow" total size. So we can control different variant sizes. */
	
	width: 0.2em; 
	height: 0.2em;
	animation: fall 3.5s cubic-bezier(0.5, 0, 1, 0.5) infinite;
}

/* Surface */

.drop:after {
	background: rgb(52, 152, 219);
	background: linear-gradient(rgba(52, 255, 255, 1) 0%, rgba(52, 152, 219, 1) 10%, rgba(152, 252, 219, 1) 100%);
	border-radius: 100% 0 50% 0;
	left: 0; 
	bottom: 0;
	width: inherit; 
	height: 3em;
	opacity: 0.7;
	animation: surface 3s linear infinite;
}

/* Drop animation */

@keyframes fall  {

	/* Drop form */

	5%, 15%  {
		box-shadow: 0 -1.4em 0 0.1em rgba(167, 217, 234, 1), 
		            0 -0.8em 0 0.15em rgba(167, 217, 234, 1), 
		            0 -0.3em 0 0.2em rgba(167, 217, 234, 1), 
		            0 -0.1em 0 0.25em rgba(167, 217, 234, 1), 
		            0 0 0 0.3em rgba(167, 217, 234, 1), 
		            0 0.2em 0 0.35em rgba(167, 217, 234, 1), 
		            0 0.4em 0 0.4em rgba(167, 217, 234, 1), 
		            0 0.6em 0 0.45em rgba(167, 217, 234, 1), 
		            0 0.8em 0 0.5em rgba(167, 217, 234, 1);
	}
	
	/* Drop fall */
	
	16%  {
		top: 80%;
	}
	
	/* Contact surface */
	
	18%  {
		top: 80%;
		box-shadow: 1em -8em 0 0.2em rgba(177, 227, 234, 1), 
		            -2.2em -3.8em 0 0.1em rgba(177, 227, 234, 1), 
		            3em -2.85em 0 0.3em rgba(177, 227, 234, 1), 
		            -3.5em -4em 0 0.2em rgba(177, 227, 234, 1), 
		            0 0 0 0.3em rgba(177, 227, 234, 1), 
		            2em -2em 0 0.2em rgba(177, 227, 234, 1), 
		            -0.3em -3em 0 0.2em rgba(177, 227, 234, 1), 
		            0.5em -5em 0 0.35em rgba(177, 227, 234, 1), 
		            -3em -1em 0 0.3em rgba(177, 227, 234, 1);
	}
	
	/* Dispersion */
	
	30%  {
		top: 90%;
		box-shadow: 1.5em 0 0 0.2em rgba(252, 252, 255, 0.1), 
		            -2em 0 0 0.15em rgba(252, 252, 255, 0.1), 
		            3em 0 0 0.2em rgba(252, 252, 255, 0.1), 
		            -2em 0 0 0.25em rgba(252, 252, 255, 0.1), 
		            0 0 0 0.2em rgba(252, 252, 255, 0.1), 
		            2.35em 0 0 0.3em rgba(252, 252, 255, 0.1), 
		            -0.5em 0 0 0.2em rgba(252, 252, 255, 0.1), 
		            1em 0 0 0.3em rgba(252, 252, 255, 0.1), 
		            -4em 0 0 0.4em rgba(252, 252, 255, 0.1);
	}
	
	/* Hidden */
	
	40%, 100%  {
		top: 95%;
		background: rgba(255, 255, 255, 1);
		box-shadow: 1.8em 0.5em 0 0.2em rgba(255, 255, 255, 0), 
		            -3em 0.5em 0 0.1em rgba(255, 255, 255, 0), 
		            4em 0.5em 0 0.1em rgba(255, 255, 255, 0), 
		            -3.5em 0.5em 0 0.1em rgba(255, 255, 255, 0), 
		            0 0 0 0.3em rgba(255, 255, 215, 0), 
		            2.45em 0.5em 0 0.1em rgba(255, 255, 255, 0), 
		            -0.8em 0.5em 0 0.2em rgba(255, 255, 255, 0), 
		            1.5em 0.5em 0 0.3em rgba(255, 255, 255, 0), 
		            -4.5em 0.5em 0 0.2em rgba(255, 255, 255, 0);
	}
}

/* Animation of water surface */

@keyframes surface  {
	50%  {
		border-radius: 0 75% 0 75%;
		opacity: 0.5;
		height: 3.5em;
	}
}

Here are two tips for obtaining more naturalistic animations:

  • Watch and analyze references like images, videos, etc.
  • Experiment with different speeds for transitioning CSS properties and for keyframes.

More about animating characters, but definitely fun and worth exploring is the “The Animator’s Survival Kit” by Richard Williams.

Let’s go back to our subject. In this last example the :before pseudo-class has the same width like the father. In order not to have unexpected problems of overflowing we will use the “inherit” value.

Example 2

AnimatingTransitioningPseudoElements02

In this example we will create a hover effect by using a transition.

The Markup

<div class="circle">
	<h1>codrops</h1>
</div>

Here we just have a container and a heading to separate the text from the rest.

The CSS

.circle {
	background: rgb(255,255,255);
	border-radius: 100%;
	cursor: pointer;
	position: relative;
	margin: 0 auto;
	width: 15em; 
	height: 15em;
	overflow: hidden;
	transform: translateZ(0);
}

.circle h1 {
	color: rgba(189, 185, 199,0);
	font-family: 'Lato', sans-serif;
	font-weight: 900;
	font-size: 1.6em;
	line-height: 8.2em;
	text-align: center;
	text-transform: uppercase;
	-webkit-font-smoothing: antialiased;
	user-select: none;
	transition: color 0.8s ease-in-out;
}

.circle:before,
.circle:after {
	border-radius: 100%;
	content:"";
	position: absolute;
	top: 0; 
	left: 0;
	width: inherit; 
	height: inherit;			
	box-shadow: inset 10.6em 0 0 rgba(30, 140, 209, 0.2), 
	            inset 0 10.6em 0 rgba(30, 140, 209, 0.2), 
	            inset -10.6em 0 0 rgba(30, 140, 209, 0.2), 
	            inset 0 -10.6em 0 rgba(30, 140, 209, 0.2);
	transition: box-shadow 0.75s;
}

/* We rotate the :after pseudo-element to get the edge from the corner, we could also just do that with box-shadows. */

.circle:after  {
	transform: rotate(45deg);
}

/* There is no problem using "pseudo-class + pseudo-element" :) */

.circle:hover:before,
.circle:hover:after  {
	box-shadow: inset 0.86em 0 0 rgba(255, 0, 0, 0.5), 
	            inset 0 0.86em 0 rgba(252, 150, 0, 0.5), 
	            inset -0.86em 0 0 rgba(0, 255, 0, 0.5),	
	            inset 0 -0.86em 0 rgba(0, 150, 255, 0.5);
}

.circle:hover > h1  {
	color: rgba(185, 185, 185,1);
}

You have to use only one color at a time, to avoid an unwanted mixing of colors by overlapping when triggering the hover.

Example 3

AnimatingTransitioningPseudoElements03

How could we dare to leave out the indispensable spinner loading animation!
The idea here is to merge colors through the rotation. It’s very simple!

The Markup

<div class="loading"></div>

We’ll just use one single element for the markup.

The CSS

.loading  {
	background: rgba(0, 50, 250, 0);
	position: relative;
	margin: 5em auto 0 auto;
	width: 3em; 
	height: 3em;
	animation-name:rotate;
}

.loading,
.loading:before,
.loading:after  {
	border-radius: 100%;
	animation-duration: 3s;
	animation-iteration-count: infinite;
	animation-timing-function: ease-in;
}

.loading:before,
.loading:after  {
	content: "";
	position: absolute;
	top: 0; 
	left: 0;
	width: inherit; 
	height: inherit;
}

.loading:before  {
	background: rgba(200, 250, 100, 0);
	animation-name: ring;
}

.loading:after  {
	background: rgba(250, 0, 200, 0);
	animation-name: ring2;
}

@keyframes rotate  {
	0%  {
		transform: rotateZ(0deg) scaleX(0.1) scaleY(0.1) translateZ(0);
		box-shadow: inset 0.8em 0 0 rgba(255, 0, 0, 0.5), 
					inset 0 0.8em 0 rgba(252, 150, 0, 0.5), 
					inset -0.8em 0 0 rgba(0, 255, 0, 0.5), 
					inset 0 -0.8em 0 rgba(0, 150, 255, 0.5);
	}
	
	/* hidden */
	
	85%, 100%  {
	
	/* 360deg * 10 */
	
		transform: rotateZ(3600deg) scaleX(2.01) scaleY(2) translateZ(0);
		box-shadow: inset 0 0 0 rgba(255, 0, 0, 0), 
					inset 0 0 0 rgba(252, 150, 0, 0), 
					inset 0 0 0 rgba(0, 255, 0, 0), 
					inset 0 0 0 rgba(0, 150, 255, 0);
	}
}

@keyframes ring  {
	0%  {
		transform: scaleX(0.1) scaleY(0.5);
		box-shadow: inset 0.8em 0 0 rgba(255, 0, 0, 0.5), 
					inset 0 0.8em 0 rgba(252, 150, 0, 0.5), 
					inset -0.8em 0 0 rgba(0, 255, 0, 0.5), 
					inset 0 -0.8em 0 rgba(0, 150, 255, 0.5);
	}
	
	/* hidden */
	
	75%, 100%  {
		transform: scaleX(2) scaleY(2.1);
		box-shadow: inset 0 0 0 rgba(255, 0, 0, 0), 
					inset 0 0 0 rgba(252, 150, 0, 0), 
					inset 0 0 0 rgba(0, 255, 0, 0), 
					inset 0 0 0 rgba(0, 150, 255, 0);
	}
}

@keyframes ring2  {
	0%  {
		transform: scaleX(0.5) scaleY(0.1);
		box-shadow: inset 0.8em 0 0 rgba(255, 0, 0, 0.5), 
					inset 0 0.8em 0 rgba(252, 150, 0, 0.5), 
					inset -0.8em 0 0 rgba(0, 255, 0, 0.5), 
					inset 0 -0.8em 0 rgba(0, 150, 255, 0.5);
	}
	
	/* hidden */
	
	65%, 100%  {
		transform: scaleX(2) scaleY(2.1);
		box-shadow: inset 0 0 0 rgba(255, 0, 0, 0), 
					inset 0 0 0 rgba(252, 150, 0, 0), 
					inset 0 0 0 rgba(0, 255, 0, 0), 
					inset 0 0 0 rgba(0, 150, 255, 0);
	}
}

This is a great example of experimenting with timings and speeds to get a really smooth animation.

Example 4

AnimatingTransitioningPseudoElements04

This is the most crazy and extravagant example from all: a little one-eyed flying creature!
We are going to use both, animations and transitions.

The Markup

<div class="pojoro">●</div>

We will use one element for the creature’s eye.

The CSS

.pojoro  {
	background: rgba( 255, 255, 255, 1);
	background: radial-gradient(ellipse at center, rgba(255,255,255,1) 40%,rgba(51,51,51,1) 100%);
	border-radius: 100%;
	
	/* box-shadow: secondary color, body, eyelash */
	
	box-shadow: 0 0 0 0.2em rgb(146,89,149), 
				0 0 0.1em 0.55em rgb(176,89,179), 
				inset 0 0.2em 0 0 rgb(136,79,139);
	
	/* ojo (eye) */
	
	color: rgba( 40, 40, 40, 0.8);
	line-height: 1.1em;
	padding-left: 0.18em;
	-webkit-font-smoothing: antialiased;
	user-select: none;
	
	/* usability, position and transition */
	
	cursor: pointer;
	position: relative;
	margin: 5em auto 0 auto;
	width: 1em; height: 1em;
	transform-origin: center;
	transition: all 0.8s ease-in-out;
	
	/* Separate the animations to have a better control over the eye and the body */
	
	animation: eye 2.2s ease-in-out infinite, body 1.15s 1.8s linear infinite; 
} 

/* Elevate and distort the creature. */

.pojoro:hover  {
	transform: scaleY(0.9) scaleX(0.95) translateY(-3em) translateZ(0);
}

/* wings */

.pojoro:before,
.pojoro:after  {
	background: rgba(0,0,0,0);
	border-radius: 100%;
	content: "";
	
	/* display:none, hide wings */
	
	display: none;
	position: absolute;
	width: 1em; height: 0.1em;
	-webkit-filter: blur(1px);
	transition: all 0.2s;
	animation-duration: 0.2s;
	animation-iteration-count: infinite;
	animation-timing-function: ease-in-out;
}

.pojoro:before  {
	top: 25%; left: 1.45em;
	margin-left: -1em;
	transform-origin: left;
	transform: rotate(-60deg);
	animation-name: wings;
}

.pojoro:after  {
	top: 25%; left: -2.2em;
	margin-left: 1em;
	transform-origin: right;
	transform: rotate(60deg);
	animation-name: wings2;
}

.pojoro:hover:before,
.pojoro:hover:after  {
	background: rgba(100,100,100,0.8);
	
	/* display:block, we allow the wings to appear and the animation starts */
	
	display: block;
	margin-left: 0em;
	width: 2em; height: 0.3em;
}

@keyframes eye  {

	/* Eye movement */
	
	5%, 10%  {
		line-height: 1.2em;
		padding-left: 0em;
	}
	15%, 20%  {
		line-height: 1.15em;
		padding-left: 0.4em;
	}
	
	/* Eyelash movement */
	
	25%  {
		box-shadow: 0 0 0 0.2em rgb(146,89,149), 
					0 0 0.1em 0.55em rgb(166,89,169), 
					inset 0 1em 0 0 rgb(136,79,139);
	}
	23%, 27%  {
		box-shadow: 0 0 0 0.2em rgb(146,89,149), 
					0 0 0.1em 0.55em rgb(166,89,169), 
					inset 0 0.2em 0 0 rgb(136,79,139);
	}
}

@keyframes body  {
	50%  {
		width: 1.4em; height: 1.4em;
	} 
}

@keyframes wings  {
	50%  {
		transform: rotate(65deg);
	}
}

@keyframes wings2  {
	50%  {
		transform: rotate(-65deg);
	}
}

On hover we activate the wings animations and the body stars to elevate.
And that was the last example!

In conclusion, pseudo-element are a great thing and combining them with animations and transitions allow for the creation of some fun effects without using too much markup or images. Wider browser support is hopefully coming soon; until then we can play with it and discover fun and interesting techniques.

What do you think about it?

I hope that this will be useful for you and serve as an inspiration.

References

Tagged with:

Marco Barria

Marco spends most of his time as a designer and front-end developer. His hobbies are to experiment with new technologies and he loves CSS.

Stay up to date with the latest web design and development news and relevant updates from Codrops.