[Перевод] Шлифуем CSS-анимацию

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

Использование значений отрицательной задержки


Скажем, у вас есть несколько анимаций, которые запускаются одновременно, и вам нужно выстроить их в шахматном порядке. Можно использовать animation-delay, но вы не хотите, чтобы пользователь при посещении страницы ждал, пока запустятся некоторые недвижимые части.
Можно задать отрицательное значение animation-delay, и это передвинет курсор воспроизведения назад по времени, в результате чего все анимации будут двигаться, когда пользователь зайдет на страницу. Такой вариант подходит, в частности, когда у анимаций одинаковые значения ключевого кадра, а разница в движении заключается только в задержке.

Эту концепцию можно использовать и для отладки. Установите animation-play-state: paused;, после чего задайте различные отрицательные значения задержки. По мере перехода вы увидите кадры анимации в различных состояниях паузы.

.thing {
  animation: move 2s linear infinite alternate;
  animation-play-state: paused;
  animation-delay: -1s;
}

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

.teal {
 animation: hover 2s ease-in-out infinite both;
}
 
.purple {
 animation: hover 2s -0.5s ease-in-out infinite both;
}
 
@keyframes hover {
  50% {
    transform: translateY(-4px);
  }
}

Проблемы значений множественных трансформаций

Для большей эффективности некоторые элементы стоит также двигать и изменять с помощью функции transform, которая поможет вам избежать затрат на перерисовку с полем или сдвигом влево/вправо и т.п. У Пола Льюиса есть отличный ресурс под названием CSS Triggers, в котором такие затраты представлены в виде удобной таблицы. Подводные камни здесь заключаются в том, что если вы попробуете сдвинуть элементы с помощью множественных трансформаций, вы столкнетесь с рядом проблем.

Главная проблема — порядок. Трансформации не будут применяться одновременно, как можно было бы ожидать, и вместо этого они будут появляться в порядке операций. Первой операцией будет крайняя справа, и далее по порядку. Например, в коде, приведенном ниже, сначала будет запускаться масштабирование, потом преобразование, и потом вращение.

@keyframes foo {
 to {
   /*         3rd           2nd              1st      */
   transform: rotate(90deg) translateX(30px) scale(1.5);
 }
}

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

@keyframes foo {
  30% {
    transform: rotateY(360deg);
  }
  65% {
    transform: translateY(-30px) rotateY(-360deg) scale(1.5);
  }
  90% {
    transform: translateY(10px) scale(0.75);
  }
}

Это приведет к немного удивительным и не совсем идеальным результатам. Решением, к сожалению, является использование нескольких вложенных -элементов, с применением к каждому отдельного преобразования, чтобы не возникало конфликтов.

Существуют и другие альтернативы, например, использование матричных трансформаций (которые не являются интуитивными для ручного кода) или использование API JavaScript-анимации, например, GreenSock, в котором нельзя настраивать порядок множественных интерполяций трансформаций.

Использование нескольких div-элементов также может помочь устранить неполадки с SVG. В Safari нельзя задать прозрачность и трансформацию анимации одновременно — одно из значений не будет работать. Обходной вариант показан в этой статье в первом демо.

В начале августа 2015 года определения отдельных трансформаций вошли в Chrome Canary. Это означает, что нам больше не нужно беспокоиться о порядке. Функции rotate, translate и scale теперь можно задавать по отдельности.

Инструменты для настройки хронометража


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

В обоих инструментах используется визуализация Ли Вероу cubic-bezier.com и GUI. Это довольно неплохо, так как вам больше не придется перескакивать туда-сюда из cubic-bezier.com в текстовый редактор для проверки.

Эти инструменты позволяют нам настроить анимацию более интуитивно. Вот как в них выглядит пользовательский интерфейс:

img

И Chrome, и Firefox позволяют контролировать хронометраж (ускорять или замедлять), а также вручную подчищать анимации. Более продвинутые инструменты контроля времени скоро будут представлены в Chrome и смогут рассматривать множество элементов одновременно. Это было бы неплохо, так как работа только с одним элементом анимации за раз — это сильный ограничивающий фактор.

Одна из проблем, с которой я столкнулась — это быстрый захват элемента, если анимация короткая и быстрая. В таких случаях я обычно задаю animation-iteration-count: infinite;, чтобы иметь возможность непрерывно проигрывать анимацию без необходимости воевать со временем.

Также я считают отличным выходом замедлить анимацию, а потом повторно ее проиграть и настроить хронометраж в браузере с помощью этих инструментов. Это позволяет разрывать каждое движение на начальном уровне и смотреть, как все взаимодействует, и как выглядит само движение. Если вы все настроите на такой скорости, тогда вы сможете задать большую скорость, и это будет выглядеть более профессионально.

Отладка событий CSS-анимации с помощью JavaScript


Если вы хотите точно определить, где и когда начинается каждая анимация, можно задействовать немного JavaScript, чтобы определить и обозначить, когда происходит каждое событие, подключив элементы animationstart, animationiteration и animationend.

