10 принципов плавных веб-анимаций
Полное руководство по анимациям 60 кадров в секунду на CSS

С тех пор как в прошлом году мы запустили Gyroscope, многие спрашивали, какую JavaScript-библиотеку мы используем для анимаций. Мы думали выложить её в открытый доступ, но волшебство на самом деле не в ней.
Нам не хочется, чтобы люди чувствовали зависимость от какого-то особого JavaScript-плагина, который магически решает эти задачи. По большей части мы просто пользуемся тем, как за последнее время выросла производительность браузеров, видеокарт и возможности спецификации CSS3.
Серебряной пули для хороших анимаций нет, кроме как потратить много времени на тесты и оптимизацию. Но за годы экспериментов и упирания в потолок производительности браузеров у нас сложился набор принципов дизайна & кода, которые надёжно дают приятные анимации. С этими приёмами страницы ощущаются плавными, работают в современных десктопных и мобильных браузерах и, что важнее всего, легко поддерживаются.

Технологии и реализация у каждого будут немного свои, но общие принципы должны помочь почти в любой ситуации.
Что такое анимация?
Анимация появилась задолго до интернета, и делать её хорошо можно учиться всю жизнь. Но у анимации для интернета есть свои особые ограничения и сложности.
Чтобы шло плавно на 60 кадрах в секунду, каждый кадр должен отрисоваться меньше чем за 16 мс! Это совсем немного, поэтому нужно искать очень экономные способы рисовать каждый кадр.
Способов сделать анимацию в вебе десятки. Например, киноплёнка, подход, существовавший ещё до интернета: чуть отличающиеся нарисованные от руки кадры сменяются много раз в секунду и создают иллюзию движения.
Twitter недавно применил именно этот простой подход для своей новой анимации сердечка, пролистывая спрайт с кадрами.

Этот эффект можно было бы сделать кучей мелких элементов, анимируемых по отдельности, или, скажем, в SVG, но это было бы неоправданно сложно и, вероятно, менее плавно.

Во многих случаях вам захочется использовать CSS-свойство transition, чтобы элемент анимировался сам, когда меняется. Этот приём ещё называют «твинингом», от английского transition between двумя разными значениями. Его плюс в том, что его легко отменить или развернуть назад, не строя всю эту логику руками. Идеально для анимаций по принципу «включил и забыл»: вступительные последовательности и тому подобное или простые взаимодействия вроде наведения.
Почитать дальше: All you need to know about CSS Transitions

