Содержание

Применяя CSS-анимации в повседневной работе, я постепенно выработал привычку экспериментировать с ними в свободное время. Постоянно пытаясь реализовать очередную интересную задумку с использованием как можно меньшего числа элементов HTML, я обнаружил немало способов сделать с помощью CSS довольно неочевидные вещи. В этой статье я хочу поделиться некоторыми из них.

Быстрое изменение состояния посреди анимации

Обычно анимации используются для того, чтобы плавно менять свойства элементов со временем. Однако изменения могут также быть практически мгновенными. Для этого надо задать два ключевых кадра с очень маленьким интервалом, например в 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; }
}

Вот как я использовал этот приём для имитации мигающей неоновой вывески с помощью прозрачности и свойства text-shadow:

b8795cc37897ac88caeab94ffe6e80ba1

Код примера:

HTML:

<link href='http://fonts.googleapis.com/css?family=Monoton' rel='stylesheet' type='text/css'>
<div>
  <p id="error">E<span>r</span>ror</p>
  <p id="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: 0 0 80px red,0 0 30px FireBrick,0 0 6px DarkRed;
  color: red;
}
div p { margin:0; }
#error:hover { text-shadow: 0 0 200px #ffffff,0 0 80px #008000,0 0 6px #0000ff; }
#code:hover { text-shadow: 0 0 100px red,0 0 40px FireBrick,0 0 8px DarkRed; }
#error {
  color: #fff;
  text-shadow: 0 0 80px #ffffff,0 0 30px #008000,0 0 6px #0000ff;
}
#error span {
  animation: upper 11s linear infinite;
}
#code span:nth-of-type(2) {
  animation: lower 10s linear infinite;
}
#code span: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: 0 0 80px #ffffff,0 0 30px #008000,0 0 6px #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: 0 0 80px red,0 0 30px FireBrick,0 0 6px 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

Отрицательные задержки анимации

Положительная задержка откладывает начало анимации на некоторое время. А отрицательная — начинает анимацию немедленно, но не с самого сначала, а со времени, указанного в задержке. Другими словами, начинает анимацию с какого-то момента внутри её цикла. Это позволяет применять анимацию к нескольким элементам со сдвигом фазы, изменяя лишь время задержки. Вот пример использования такой анимации:

3adeb73124495f1a4368063055f207f01

Код примера:

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

Пропорциональные анимации

Я стараюсь делать веб-приложения настолько адаптивными, насколько возможно. Это касается и CSS-анимаций. Хотя сделать абсолютно все анимации адаптивными невозможно, иногда всё же удаётся использовать вместо абсолютных единиц измерения проценты и другие относительные единицы.

Во многих своих анимациях я использую элементы, размеры которых должны быть пропорциональны размерам страницы. Возможно вы думаете, что для этого я использую фиксированную ширину и высоту. но это не так. Можно указать ширину в процентах, нулевую высоту и процентное значение для padding. С помощью padding-bottom высота элементов остаётся пропорциональной ширине:

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

Если вы откроете следующий пример на Codepen и попробуете изменить размеры окна, вы увидите как это работает. В этом примере также использованы отрицательные задержки анимации.

e54b9f55345a3bdfc78100971193cdc02

Код примера:

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 17s   0.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

Изменение transform-origin посреди анимации

Работая над одной из анимаций, я с удивлением обнаружил, что свойство transform-origin не только может меняться посреди анимации, но и само по себе может быть анимировано. В следующем примере это используется для того, чтобы в одной анимации делать четыре разных поворота вместо того, чтобы создавать четыре отдельные анимации:

7e6c7a6e1efbd2bc8f9473bb18f3d4572

Код примера:

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

У этого трюка есть недостаток: вы не можете использовать animation-mode: forwards; только для части анимации. Это значит, что нам придется передвигать элемент при каждом изменении transform-origin. В этом примере translate используется для имитации эффектов поворота. В более сложных примерах это может оказаться довольно утомительным.

Отрицательный transform-origin

Можно задать отрицательное значение transform-origin, что может быть полезно для создания вращающихся элементов. Вместо того, чтобы отдельно задавать смещение и угол поворота для элемента, как описывает Ли Веру, этого можно добиться проще, используя отрицательные значения transform-origin и дополнительный элемент или псевдоэлемент (или только один элемент, если он не обязан сохранять постоянный угол относительно горизонта). С разными значениями transform-origin можно использовать одну и ту же анимацию для разных элементов:

6dfd54c939e6023ef242c813e6e594961

Код примера:

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

Магия box-shadow

Для анимации простых форм без контента внутри может пригодиться свойство box-shadow. С его помощью можно создавать несколько границ вокруг одного элемента. На основе этой идеи, используя разные смещения для теней, можно создавать целые наборы анимированных фигур на основе одного элемента HTML. Вот пример анимации, которая выглядит, как шесть вращающихся кружков, которые целиком сделаны с помощью box-shadow:

