From our sponsor: Chromatic - Visual testing for Storybook, Playwright & Cypress. Catch UI bugs before your users do.
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
- Only supported by modern browsers like Firefox, IE10 and recently Chrome (see this updated support table for more info)
- Does not work in mobile browsers yet
- Pseudo-elements cannot be identified by ID
- Pseudo-elements don’t appear in the DOM.
- Pseudo-elements can’t be animated with JavaScript
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
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
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.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
Example 3
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
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
- Use CSS transitions for pseudo-elements right now Roman Komarov.
- Transitions and Animations on CSS Generated Content support table by Chris Coyier.
- Manipulating CSS pseudo-elements using jQuery (e.g. :before and :after) on Stackoverflow
Excellent work, Marco! Love the 3rd demo, would make for a great loading spinner!
Awesome Stuff!! Demo 2 is really cool
None of the examples really work on Chromium 25/Ubuntu. I think it’s the problem with the gpu acceleration again.
I really loved second one….. 🙂
Brilliant. Demo 2 looks very elegant and the last one reminded me of the Snitch from Quidditch.
wow.. Its really amazing.. excellent work
That is absolutely fantastic. I never knew that CSS could do so much. I love the demo of the opening and closing iris. Great work.
Jeez, that eye will appear in my dreams tonight…. LOL Demo 2 is SO beautiful!
So every time I feel like I have learned a lot in this simple life of mine, I come to this site and get my mind blown. *sigh*
Very True…!! The second demo is simply beautiful… Love it!
Demo 2 really awesome… love it
Wow! It’s very nice, Excellent work
Demo 2 is very nice!
Excellent examples Marco! 🙂 The 2nd and 4th demos especially cool!
Thank you! Keep up your great work!
#HELP
How to use anchor tag within the circle in demo 2.
i tried both,
<!–
codedrops
And,
codedrops
–>
A simple solution would be to change:
div by a
h1 by span
Both display: block
Love it, I love all what you do, you`re really creative, GOD BLESS YOU !
Also Thank you so much, I`ll try to learn how could I do similar one 🙂
Hi,
Great work, love it, second one is the best. Thank you so much, for training other with your creativity.
Demo 2
Cooooool
Simply amazed. Great work and thanks for the share marco!
Great work! Thanks for sharing
Wow, I’m amazed. Wonderful tut. Thanks for sharing.
Hi I need Help…
How to add a link to the text?
I tried
abc
Please Help!!!
I could not put a link around the text but you can put a link around the whole circle div. You do this simply by putting < a href around the whole div.
<a href="">codrops</a>
It’s Too Great ! Thanks To share This 🙂
AWESOME, thanks to share
How did you get the flying creature? Just a symbol?
Hi, Such a nice article with excellence. I have one query in the example with demo 2, i need animation in loop with slow effect but that should work on onLoad can you help me with it?
Hey again@ I looked over your code once again and I must say that I don’t know how you call the content slides from the menu. I’m trying to set it up so that it slides like this wix site http://liko0312.wix.com/wildfireimages#!fashion/c3oe but all I found so far were toggle options. I’m currently using CSS transitions on my version of it which can be viewd at wixwebsite.seobrasov.com but my client wants it like WIX. That’s why I was thinking of using your slide javascript – I’m a javascript noob. Please advise on the steps to take. My content is currently with div:target css all on one page.
Best regards,
Andra
You can fix the overlapping water in the droplets demo by adding
.drop { -webkit-transform-style: preserve-3d; }
. Tested in Chrome 34.Hi John, thanks a lot for the fix. I’ve updated the demo and download files. Cheers.