CSS-анимации

На собеседованиях я часто спрашивал, какие CSS-свойства можно анимировать. Почему-то многих он ставил в тупик, кто-то вспоминал отдельные свойства, но сформулировать общее правило не получалось. А ведь всё довольно просто. Что вообще такое «анимация»? Это некое изменение внешнего вида, имеющее промежуточные положения, благодаря чему изменение происходит плавно, а не дискретно. У чего бывают промежуточные состояния? У всего, что можно измерить числами. Это может быть ширина объекта, его отступ, да даже цвет: будь то hex, rgba и пр., это всё числа. И нельзя анимировать display, имеющий определённый набор свойств.

Следующим был вопрос о стоимости анимации, какие же свойства можно использовать со спокойной душой, а с какими надо быть осторожными. Здесь достаточно запомнить три свойства: transform, opacity и filter (c которым не всё так просто).


Если у нас есть некий элемент, который мы хотим подвигать туда-сюда, то почему же его двигать лучше при помощи transform, а не left, margin или чего-то ещё?

Дело в том, что изменение большинства свойств вызывают рендеринг, в то время как при transform элемент не меняет своего положения в сетке, рендеринг не запускается. Причём элемент может двигаться сам по себе в своём контейнере, никак фактически не влияя на другие узлы в дереве, браузеру всё равно это надо пересчитать. Но насколько это важно и критично, можно ли это пощупать и измерить? Сделал пару простых экспериментов.

Возьмём сто элементов. Хотя нет, возьмём тысячу. И напишем странных вещей на SCSS:

.item {
  width: 50px;
  height: 40px;
  background-color: rebeccapurple;
}

@for $i from 1 through 20 {
  .item:nth-child(20n + #{$i}) {
    animation: left_#{$i} #{5 - $i/8}s ease-in-out infinite;
  }

  @keyframes left_#{$i} {
    0%, 100% {
      margin-left: calc(50px * #{$i - 1});
    }

    50% {
      margin-left: 1000px;
    }
  }
}

Используя вкладку Performance в DevTools, получаем такую картинку:

Из 3067 мс рендеринг занял 1469 мс, около 48%.

Теперь включим троттлинг процессора, а на вкладке Rendering выберем Paint flashing (дабы подсветить области на странице, которые необходимо перерисовать). Постоянный рендер вызывает постоянную перерисовку, анимация откровенно подтормаживает.

Меняем margin-left на transform: translateX, замеряем:

Всего лишь 66,5 мс, посколько рендеринг был только в самом начале, а далее в основном холостой ход, не пожирающий ресурсы.

Оба видео по 15 кадров в секунду, поэтому второй случай не столь гладкий, как в жизни, но здесь суть в сравнении.

Аналогично дело обстоит с другими трансформами. Если нужно анимировать width, height, то посмотрите, нельзя ли это сделать при помощи scaleX, scaleY. Более того, использование scale для текста вместо font-size тоже имеет смысл и может быть оправданно с точки зрения производительности, а разница в плавности становится очевидной и без троттлинга.


Может казаться, что одно дело, когда мы меняем какие-то физические размеры и местоположение элемента, но совсем другое дело, если работаем с цветом, например, с фоном блока, неужели это может вызвать проблемы? Можем ли мы воспользоваться альфа-каналом? Сравним.

@keyframes color_#{$i} {
  0%, 100% {
    background-color: rgba(102,51,153,1);
  }

  50% {
    background-color: rgba(102,51,153,0);
  }
}

@keyframes opacity_#{$i} {
  0%, 100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}
Слева — rgba, справа opacity, комментарии излишни.

А с filter немного сложнее, поскольку там всё зависит от конкретного фильтра и браузера. И если, скажем, grayscale отрабатывает без проблем, то анимация нескольких свойств в drop-shadow уже тяжелее, и не только начинает вызывать рендеринги, но и знатно подвешивает браузер в целом. Так что надо тестировать и разбирать конкретные случаи.

{{ message }}

{{ 'Comments are closed.' | trans }}