В других случаях лучше подойдёт CSS-свойство animation с ключевыми кадрами: для постоянно работающих фоновых деталей. Например, кольца в логотипе Gyroscope крутятся непрерывно. Синтаксис CSS animation пригодится и для таких вещей, как передаточные отношения шестерёнок.
Итак, без лишних предисловий, несколько советов, которые, надеюсь, сильно поднимут производительность ваших анимаций…
#1
Не меняйте ничего, кроме opacity и transform!
Даже если вам кажется, что вот тут можно, нельзя!
Один только этот базовый принцип даст вам 80% результата, даже на мобильных. Вы наверняка уже это слышали: идея не новая, но ей редко следуют. Это веб-эквивалент «питайтесь правильно и занимайтесь спортом»: совет вроде хороший, но вы его, скорее всего, пропускаете мимо ушей.
Когда привыкаешь так думать, всё довольно просто, но для тех, кто привык анимировать обычные CSS-свойства, это может быть большим скачком.
Например, если нужно сделать что-то меньше, используйте transform: scale() вместо изменения width. Если нужно что-то подвинуть, вместо возни с margin или padding, из-за которой раскладку всей страницы придётся пересчитывать на каждом кадре, достаточно простого transform: translateX или transform: translateY.
Почему это работает?
Человеку кажется, что изменить width, margin или другое свойство, дело нехитрое, а то и предпочтительное, ведь оно проще. Но с точки зрения того, что приходится делать компьютеру, это два разных мира, и один из них гораздо, гораздо хуже.
Команды браузеров проделали огромную работу по оптимизации этих операций. Трансформации очень легко выполнять эффективно, и они часто задействуют вашу видеокарту, не перерисовывая элементы.
При первой загрузке страницы можно отрываться как угодно: скруглять все углы, ставить картинки, вешать тени на всё подряд, а если вы совсем без тормозов, то и динамическое размытие. Если это происходит один раз, лишние несколько миллисекунд расчётов не важны. Но когда содержимое отрисовано, пересчитывать всё снова и снова не стоит.
Почитать дальше: Moving elements with translate (Paul Irish)
#2
Прячьте содержимое на виду.
Скрывайте элементы через pointer-events: none вместе с нулевой прозрачностью
Здесь могут быть оговорки по разным браузерам, но если вы делаете под webkit и другие современные браузеры, жизнь станет намного проще.
Давным-давно, когда анимации приходилось делать через jQuery animate(), львиная доля сложности плавного появления и исчезновения была в том, чтобы вовремя переключить display: none на block. Слишком рано, и анимация не доиграет, слишком поздно, и по странице разложено невидимое содержимое с нулевой прозрачностью, перекрывающее всё остальное. Всюду нужны были колбэки, чтобы прибираться после анимации.
CSS-свойство pointer-events (оно существует уже довольно давно, но используется нечасто) попросту делает элементы невосприимчивыми к кликам и взаимодействиям, как будто их и нет. Оно легко включается и выключается через CSS, не прерывая анимаций и никак не влияя на отрисовку и видимость.
В связке с нулевой прозрачностью эффект получается почти как у display: none, но без просадок производительности от новых отрисовок. Скрывая что-то, я обычно просто ставлю opacity в 0 и выключаю pointer-events, а дальше могу забыть про элемент: он позаботится о себе сам.
Особенно хорошо это работает с абсолютно спозиционированными элементами, потому что вы уверены, что они ни на что другое на странице не влияют.
А ещё это даёт вам свободу в мелочах: тайминг не обязан быть идеальным. Ничего страшного, если элемент кликабелен или перекрывает что-то на секунду дольше, чем он был виден, или если он становится кликабельным только после того, как полностью проявился.
#3
Не анимируйте всё одновременно.
Лучше выстраивайте хореографию.
Отдельная анимация сама по себе может идти плавно, но вместе с десятком других она, скорее всего, посыплется. Сделать простое демо, где что угодно работает плавно, легко, а вот удержать эту производительность на целом сайте на порядок сложнее. Поэтому важно правильно их расписать по времени.
Разнесите тайминги так, чтобы всё не начиналось и не шло ровно в один момент. Обычно 2-3 вещи могут двигаться одновременно без замедления, особенно если они стартовали чуть в разное время. Больше, и вы рискуете получить рывки.
Если на ваших страницах не одна-единственная вещь, важно понимать, что такое хореография. Слово вроде бы танцевальное, но для анимации интерфейсов оно так же важно. Элементы должны появляться с правильной стороны и в правильный момент. Пусть они и отдельные, ощущаться они должны как части одного продуманного целого.
В material design от Google есть интересные соображения на эту тему. Это не единственный правильный способ, но об этом стоит думать и это стоит проверять.

Почитать дальше: Google Material Design · Motion
#4
Понемногу растущие задержки перехода делают хореографию простой.
Хореография анимаций очень важна, и чтобы она села как надо, понадобится много экспериментов и тестов. Но код для неё вовсе не обязан быть сложным.
Обычно я меняю один класс у родительского элемента (часто у body), и это запускает пачку переходов, у каждого из которых свой transition-delay, чтобы он вступил в нужный момент. С точки зрения кода вам нужно думать об одной смене состояния, а не поддерживать десятки таймингов в JavaScript.

Сдвигать элементы лесенкой, простой и лёгкий способ выстроить хореографию. Он хорош тем, что одновременно и выглядит здорово, и покупает вам драгоценную производительность: помните, одновременно должно происходить лишь несколько вещей. Разнесите их достаточно, чтобы каждая ощущалась плавной, но не настолько, чтобы всё вместе казалось медленным. Пересекаться они должны ровно настолько, чтобы это ощущалось непрерывным потоком, а не цепочкой отдельных событий.
Пример кода
Есть пара простых приёмов, чтобы разнести элементы лесенкой, особенно если это длинный список. Если элементов меньше 10 или их количество вполне предсказуемо (как на статической странице), я обычно задаю значения прямо в CSS. Так проще всего и легче всего поддерживать.

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

