Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable CSS animation on pseudo element inherited from parent

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?

like image 473
Robert Avatar asked Nov 09 '15 13:11

Robert


People also ask

How do you stop an animation in CSS?

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.

Do pseudo elements inherit?

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.

Can you animate pseudo elements?

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.

What is the difference between pseudo elements and pseudo-classes?

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.


2 Answers

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>
like image 198
Harry Avatar answered Sep 21 '22 01:09

Harry


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>
like image 34
Robert Avatar answered Sep 18 '22 01:09

Robert