Демо

Ключевые кадры должны оставаться компактными


Я часто вижу, как люди определяют одно и то же свойство и значение в ключевом кадре на 0% и в ключевом кадре на 100%. Это делать необязательно, и это может привести к более раздутому коду. Браузер возьмет значение свойств как изначальное, а конечное — по умолчанию.

Вот это уже чересчур:

.element {
 animation: animation-name 2s linear infinite;
}
 
@keyframes animation-name {
  0% {
   transform: translateX(200px);
 }
  50% {
   transform: translateX(350px);
 }
 100% {
   transform: translateX(200px);
 }
}

Это должно выглядеть следующим образом:

.element {
 transform: translateX(200px);
 animation: animation-name 2s linear infinite;
}
 
@keyframes animation-name {
  50% {
   transform: translateX(350px);
 }
}

«Сушим» анимацию


Создание красивой и емкой анимации обычно означает написание определенной функции плавности cubic-bezier (). Хорошо настроенная функция плавности работает подобно палитре компании. У вас есть определенный брендинг и «голос» в движении. Если вы используете их по всему сайту (а вам придется, чтобы выдержать все в одном стиле), простейший способ это сделать — сохранить одну или две функции плавности в переменной, точно так же, как мы это делаем в палитрах. SASS и прочие пре/пост-процессоры делают это довольно просто:

$smooth: cubic-bezier(0.17, 0.67, 0.48, 1.28);
 
.foo { animation: animation-name 3s $smooth; }
 
.bar { animation: animation-name 1s $smooth; }


При создании анимации с использованием ключевых кадров CSS мы хотим как можно больше помощи от GPU. Это означает, что если вы анимируете несколько объектов, вам нужен способ легко подготовить DOM к входящему движению и наслоить элемент. Можно аппаратно ускорить родной DOM-элемент (не SVG) с помощью CSS за счет использования стандартного компонента описания. Так как мы повторно используем его на всех элементах, которые мы анимируем, имеет смысл сделать немного описания и дополнительно использовать примеси или наследования:

@mixin accelerate($name) {
 will-change: $name;
 transform: translateZ(0);
 backface-visibility: hidden;
 perspective: 1000px;
}


.foo {
  @include accelerate(transform);
}



Будьте осторожны. Выгрузка слишком большого количества элементов за раз может привести к обратному эффекту и снижению производительности. Большинство анимаций должны быть детальными, но будьте осторожны, если вы используете что-то вроде haml для генерирования огромного количества DOM-элементов.

Циклы для более высокой производительности


На Smashing Magazine недавно публиковали неплохую статью, в которой была представлена работа над фантастическим проектом — Species in Pieces. В одном из разделов автор довольно подробно описывает, как анимация всех деталей за раз привела к проблемам с производительностью. Он пишет:

Представьте себе, что вы двигаете 30 объектов одновременно; вы требуете от браузера слишком многого, и логично, что это может привести к проблемам. Если скорость каждого объекта равна 0,199 секунд, а задержка — 0,2 секунды, проблему может решить смещение только одного объекта за раз. Тот факт, что на самом деле происходит такое же количество движений, не имеет значения: если анимация выполняется по цепочке, производительность сразу же повышается в 30 раз.

Вы можете воспользоваться циклами for в Sass или других пре/пост-процессорах для создания такого функционала. Вот довольно простой вариант, который я написала, чтобы зациклить дочерний элемент nth-child:

@for $i from 1 through $n {
  &:nth-child(#{$i}) {
    animation: loadIn 2s #{$i*0.11}s $easeOutSine forwards;
  }
}

Кроме того, их можно использовать для размещения в шахматном порядке таких визуальных эффектов, как цвет и пр.

Настройка нескольких анимаций в последовательности


При создании более длинных анимаций, выстроить последовательность из нескольких анимаций или событий можно путем связывания их в цепочку с прогрессивными задержками. Например, так:

animation: foo 3s ease-in-out, bar 4s 3s ease-in-out, brainz 6s 7s ease-in-out;


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

animation: foo 2.5s ease-in-out, bar 4s 2.5s ease-in-out, brainz 6s 6.5s ease-in-out;


А теперь давайте добавим еще одну анимацию и снова изменим хронометраж второй (такой вариант настройки происходит практически всегда при создании действительно качественной анимации). Это становится немного неэффективным. Если вы проделаете это более трех раз, это будет действительно неэффективным.

Теперь представьте, что на середине пути два движения анимации должны начинаться одновременно, поэтому вам нужно придерживаться равномерного хронометража с двумя разными свойствами и… ну, вы поняли. Поэтому, каждый раз, когда я выстраиваю в последовательность три или четыре связанных анимации, я перехожу на JavaScript. Лично мне нравится GreenSock animation API, так как в нем очень удобный функционал настройки времени, при этом большинство анимаций JS позволяют легко выстроить анимацию без пересчетов, что определенно хорошо сказывается на рабочем процессе.

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

Платежные решения Paysto для читателей Хабра:

© Habrahabr.ru