Обычно переменных две: базовая задержка и промежуток между элементами. Найти баланс непросто, но когда вы попадаете в нужные числа, ощущение будет идеальным.
#5
Используйте глобальный множитель, чтобы проектировать в замедленной съёмке
А потом всё ускорьте.
В дизайне анимации тайминг решает всё. 20% работы уйдёт на то, чтобы что-то реализовать, а остальные 80%, на то, чтобы подобрать параметры & длительности, при которых всё синхронно и плавно.
Особенно когда вы работаете над хореографией нескольких элементов и пытаетесь выжать из страницы производительность и одновременность, увидеть всё это в замедлении сильно упрощает дело.
Пользуетесь ли вы JavaScript или каким-нибудь CSS-препроцессором вроде SASS (мы его обожаем), добавить немного арифметики и собирать всё на переменных несложно.
Позаботьтесь, чтобы менять скорость и тайминги было удобно. Например, если анимация дёргается даже на скорости 1/10, что-то, вероятно, устроено принципиально неправильно. Если растянутая в 50 раз она идёт гладко, остаётся найти максимальную скорость, на которой она ещё работает. На полной скорости 5-миллисекундную проблему заметить сложно, а если всё замедлить, она станет очевидной.
Особенно для очень сложных анимаций или для поиска хитрых узких мест возможность смотреть на всё в замедлении бывает очень полезной.
Идея в том, чтобы, пока всё идёт медленно, набить это множеством выверенных деталей, а потом ускорить целиком, чтобы ощущалось идеально. Разница очень тонкая, но пользователь заметит и плавность, и детали.
Кстати, такая возможность есть в OS X: если нажать на кнопку сворачивания или иконку приложения с shift, вы увидите анимацию в замедлении. В своё время мы даже сделали такое замедление в Gyroscope, оно включалось по нажатию shift.
#6
Снимайте свой интерфейс на видео и пересматривайте: взгляд со стороны очень ценен.
Иногда другая точка зрения помогает увидеть вещи яснее, и видео для этого отлично подходит.
Некоторые собирают ролик в After Effects и потом пытаются повторить его на сайте. Я часто иду в обратную сторону и стараюсь сделать хорошее видео из интерфейса сайта.
Выложить Vine* или видео, довольно высокая планка. Однажды я был в восторге от того, что построил, и попробовал записать это, чтобы показать друзьям.
Но, пересмотрев запись, я заметил кучу неудачных мест. Там был заметный рывок, и все тайминги оказались чуть-чуть не те. Мне стало неловко, и вместо того чтобы отправить видео, я понял, что работы ещё много.
В реальном времени такое легко не заметить, но на видео, пересматривая снова и снова или в замедлении, любые проблемы становятся кричаще очевидными.
Говорят, камера прибавляет 10 фунтов. Возможно, она прибавляет и 10 кадров.
Теперь смотреть замедленные видео своих страниц и править всё, что в кадрах ощущается не так, стало важной частью моей работы. Легко списать это на медленные браузеры, но с дополнительной оптимизацией и тестами все эти проблемы решаемы.
Когда вам уже не стыдно за пойманные на видео рывки и кажется, что видео не стыдно показать, страница, скорее всего, готова к выпуску.
#7
Сетевая активность может вызывать тормоза.
Большие HTTP-запросы стоит подгружать заранее или откладывать
Главные виновники здесь, картинки: несколько больших (например, крупный фон), или множество мелких (представьте 50 загружающихся аватарок), или просто много содержимого (длинная страница с картинками до самого футера).
Пока страница загружается, инициализируется и скачивается огромное количество всего. Аналитика, реклама и другие сторонние скрипты делают только хуже. Иногда задержка всех анимаций на пару сотен миллисекунд после загрузки творит с производительностью чудеса.
Не оптимизируйте это сверх меры, пока не понадобится, но сложной странице для плавной работы могут потребоваться очень точные задержки и тайминги. В целом в начале стоит загружать как можно меньше данных, а остальную страницу догружать, когда тяжёлая работа и вступительные анимации закончились.
На страницах с большим объёмом данных загрузка всего может быть весьма затратной. Анимация, которая хорошо работает на статическом содержимом, может рассыпаться, как только вы начнёте одновременно грузить реальные данные. Если что-то вроде бы должно работать или иногда идёт плавно, а иногда нет, я советую посмотреть на сетевую активность и убедиться, что вы не делаете параллельно ещё что-то.
#8
Не привязывайтесь напрямую к прокрутке.
Идея кажется классной, но на деле она так себе.
Анимации на прокрутке за последние годы стали очень популярны, особенно с параллаксом и прочими спецэффектами. Хороший ли это дизайн, вопрос спорный, но технически их можно реализовать лучше или хуже.
Умеренно производительный способ в этой категории, считать достижение определённой позиции прокрутки событием и запускать что-то один раз. Если вы не очень хорошо понимаете, что делаете, я бы советовал этой категории избегать: ошибиться легко, а поддерживать очень тяжело.
Ещё хуже, писать собственную прокрутку вместо стандартной, то есть перехватывать скролл. Пожалуйста, не надо так.
Это одно из тех правил, которые особенно полезны на мобильных, но и вообще хорошая практика ради идеального пользовательского опыта.
Если вам всё же нужен особый опыт, построенный на прокрутке или каких-то специальных событиях, я советую сначала собрать быстрый прототип и убедиться, что он работает хорошо, прежде чем тратить много времени на дизайн.
#9
Тестируйте на мобильных рано & часто.
Большинство сайтов делают на компьютере и чаще всего проверяют на той же машине, где их пишут. Поэтому мобильный опыт & производительность анимаций часто остаются на потом. Некоторые технологии (например, canvas) или приёмы анимации на мобильных работают хуже.
Но при правильном коде & оптимизации (см. правило #1) на мобильном может быть даже плавнее, чем на компьютере. Когда-то оптимизация под мобильные была очень непростой темой, но новые iPhone теперь быстрее большинства ноутбуков! Если вы следовали предыдущим советам, мобильная производительность вполне может получиться отличной сама собой.

Мобильные, большая и очень важная часть почти любого сайта. Совет может показаться радикальным, но я бы предложил целую неделю смотреть на сайт только с телефона. Пользоваться мобильной версией не должно ощущаться как наказание, но часто именно так и выходит.
Улучшайте дизайн & производительность, пока она не станет такой же вылизанной и удобной, как большая версия сайта.
Если вы заставите себя неделю пользоваться только мобильной версией, вы, скорее всего, доведёте её до состояния, когда она даже лучше большой. Раздражение от ежедневного использования того стоит, если благодаря ему проблемы будут исправлены до того, как на них наткнутся пользователи!
#10
Тестируйте почаще на разных устройствах
Размер экрана, плотность пикселей и само устройство могут всё изменить
Кроме «мобильный или десктоп» есть множество факторов, резко влияющих на производительность: «ретина» экран или нет, общее число пикселей в окне, насколько старое железо и так далее.
Хотя Chrome и Safari оба построены на Webkit и синтаксис у них похожий, у каждого свои причуды. Каждое обновление Chrome что-то чинит и приносит новые баги, так что расслабляться нельзя.
Разумеется, делать всё под самый слабый вариант тоже не хочется, поэтому очень полезно придумывать разумные способы постепенно добавлять или убирать украшения.
Я постоянно переключаюсь между крошечным MacBook Air и огромным iMac, и каждый круг вскрывает мелкие проблемы и поводы что-то улучшить, особенно в производительности анимаций, но и в общем дизайне, плотности информации, читаемости и прочем.
Медиазапросы, очень мощный инструмент для этих разных сегментов: обычно их применяют, чтобы менять стили по высоте или ширине, но их можно нацелить и на плотность пикселей или другие свойства. Полезно бывает и определить ОС и тип устройства, ведь по производительности мобильные сильно отличаются от компьютеров.

Надеюсь, эти приёмы пригодятся вам в следующем проекте. Удачи!