6c7980f1f97fcb9cba56f46b0e9622b51

Код примера:

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 -125px 0 40px #6cce74,
     75px -125px 0 40px #c18d46,
    150px    0px 0 40px #c14745,
     75px  125px 0 40px #2e1e5b,
    -75px  125px 0 40px #9c37a6,
   -150px    0px 0 40px #76bdd1;
  -webkit-animation:rotate 12s infinite linear;
  animation:rotate 12s infinite linear;
}
@keyframes rotate {
  16.67% {
    box-shadow:
      -75px -125px 0 40px #76bdd1,
       75px -125px 0 40px #6cce74,
      150px    0px 0 40px #c18d46,
       75px  125px 0 40px #c14745,
      -75px  125px 0 40px #2e1e5b,
     -150px    0px 0 40px #9c37a6;
  }
  33.33%   { 
    box-shadow:
      -75px -125px 0 40px #9c37a6,
       75px -125px 0 40px #76bdd1,
      150px    0px 0 40px #6cce74,
       75px  125px 0 40px #c18d46,
      -75px  125px 0 40px #c14745,
     -150px    0px 0 40px #2e1e5b;
  }
  50%      { 
    box-shadow:
      -75px -125px 0 40px #2e1e5b,
       75px -125px 0 40px #9c37a6,
      150px    0px 0 40px #76bdd1,
       75px  125px 0 40px #6cce74,
      -75px  125px 0 40px #c18d46,
     -150px    0px 0 40px #c14745;
  }
  66.67%   { 
    box-shadow:
      -75px -125px 0 40px #c14745,
       75px -125px 0 40px #2e1e5b,
      150px    0px 0 40px #9c37a6,
       75px  125px 0 40px #76bdd1,
      -75px  125px 0 40px #6cce74,
     -150px    0px 0 40px #c18d46;
  }
  88.88%   { 
    box-shadow:
      -75px -125px 0 40px #c18d46,
       75px -125px 0 40px #c14745,
      150px    0px 0 40px #2e1e5b,
       75px  125px 0 40px #9c37a6,
      -75px  125px 0 40px #76bdd1,
     -150px    0px 0 40px #6cce74;
  }
  100% {
    transform:rotate(-360deg);
    box-shadow:
      -75px -125px 0 40px #6cce74,
       75px -125px 0 40px #c18d46,
      150px    0px 0 40px #c14745,
       75px  125px 0 40px #2e1e5b,
      -75px  125px 0 40px #9c37a6,
     -150px    0px 0 40px #76bdd1;
  }
}
@-webkit-keyframes rotate {
  16.67% {
    box-shadow:
      -75px -125px 0 40px #76bdd1,
       75px -125px 0 40px #6cce74,
      150px    0px 0 40px #c18d46,
       75px  125px 0 40px #c14745,
      -75px  125px 0 40px #2e1e5b,
     -150px    0px 0 40px #9c37a6;
  }
  33.33%   { 
    box-shadow:
      -75px -125px 0 40px #9c37a6,
       75px -125px 0 40px #76bdd1,
      150px    0px 0 40px #6cce74,
       75px  125px 0 40px #c18d46,
      -75px  125px 0 40px #c14745,
     -150px    0px 0 40px #2e1e5b;
  }
  50%      { 
    box-shadow:
      -75px -125px 0 40px #2e1e5b,
       75px -125px 0 40px #9c37a6,
      150px    0px 0 40px #76bdd1,
       75px  125px 0 40px #6cce74,
      -75px  125px 0 40px #c18d46,
     -150px    0px 0 40px #c14745;
  }
  66.67%   { 
    box-shadow:
      -75px -125px 0 40px #c14745,
       75px -125px 0 40px #2e1e5b,
      150px    0px 0 40px #9c37a6,
       75px  125px 0 40px #76bdd1,
      -75px  125px 0 40px #6cce74,
     -150px    0px 0 40px #c18d46;
  }
  88.88%   { 
    box-shadow:
      -75px -125px 0 40px #c18d46,
       75px -125px 0 40px #c14745,
      150px    0px 0 40px #2e1e5b,
       75px  125px 0 40px #9c37a6,
      -75px  125px 0 40px #76bdd1,
     -150px    0px 0 40px #6cce74;
  }
  100% {
    -webkit-transform:rotate(-360deg);
    box-shadow:
      -75px -125px 0 40px #6cce74,
       75px -125px 0 40px #c18d46,
      150px    0px 0 40px #c14745,
       75px  125px 0 40px #2e1e5b,
      -75px  125px 0 40px #9c37a6,
     -150px    0px 0 40px #76bdd1;
  }
}

Демо на Codepen

К сожалению, box-shadow не поддерживает относительные размеры в процентах, так что их труднее сделать адаптивными, чем обычные элементы. Тем не менее, их размеры можно менять вручную или применяя transform:scale(n) для родительского элемента.

