CSS animations tricks: instant changes, negative delays, transform-origin animations and more

Original author: Zach Saucier
  • Transfer
Applying CSS animations in my daily work, I gradually developed the habit of experimenting with them in my free time. Constantly trying to implement another interesting idea using as few HTML elements as possible, I found many ways to do fairly unobvious things with CSS. In this article I want to share some of them.

Fast state change in the middle of animation

Usually, animations are used to smoothly change the properties of elements over time. However, changes can also be almost instantaneous. To do this, set two keyframes with a very small interval, for example, at 0.001%:

@keyframes toggleOpacity {
  50% { opacity: 1; } /* Turn off */
  50.001% { opacity: 0.4; }
  /* Keep off state for a short period */
  52.999% { opacity: 0.4; } /* Turn back on */
  53% { opacity: 1; }
}

Here's how I used this technique to simulate a blinking neon sign using transparency and property text-shadow:



Example code:
HTML:

<linkhref='http://fonts.googleapis.com/css?family=Monoton'rel='stylesheet'type='text/css'><div><pid="error">E<span>r</span>ror</p><pid="code">4<span>0</span><span>4</span></p></div>

CSS:

body { background-color: #111111; }
div {
  padding: 40px;
  font-size: 75px;
  font-family: 'Monoton', cursive;
  text-align: center;
  text-transform: uppercase;
  text-shadow: 0080px red,0030px FireBrick,006px DarkRed;
  color: red;
}
divp { margin:0; }
#error:hover { text-shadow: 00200px#ffffff,0080px#008000,006px#0000ff; }
#code:hover { text-shadow: 00100px red,0040px FireBrick,008px DarkRed; }
#error {
  color: #fff;
  text-shadow: 0080px#ffffff,0030px#008000,006px#0000ff;
}
#errorspan {
  animation: upper 11s linear infinite;
}
#codespan:nth-of-type(2) {
  animation: lower 10s linear infinite;
}
#codespan:nth-of-type(1) {
  text-shadow: none;
  opacity:.4;
}
@keyframes upper {
  0%,19.999%,22%,62.999%,64%, 64.999%,70%,100% {
    opacity:.99; text-shadow: 0080px#ffffff,0030px#008000,006px#0000ff;
  }
  20%,21.999%,63%,63.999%,65%,69.999% {
    opacity:0.4; text-shadow: none; 
  }
}
@keyframes lower {
  0%,12%,18.999%,23%,31.999%,37%,44.999%,46%,49.999%,51%,58.999%,61%,68.999%,71%,85.999%,96%,100% {
    opacity:0.99; text-shadow: 0080px red,0030px FireBrick,006px DarkRed;
  }
  19%,22.99%,32%,36.999%,45%,45.999%,50%,50.99%,59%,60.999%,69%,70.999%,86%,95.999% { 
    opacity:0.4; text-shadow: none; 
  }
}


Codepen Demo

Negative animation delays

A positive delay delays the start of the animation for a while. And negative - it starts the animation immediately, but not from the very beginning, but from the time specified in the delay. In other words, the animation starts at some point inside its loop. This allows you to apply animation to several elements with a phase shift, changing only the delay time. Here is an example of using such an animation:



Example code:
HTML:

<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>

CSS:

div {
  border-radius:50%;
  position:absolute;
  top:50%; left:75%;
}
div:nth-of-type(odd) { background:black; }
div:nth-of-type(even) { background:white; border:2px solid black; }
div:nth-of-type(11) {
  height:10px; width:10px;
  margin-top:-5px; margin-left:-5px;
  -webkit-animation:slide 3s ease-in-out infinite;
  animation:slide 3s ease-in-out infinite;
}
div:nth-of-type(10) {
  height:20px; width:20px;
  margin-top:-12px; margin-left:-12px;
  -webkit-animation:slide 3s -2.7s ease-in-out infinite;
  animation:slide 3s -2.7s ease-in-out infinite;
}
div:nth-of-type(9) {
  height:40px; width:40px;
  margin-top:-20px; margin-left:-20px;
  -webkit-animation:slide 3s -2.4s ease-in-out infinite;
  animation:slide 3s -2.4s ease-in-out infinite;
}
div:nth-of-type(8) {
  height:60px; width:60px;
  margin-top:-32px; margin-left:-32px;
  -webkit-animation:slide 3s -2.1s ease-in-out infinite;
  animation:slide 3s -2.1s ease-in-out infinite;
}
div:nth-of-type(7) {
  height:80px; width:80px;
  margin-top:-40px; margin-left:-40px;
  -webkit-animation:slide 3s -1.8s ease-in-out infinite;
  animation:slide 3s -1.8s ease-in-out infinite;
}
div:nth-of-type(6) {
  height:100px; width:100px;
  margin-top:-52px; margin-left:-52px;
  -webkit-animation:slide 3s -1.5s ease-in-out infinite;
  animation:slide 3s -1.5s ease-in-out infinite;
}
div:nth-of-type(5) {
  height:120px; width:120px;
  margin-top:-60px; margin-left:-60px;
  -webkit-animation:slide 3s -1.2s ease-in-out infinite;
  animation:slide 3s -1.2s ease-in-out infinite;
}
div:nth-of-type(4) {
  height:140px; width:140px;
  margin-top:-72px; margin-left:-72px;
  -webkit-animation:slide 3s -0.9s ease-in-out infinite;
  animation:slide 3s -0.9s ease-in-out infinite;
}
div:nth-of-type(3) {
  height:160px; width:160px;
  margin-top:-80px; margin-left:-80px;
  -webkit-animation:slide 3s -0.6s ease-in-out infinite;
  animation:slide 3s -0.6s ease-in-out infinite;
}
div:nth-of-type(2) {
  height:180px; width:180px;
  margin-top:-92px; margin-left:-92px;
  -webkit-animation:slide 3s -0.3s ease-in-out infinite;
  animation:slide 3s -0.3s ease-in-out infinite;
}
div:nth-of-type(1) {
  height:200px; width:200px;
  margin-top:-100px; margin-left:-100px;
  -webkit-animation:slide 3s ease-in-out infinite;
  animation:slide 3s ease-in-out infinite;
}
@keyframes slide {
  0% { left:75% }
  50% { left:25%; }
  100% { left:75%; }
}
@-webkit-keyframes slide {
  0% { left:75% }
  50% { left:25%; }
  100% { left:75%; }
}


Codepen Demo

Proportional animations

I try to make web applications as responsive as possible. This also applies to CSS animations. Although it is impossible to make absolutely all animations adaptive, sometimes you can still use percentages and other relative units instead of absolute units.

In many of my animations, I use elements whose sizes should be proportional to the size of the page. Perhaps you think that I use a fixed width and height for this, but that is not the case. You can specify the width in percent, zero height, and percentage for padding. Using the padding-bottomelement height remains proportional to the width:

.container {
  position: relative;
  display: block;
  width: 100%;
  height: 0;
  padding-bottom: 100%;
}

If you open the following example on Codepen and try to resize the window, you will see how it works. Negative animation delays are also used in this example.



Example code:
HTML:

<span><!-- Square container --><div></div><!-- Bars --><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></span>

CSS:

body { 
  background: rgb(20, 20, 20);
  overflow: hidden;
}
span {
  position: relative;
  display: block;
  width: 100%;
  height: 0;
  padding-top: 100%;
  overflow: hidden;
}
div {
  margin-top: -17%;
  height: 34%; 
  width: 2%;
  top: 30%;
  border-radius: 20px;
  position: absolute;
}
div:nth-of-type(1)  { animation: wave 17s0.000s linear infinite; }
div:nth-of-type(2)  { animation: wave 17s -16.227s linear infinite; }
div:nth-of-type(3)  { animation: wave 17s -15.455s linear infinite; }
div:nth-of-type(4)  { animation: wave 17s -14.682s linear infinite; }
div:nth-of-type(5)  { animation: wave 17s -13.909s linear infinite; }
div:nth-of-type(6)  { animation: wave 17s -13.136s linear infinite; }
div:nth-of-type(7)  { animation: wave 17s -12.364s linear infinite; }
div:nth-of-type(8)  { animation: wave 17s -11.591s linear infinite; }
div:nth-of-type(9)  { animation: wave 17s -10.818s linear infinite; }
div:nth-of-type(10) { animation: wave 17s -10.045s linear infinite; }
div:nth-of-type(11) { animation: wave 17s  -9.273s linear infinite; }
div:nth-of-type(12) { animation: wave 17s  -8.500s linear infinite; }
div:nth-of-type(13) { animation: wave 17s  -7.727s linear infinite; }
div:nth-of-type(14) { animation: wave 17s  -6.955s linear infinite; }
div:nth-of-type(15) { animation: wave 17s  -6.182s linear infinite; }
div:nth-of-type(16) { animation: wave 17s  -5.409s linear infinite; }
div:nth-of-type(17) { animation: wave 17s  -4.636s linear infinite; }
div:nth-of-type(18) { animation: wave 17s  -3.864s linear infinite; }
div:nth-of-type(19) { animation: wave 17s  -3.091s linear infinite; }
div:nth-of-type(20) { animation: wave 17s  -2.318s linear infinite; }
div:nth-of-type(21) { animation: wave 17s  -1.545s linear infinite; }
div:nth-of-type(22) { animation: wave 17s  -0.773s linear infinite; }
@keyframes wave {
  0%   { left:-2%; background: #3B44D1; }
  5%   { background: #9337FE; }
  10%  { height:10%; margin-top: -5%; background: #C532FC; }
  15%  { background: #F639F8; }
  20%  { height:34%; margin-top:-17%; background: #F236C8; }
  25%  { background: #FF2F8D; }
  30%  { height:10%; margin-top: -5%; background: #EE3860; }
  35%  { background: #DC5245; }
  40%  { height:34%; margin-top:-17%; background: #F38643; }
  45%  { background: #F8B435; }
  50%  { height:10%; margin-top: -5%; background: #FAF444; }
  55%  { background: #E0FF3B; }
  60%  { height:34%; margin-top:-17%; background: #E1FF3C; }
  65%  { background: #46F443; }
  70%  { height:10%; margin-top: -5%; background: #54E67B; }
  75%  { background: #4DF3A9; }
  80%  { height:34%; margin-top:-17%; background: #3AF9DA; }
  85%  { background: #36EBF4; }
  90%  { height:10%; margin-top: -5%; background: #3DB3F3; }
  95%  { background: #3C82F1; }
  100% { height:34%; margin-top:-17%; left:100%; background: #5B38EE; }
}


Codepen Demo

Change transform-origin in the middle of an animation

While working on one of the animations, I was surprised to find that the property transform-origincan not only change in the middle of the animation, but can itself be animated. In the following example, this is used to make four different rotations in the same animation instead of creating four separate animations:



Example code:
HTML:

<div></div>

CSS:

div {
  width:200px; height:200px;
  background:rgba(0,0,255,.5);
  animation:flipAround 8s infinite;
}
@keyframes flipAround {
  25% { animation-mode:forwards; transform-origin:right; transform:rotateY(-180deg); }
  25.001% { transform:translateX(200px); }
  50% { transform-origin:bottom; transform:translateX(200px) rotateX(-180deg); }
  50.001% { transform:translateX(200px) translateY(200px); }
  75% { transform-origin:left; transform:translateX(200px) translateY(200px) rotateY(180deg); }
  75.001% { transform:translateY(200px); }
  100% { transform-origin:top; transform:translateY(200px) rotateX(180deg); }
}
body { background:rgb(20,20,20); }


Codepen demo

This trick has a drawback: you cannot use animation-mode: forwards; only for part of the animation. This means that we will have to move the element with every change transform-origin. This example is translateused to simulate rotation effects. In more complex examples, this can be quite tedious .

Negative transform-origin

You can specify a negative transform-origin value, which can be useful for creating rotating elements. Instead of specifying the offset and rotation angle for an element separately, as Lee Veru describes , this can be achieved more easily by using negative values transform-originand an additional element or pseudo element (or only one element if it is not required to maintain a constant angle relative to the horizon). With different values, transform-originyou can use the same animation for different elements:



Example code:
HTML:

<div></div>

CSS:

div { width:100px; height:100px; margin-top:180px; transform-origin:200% center; position:relative; animation:rotate 3s linear infinite; }
div:before { content:''; position:absolute; height:100%; width:100%; background:blue; animation:rotate 3s linear reverse infinite; }
@keyframes rotate { 100% { transform:rotate(-360deg); } }
body { background:rgb(20,20,20); } /* The best color ever */


Codepen Demo

Magic box-shadow

To animate simple forms without content inside, a property can come in handy. box-shadow.Using it, you can create several borders around one element . Based on this idea, using different shifts for the shadows, you can create entire sets of animated shapes based on a single HTML element. Here's an example of an animation that looks like six spinning circles that are entirely made using box-shadow:



Example code:
HTML:

<div></div>

CSS:

div {
  border-radius:50%;
  height:2px; width:2px; /* To allow border-radius to work */position:absolute;
  top:50%; left:50%;
  margin-top:-1px; margin-left:-1px;
  box-shadow:
    -75px -125px040px#6cce74,
     75px -125px040px#c18d46,
    150px0px040px#c14745,
     75px125px040px#2e1e5b,
    -75px125px040px#9c37a6,
   -150px0px040px#76bdd1;
  -webkit-animation:rotate 12s infinite linear;
  animation:rotate 12s infinite linear;
}
@keyframes rotate {
  16.67% {
    box-shadow:
      -75px -125px040px#76bdd1,
       75px -125px040px#6cce74,
      150px0px040px#c18d46,
       75px125px040px#c14745,
      -75px125px040px#2e1e5b,
     -150px0px040px#9c37a6;
  }
  33.33%   { 
    box-shadow:
      -75px -125px040px#9c37a6,
       75px -125px040px#76bdd1,
      150px0px040px#6cce74,
       75px125px040px#c18d46,
      -75px125px040px#c14745,
     -150px0px040px#2e1e5b;
  }
  50%      { 
    box-shadow:
      -75px -125px040px#2e1e5b,
       75px -125px040px#9c37a6,
      150px0px040px#76bdd1,
       75px125px040px#6cce74,
      -75px125px040px#c18d46,
     -150px0px040px#c14745;
  }
  66.67%   { 
    box-shadow:
      -75px -125px040px#c14745,
       75px -125px040px#2e1e5b,
      150px0px040px#9c37a6,
       75px125px040px#76bdd1,
      -75px125px040px#6cce74,
     -150px0px040px#c18d46;
  }
  88.88%   { 
    box-shadow:
      -75px -125px040px#c18d46,
       75px -125px040px#c14745,
      150px0px040px#2e1e5b,
       75px125px040px#9c37a6,
      -75px125px040px#76bdd1,
     -150px0px040px#6cce74;
  }
  100% {
    transform:rotate(-360deg);
    box-shadow:
      -75px -125px040px#6cce74,
       75px -125px040px#c18d46,
      150px0px040px#c14745,
       75px125px040px#2e1e5b,
      -75px125px040px#9c37a6,
     -150px0px040px#76bdd1;
  }
}
@-webkit-keyframes rotate {
  16.67% {
    box-shadow:
      -75px -125px040px#76bdd1,
       75px -125px040px#6cce74,
      150px0px040px#c18d46,
       75px125px040px#c14745,
      -75px125px040px#2e1e5b,
     -150px0px040px#9c37a6;
  }
  33.33%   { 
    box-shadow:
      -75px -125px040px#9c37a6,
       75px -125px040px#76bdd1,
      150px0px040px#6cce74,
       75px125px040px#c18d46,
      -75px125px040px#c14745,
     -150px0px040px#2e1e5b;
  }
  50%      { 
    box-shadow:
      -75px -125px040px#2e1e5b,
       75px -125px040px#9c37a6,
      150px0px040px#76bdd1,
       75px125px040px#6cce74,
      -75px125px040px#c18d46,
     -150px0px040px#c14745;
  }
  66.67%   { 
    box-shadow:
      -75px -125px040px#c14745,
       75px -125px040px#2e1e5b,
      150px0px040px#9c37a6,
       75px125px040px#76bdd1,
      -75px125px040px#6cce74,
     -150px0px040px#c18d46;
  }
  88.88%   { 
    box-shadow:
      -75px -125px040px#c18d46,
       75px -125px040px#c14745,
      150px0px040px#2e1e5b,
       75px125px040px#9c37a6,
      -75px125px040px#76bdd1,
     -150px0px040px#6cce74;
  }
  100% {
    -webkit-transform:rotate(-360deg);
    box-shadow:
      -75px -125px040px#6cce74,
       75px -125px040px#c18d46,
      150px0px040px#c14745,
       75px125px040px#2e1e5b,
      -75px125px040px#9c37a6,
     -150px0px040px#76bdd1;
  }
}


Codepen demo

Unfortunately, it box-shadowdoes not support relative percentages, so it’s harder to make them responsive than regular elements. However, their sizes can be changed manually or applied transform:scale(n)to the parent element.

Using pseudo-elements

As well as box-shadow, pseudo-elements can be used to enrich the appearance of HTML elements. For them, you can use separate animations from the parent element, they can have separate shadows - almost like real elements. This allows you to do amazing things:



Example code:
HTML:

<divid='gif'></div>

CSS:

body { background:black; overflow:hidden; }
#gif {
  background:black;
  padding:10px;
  height:80px;
  width:80px;
  border-radius:50%;
  position:absolute;
  top:calc(50% - 40px);
  left:calc(50% - 40px);
  box-shadow:
    178px00 -25px black,
    178px00 -20px white,
    -178px00 -25px black,
    -178px00 -20px white, 
    00020px black,
    00030px white,
    000130px black,
    000135px white;
  -webkit-animation: rotate 3s linear infinite;
  animation: rotate 3s linear infinite;
}
#gif:before {
  content: " ";
  position: absolute;
  height:50px;
  width:50px;
  border-radius:50%;
  top: -155px;
  left: 20px;
  background:black;
  border: 5px solid white;
  box-shadow:
    0355px0 -5px black,
    0355px00px white;
  -webkit-animation: reverseRotate 3s linear infinite;
  animation: reverseRotate 3s linear infinite;
}
#gif:after {
  /* Segmented circle code goes here */content: " ";
  position: absolute;
  height:280px;
  width:280px;
  left:-90px;
  top:-90px;
  background-image: url("data:image/svg+xml;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSAiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDEwMCAxMDAiID4NCiAgICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlLWRhc2hhcnJheT0iMC45NTIiIHN0cm9rZS13aWR0aD0iOCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ3aGl0ZSIvPg0KPC9zdmc+");
  background-repeat: no-repeat;
  z-index:2;
  -webkit-animation: segmentRotate 300s linear infinite;
  animation: segmentRotate 300s linear infinite;
}
@keyframes rotate {
  0%   { transform: rotate(  0deg); }  
  10%, 15%, 35%, 40%, 60%, 65%, 85%, 90%  { 
    background: black; 
    box-shadow:
      178px00 -25px black,
      178px00 -20px white,
      -178px00 -25px black,
      -178px00 -20px white, 
      00020px black,
      00030px white,
      000130px black,
      000135px white;
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; 
    box-shadow:
      178px00 -25px white,
      178px00 -20px white,
      -178px00 -25px white,
      -178px00 -20px white, 
      00020px black,
      00030px white,
      000130px black,
      000135px white;
  }    
  100% { transform: rotate(360deg); }
}
@keyframes reverseRotate {
  0%   { transform: translateY(178px) rotate(0deg) translateY(-178px) rotate(0deg); }  
  10%, 15%, 35%, 40%, 60%, 65%, 85%, 90%  { 
    background: black; box-shadow: 0355px0 -5px black, 0355px00px white; 
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; box-shadow: 0355px0 -5px white, 0355px00px white; 
  }    
  100% { transform: translateY(178px) rotate(-720deg) translateY(-178px) rotate(0deg); }
}
@keyframes segmentRotate {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(-32000deg); }
}
@-webkit-keyframes rotate {
  0%   { -webkit-transform: rotate(  0deg); }  
  10%, 15%, 35%, 40%, 60%, 65%, 85%, 90%  { 
    background: black; 
    box-shadow:
      178px00 -25px black,
      178px00 -20px white,
      -178px00 -25px black,
      -178px00 -20px white, 
      00020px black,
      00030px white,
      000130px black,
      000135px white;
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; 
    box-shadow:
      178px00 -25px white,
      178px00 -20px white,
      -178px00 -25px white,
      -178px00 -20px white, 
      00020px black,
      00030px white,
      000130px black,
      000135px white;
  }    
  100% { -webkit-transform: rotate(360deg); }
}
@-webkit-keyframes reverseRotate {
  0%   { -webkit-transform: translateY(178px) rotate(0deg) translateY(-178px) rotate(0deg); }  
  10%, 15%, 35%, 40%, 60%, 65%, 85%, 90%  { 
    background: black; box-shadow: 0355px0 -5px black, 0355px00px white; 
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; box-shadow: 0355px0 -5px white, 0355px00px white; 
  }    
  100% { -webkit-transform: translateY(178px) rotate(-720deg) translateY(-178px) rotate(0deg); }
}
@-webkit-keyframes segmentRotate {
  0%   { -webkit-transform: rotate(0deg); }
  100% { -webkit-transform: rotate(-32000deg); }
}


Codepen demo

In this example, all concentric circles around a central blinking circle, as well as two small circles on the outer ring, are made using box-shadow. The other two circles are the shadows of the pseudo-element, and the ring of strokes is the background of another pseudo-element, set as inline SVG.

Last few tips


Use transformations wherever possible

As shown Paul Irish and others , the transformation is faster than changing the size and position of elements using the properties top, left, widthand height.

Using transformations, it’s easier to implement an adaptive design by using relative values ​​for scale( example ).

Failure to use transformations leads to errors that are difficult to catch. For example, this animation is displayed in the Chrome browser with incorrect colors, although the values ​​in the code are correct. After switching to CSS transformations, the problem resolved by itself.

z-index can cause problems

Perhaps, z-indexI spent more time on solving problems with than on any other. Implementation z-indexvaries from browser to browser. The main difference is that Mozilla does not animate z-index, and its value changes in steps, then Webkit-based browsers can change it smoothly.

It is also worth noting that if you want the pseudo-elements to be behind the parent element, the pseudo-element must be negative z-index, and the parent element must be located in the default overlay context, that is, you cannot apply to it z-indexor any other tricks that pull it out from standard position in context.

And the last one. Any element for which a transparency other than “1” is set gets its own overlay context. Read more about this in an article by Philip Walton .

Look for sources of inspiration

Something in the real world, an interesting web page, an unusual video effect, animated gif, or anything else - constantly look for things that are worth trying to implement.

I found that if you do not peek right away at how this or that effect is made, you can find a unique way and even surpass the original. Even if I fail, I always at least learn something new about the programming language that I use. Often it turns out that even a completely unrealized idea is quite spectacular. Sometimes vice versa - the result is much better than I could have dreamed.

I hope that this article will help you create something new and beautiful, even many of the specific techniques described in it are already familiar to you.


Also popular now: