The title pretty much says it all but here is an example.
Let's say I have a CSS 'loading spinner' as below:
.spinner {
height: 50px;
width: 50px;
position: relative;
animation: rotate .6s infinite linear;
border-left: 6px solid #222;
border-right: 6px solid #222;
border-bottom: 6px solid #222;;
border-top: 6px solid #ccc;
border-radius: 100%;
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
I want to add a pseudo element to this - for example with content: 'loading...'
before or after .spinner
.
Is it possible to ensure the pseudo element does not inherit the animation from .spinner
, or must a pseudo element always take what the parent has?
The only way to truly pause an animation in CSS is to use the animation-play-state property with a paused value. In JavaScript, the property is “camelCased” as animationPlayState and set like this: element. style.
The only way that pseudo-elements can inherit values from the parent of their generating element is when the generating element itself is also inheriting from its parent. This is because inheritance occurs from a parent to a child, one level at a time.
As you have seen, the ::before and ::after pseudo-elements can be used in several different ways to produce interesting animated effects that give life to our designs.
Pseudo-classes enable you to target an element when it's in a particular state, as if you had added a class for that state to the DOM. Pseudo-elements act as if you had added a whole new element to the DOM, and enable you to style that.
As the pseudo-element is a child element of the parent it will continue to get rotated as long as parent has the animation. Even setting animation: none
on the pseudo element will have no effect.
The only way to make it look as though the child has no animation is to reverse the effect like shown in below snippet. What is being done is that the very same animation is added to the pseudo element but the animation-direction
is set as reverse
. This means that the pseudo get the exact reverse transform effect and thus would retain it in the same position.
.spinner {
height: 50px;
width: 50px;
position: relative;
animation: rotation .6s infinite linear;
border-left: 6px solid #222;
border-right: 6px solid #222;
border-bottom: 6px solid #222;
border-top: 6px solid #ccc;
border-radius: 100%;
}
.spinner:after {
position: absolute;
content: 'Loading..';
top: 0px;
left: 0px;
height: 100%;
width: 100%;
animation: rotation .6s infinite linear reverse; /* added this line */
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
<div class='spinner'></div>
The above snippet uses the default setting for transform-origin
which is 50% 50%
but if the child pseudo-element has padding
and/or margin
then the transform-origin
setting has to be adjusted accordingly to avoid the pseudo-element from producing a shivering like effect. The calculation logic is provided in the below snippet.
.spinner {
height: 50px;
width: 50px;
position: relative;
animation: rotation .6s infinite linear;
border-left: 6px solid #222;
border-right: 6px solid #222;
border-bottom: 6px solid #222;
border-top: 6px solid #ccc;
border-radius: 100%;
}
.spinner.parent-padded-margin {
padding: 10px;
margin: 10px;
}
.spinner:after {
position: absolute;
content: 'Loading..';
top: 0px;
left: 0px;
height: 100%;
width: 100%;
animation: rotation .6s infinite linear reverse;
/* added this line */
}
.spinner.child-padded-margin:after {
padding: 10px 8px;
margin: 5px 4px;
transform-origin: calc(50% - 12px) calc(50% - 15px); /* calc(50% - ((padding-left + padding-right)/2 + margin-left)) calc(50% - ((padding-top + padding-bottom)/2 + margin-top)) */
}
.spinner.child-padded-margin-2:after {
padding: 10px 6px 16px 14px;
margin: 7px 12px 5px 10px;
transform-origin: calc(50% - 20px) calc(50% - 20px); /* calc(50% - ((padding-left + padding-right)/2 + margin-left)) calc(50% - ((padding-top + padding-bottom)/2 + margin-top)) */
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
<div class='spinner'></div>
<div class='spinner parent-padded-margin'></div>
<div class='spinner child-padded-margin'></div>
<div class='spinner child-padded-margin-2'></div>
Positioning the pseudo-element (using top
,left
,bottom
,right
) also has affects the animation. It would also require the transform-origin
to be modified accordinly in-order for the animation to work properly. A sample is available in the below snippet.
.spinner {
height: 50px;
width: 50px;
position: relative;
animation: rotation .6s infinite linear;
border-left: 6px solid #222;
border-right: 6px solid #222;
border-bottom: 6px solid #222;
border-top: 6px solid #ccc;
border-radius: 100%;
}
.spinner.parent-padded-margin {
padding: 10px;
margin: 10px;
}
.spinner:after {
position: absolute;
content: 'Loading..';
height: 100%;
width: 100%;
animation: rotation .6s infinite linear reverse; /* added this line */
}
.spinner.child-positioned{
margin-bottom: 40px;
}
.spinner.child-positioned:after {
top: 120%;
left: 2%;
transform-origin: calc(50% - 2%) calc(50% - 120%); /* basically need to subtract the distance from the left and top of the container */
}
.spinner.child-positioned-negative:after {
bottom: -120%;
right: -2%;
transform-origin: calc(50% - 2%) calc(50% - 120%); /* basically need to subtract the distance from the left and top of the container */
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
<div class='spinner child-positioned'></div>
<div class='spinner child-positioned-negative'></div>
Note: Both the above solutions work perfectly fine in latest versions of Chrome, Opera and Safari but are causing the text to have a slanted appearance in IE 11, Edge and Firefox. Firefox seems to require a separate animation which goes from rotate(-10deg) to rotate(-370deg) for FF while it gets more complex in IE.
The only alternate without setting the reverse animation on pseudo (child) element would be to make use of the method mentioned by Chris in his comment. That would mean setting borders and the animation directly to the pseudo element. This would mean that the parent's contents would remain unaffected as the parent won't get affected by a transform on the child.
.spinner {
height: 50px;
width: 50px;
position: relative;
}
.spinner:after {
position: absolute;
content: '';
top: 0px;
left: 0px;
height: 100%;
width: 100%;
animation: rotation .6s infinite linear;
border-left: 6px solid #222;
border-right: 6px solid #222;
border-bottom: 6px solid #222;
border-top: 6px solid #ccc;
border-radius: 100%;
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
<div class='spinner'>Loading...</div>
For completeness to the question alongside the comprehensive answer from @Harry I produced a version with the text below the spinner. The method of this is to use the .spinner
as a canvas, put the actual spinning circle :before
and the loading...
in the :after
as follows:
.spinner:before {
content: ' ';
display: block;
height: 50px;
width: 50px;
margin: 24px auto 6px auto;
animation: rotation .6s infinite linear;
border-left: 6px solid #222;
border-right: 6px solid #222;
border-bottom: 6px solid #222;;
border-top: 6px solid #ccc;
border-radius: 100%;
}
.spinner {
height: 100px;
width: 100px;
position: relative;
}
.spinner:after {
display: block;
text-align: center;
content: 'loading...';
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
<div class='spinner'></div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With