Использование псевдоэлементов

Так же как и box-shadow, псевдоэлементы можно использовать для обогащения внешнего вида элементов HTML. Для них можно использовать отдельные от родительского элемента анимации, они могут иметь отдельные тени — практически как настоящие элементы. Это позволяет делать удивительные вещи:

e2396cdea68ce5d11e21b84fe22fb957

Код примера:

HTML:

<div id='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:
    178px 0 0 -25px black,
    178px 0 0 -20px white,
    -178px 0 0 -25px black,
    -178px 0 0 -20px white, 
    0 0 0 20px black,
    0 0 0 30px white,
    0 0 0 130px black,
    0 0 0 135px 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:
    0 355px 0 -5px black,
    0 355px 0 0px 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:
      178px 0 0 -25px black,
      178px 0 0 -20px white,
      -178px 0 0 -25px black,
      -178px 0 0 -20px white, 
      0 0 0 20px black,
      0 0 0 30px white,
      0 0 0 130px black,
      0 0 0 135px white;
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; 
    box-shadow:
      178px 0 0 -25px white,
      178px 0 0 -20px white,
      -178px 0 0 -25px white,
      -178px 0 0 -20px white, 
      0 0 0 20px black,
      0 0 0 30px white,
      0 0 0 130px black,
      0 0 0 135px 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: 0 355px 0 -5px black, 0 355px 0 0px white; 
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; box-shadow: 0 355px 0 -5px white, 0 355px 0 0px 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:
      178px 0 0 -25px black,
      178px 0 0 -20px white,
      -178px 0 0 -25px black,
      -178px 0 0 -20px white, 
      0 0 0 20px black,
      0 0 0 30px white,
      0 0 0 130px black,
      0 0 0 135px white;
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; 
    box-shadow:
      178px 0 0 -25px white,
      178px 0 0 -20px white,
      -178px 0 0 -25px white,
      -178px 0 0 -20px white, 
      0 0 0 20px black,
      0 0 0 30px white,
      0 0 0 130px black,
      0 0 0 135px 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: 0 355px 0 -5px black, 0 355px 0 0px white; 
  }
  12.5%, 37.5%, 62.5%, 87.5% { 
    background: white; box-shadow: 0 355px 0 -5px white, 0 355px 0 0px 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

В этом примере все концентрические окружности вокруг центрального мигающего круга, так же как и два маленьких кружка на внешнем кольце сделаны с помощью box-shadow. Другие два кружка — это тени псевдоэлемента, а кольцо из штрихов — это фон ещё одного псевдоэлемента, заданный в виде inline SVG.

Несколько советов напоследок

 

Используйте трансформации везде, где только можно

Как показал Пол Айриш и другие, трансформации работают быстрее, чем изменение размеров и положения элементов с помощью свойст topleftwidth и height.

С помощью трансформаций легче реализовать адаптивный дизайн, применяя относительные значения для scale (пример).

Отказ от использования трансформаций приводит к появлению ошибок, которые трудно отловить. Например, вот эта анимация отображается в браузере Chrome с некорректными цветами, хотя значения в коде правильные. После перехода на CSS-трансформации проблема решилась сама собой.

z-index может вызывать проблемы

Пожалуй, на решение проблем с z-index я потратил больше времени, чем на любые другие. Реализация z-index разнится от браузера к браузеру. Главное различие — Mozilla не анимирует z-index, и его значение изменяется скачкообразно, тогда браузеры на основе Webkit умеют изменять его плавно.

Также стоит отметить, что если вам нужно, чтобы псевдоэлементы оказались за родительским элементом, псевдоэлемент должен иметь отрицательный z-index, а родительский элемент при этом обязан располагаться в контексте наложения на месте по умолчанию, то есть к нему нельзя применять z-index или любые другие приёмы, которые вырывают его из стандартного положения в контексте.

И последнее. Любой элемент, для которого задана прозрачность, отличная от «1», получает собственный контекст наложения. Подробнее об этом — в статье Филипа Уолтона.

Ищите источники вдохновения

Что-то в реальном мире, интересная веб-страница, необычный видеоэффект, анимированный gif или что угодно ещё — постоянно ищите вещи, которые стоит попробовать реализовать.

Я обнаружил, что если не подглядывать сразу, как именно сделан тот или иной эффект, можно найти уникальный способ и даже превзойти оригинал. Даже если я терплю неудачу, я всегда по крайней мере узнаю что-то новое о языке программирования, который я использую. Часто получается так, что даже не реализованная полностью задумка оказывается довольно эффектной. Иногда наоборот — результат оказывается намного лучше, чем я мог мечтать.

Надеюсь, что эта статья поможет и вам создать что-то новое и прекрасное, даже многие конкретные техники, описанные в ней, вам уже знакомы.