Статьи

Невероятно быстрая анимация интерфейса с помощью Velocity.js

Производительность влияет на все. Повышенная производительность — очевидная или реальная — улучшает пользовательский опыт. В свою очередь, улучшенный пользовательский опыт повышает прибыль.

Несколько крупных исследований доказали, что увеличение времени ожидания резко снижает доход. Бинг сообщил, что увеличение задержки на 2000 мс приводит к колоссальному снижению дохода на пользователя на 2%. Точно так же Google обнаружил, что задержка в 500 мс вызывает снижение трафика на 20% .

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

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

Короткий ответ: у разработчиков есть фундаментальное отсутствие понимания веб-производительности. Давайте исследуем.

Ландшафт веб-производительности

С точки зрения дизайна пользовательского интерфейса, нет недостатка в статьях, посвященных достоинствам создания мобильных, отзывчивых сайтов. К настоящему времени разработчики понимают это. И наоборот, с точки зрения производительности пользовательского интерфейса большинство разработчиков признают, что не знают, что делают. Хотя сторонники Google, Mozilla и Microsoft написали бесчисленное множество статей о лучших методах повышения производительности, большинство разработчиков просто не читают их.

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

Но с учетом того, что разработчики уже имеют нагрузку на свои планшеты, нынешний дух предполагает, что среднестатистический разработчик не имеет смысла осваивать этот домен. В качестве реакции на это главный сторонник производительности Интернета, Илья Григорик из Google, недавно написал поэтапный анализ мифов, связанных с производительностью браузера и сетей: высокопроизводительной сетью браузеров. (Дополнительные ресурсы веб-производительности можно найти в нижней части этой статьи.)

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

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

К сожалению, правда в том, что полярная противоположность: во-первых, смартфоны, которые внедряются в развивающемся мире, отстают от производительности айфонов в наших карманах — вы действительно хотите отказаться от создания продуктов для следующих двух миллиардов пользователей, которые подключатся к сети? Если ваша внутренняя реакция звучит так: «Это не моя проблема», будьте уверены, что ваш злой близнец веб-разработчика сидит за тысячу миль от нас, думая о том, чтобы выйти на рынок раньше, чем вы, приложив усилия для разработки решения, которое будет блестящим. быстро даже на маломощных устройствах.

Предстоящая инициатива Firefox OS призвана предоставить способные смартфоны сотням миллионов людей. Будущее уже здесь. Мы не говорим гипотетически. Эрикссон сообщает, что глобальное число подписчиков смартфонов вырастет с 1,9 млрд. До 5,9 млрд. В течение следующих пяти лет — за счет почти исключительно развивающихся стран.

Вторая опасность установки «забей и забывай» для производительности сети заключается в том, что разработчики систематически совершают ошибку, тестируя свои мобильные страницы на устройствах, испытывающих идеальную нагрузку на производительность. Но попробуйте открыть еще пару приложений и веб-страниц. Теперь повторно протестируйте свой сайт. Да, вы только что искусственно воссоздали, имея относительно «древнее» устройство Android 2.3. Кроме того, вы наткнулись на суть нашей второй проблемы: приложения на основе браузера чувствительны к нагрузке на устройство — процессор, графический процессор и использование памяти. Добавьте разнообразие оборудования, и вы начнете приближаться к реальности производительности мобильных устройств: вы всегда должны разрабатывать самый быстрый сайт, а не просто сайт, который отлично работает на вашем iPhone.

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

Веб-анимация Пейзаж

Принимая во внимание, что jQuery, который также является основным инструментом анимации в Интернете, начал разработку в 2006 году, Velocity был построен в 2014 году. Как таковой, он включает в себя последние практические рекомендации по производительности с нуля.

Короче говоря, Velocity — это легкая библиотека манипуляций с CSS с анимационным слоем сверху. Он полностью работает на JavaScript, а не на CSS-переходах. Он предоставляет тот же API, что и jQuery $.animate() , чтобы упростить переход от $.animate() к $.velocity() .

До Velocity ландшафт анимации DOM в основном состоял из jQuery, Transit (библиотека перехода к управлению переходами CSS через JavaScript) и GSAP (первая эффективная библиотека анимации JavaScript).

Вот недостатки этих библиотек:

  • $.animate() jQuery $.animate() медленен и относительно легок в функциях дизайна анимации пользовательского интерфейса — даже в сочетании с пользовательским интерфейсом jQuery.
  • Транзит значительно быстрее, чем jQuery, но еще меньше возможностей, иногда глючит из-за своей природы перелива CSS-переходов через JavaScript, и не поддерживает IE8 и IE9 (которые по-прежнему имеют огромную глобальную долю браузера .
  • GSAP — это полноценная анимационная платформа с огромной мощностью. Его возможности практически безграничны; он оживляет что угодно от DOM до WebGL. (В отличие от этого, Velocity сосредоточена исключительно на том, чтобы быть легким инструментом для существенного улучшения производительности и рабочего процесса анимации пользовательского интерфейса.) В то время как GSAP требует лицензионных сборов для различных типов предприятий, Velocity свободно распространяется с помощью ультрадополнительной лицензии MIT.

Скорость значительно превосходит jQuery на всех уровнях стресса, а Transit начинается на средних уровнях стресса. GSAP работает аналогично Velocity. Сравнения производительности интерфейса пользователя лицом к лицу см. В документации Velocity .

Оптимизация таймера

Мы готовы погрузиться в сочные детали исполнения. Как сделать анимационный движок быстрым? Это микрооптимизация? Нет.

В Velocity нет микрооптимизаций. Это тенденция, которую я убедился, чтобы сопротивляться. Переполнение стека содержит множество сравнений jsPerf.com, которые разработчики из лучших побуждений используют для определения наиболее эффективной реализации на основе JavaScript. Однако разработчики часто становятся жертвами этих сравнений номинальной стоимости, не учитывая их контекст. Если одна реализация уже может достигать нескольких миллионов операций в секунду, не имеет значения, насколько быстрее ее альтернативная реализация. Ваш код JavaScript, скорее всего, никогда не будет работать в масштабе миллионов операций в секунду.

Реальные узкие места в производительности DOM — это прежде всего создание таймеров и манипулирование DOM.

Давайте начнем с анализа создания таймера. Таймеры создаются при использовании setInterval() , setTimeout() и requestAnimationFrame() . Существуют две проблемы с производительностью при создании таймера: 1) слишком большое количество одновременных срабатываний таймеров снижает частоту кадров из-за накладных расходов браузера на их обслуживание, и 2) неправильная отметка времени, в которое ваша анимация начинается, приводит к пропущенным кадрам.

Решением Velocity для первой проблемы является поддержание единого глобального тикового цикла, который циклически повторяет все активные анимации Velocity одновременно. Отдельные таймеры не создаются для каждой анимации скорости. Короче говоря, Velocity отдает приоритет планированию над прерыванием.

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

Напротив, разработчики обычно устанавливают время начала своих анимаций в момент запуска анимации. Однако между моментом, когда пользователь, скажем, нажимает кнопку для запуска анимации пользовательского интерфейса, и моментом, когда фактически начинается результирующий цикл анимации, сторонние факторы (связанная логика JavaScript, дополнительное взаимодействие с пользовательским интерфейсом, общесистемная загрузка, и т. д.) может привести к задержке. Впоследствии, когда цикл анимации, в конце концов, запускается (например, ~ 16-85 мс позже), то, как будет реализовано большинство циклов анимации, приведет к тому, что часть авансовых кадров будет отброшена, чтобы компенсировать несоответствие времени.

Следствием того, что Velocity устанавливает время начала внутри первого тика цикла анимации, а не когда анимация фактически запускается, является то, что анимации могут начинаться примерно через 16-85 мс после их срабатывания. Эта задержка, однако, практически незаметна и в конечном итоге не имеет значения, если, например, вы не создаете игру, которая часто требует точного времени обнаружения столкновений.

Таким образом, в случае анимации пользовательского интерфейса плавность всегда должна быть приоритетом с течением времени.

Минимизация манипуляции с DOM

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

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

Этот феномен снижения частоты кадров, возникающий в результате чередования наборов и приемов DOM, известен как «перебивание макетов».

Браузеры оптимизированы для быстрого пересчета макета DOM, но при одновременном запуске цикла анимации драгоценная каждая миллисекунда и манипулирование DOM — это самый быстрый способ вызвать издержки браузера в диапазоне нескольких миллисекунд (тогда как большинство операций JavaScript завершаются за доли миллисекунды). Чтобы контекстуализировать, насколько чувствителен ко времени цикл анимации, для достижения 60 кадров в секунду (скорость, с которой глаз воспринимает плавное движение), каждый такт в цикле анимации должен завершаться в течение 16 мс (1 с / 60 = 16,666 мс) ,

Скорость идет на все, чтобы минимизировать разрушение макета и манипулирование DOM в целом.

Во-первых, как единственный разработчик Velocity, я уделяю время размещению комментариев по всему источнику Velocity, выделяя каждую оскорбительную строку кода, которая манипулирует DOM. Просто я опрыскиваю /* GET */ и /* SET */ где это уместно. Придерживаясь этой практики, я могу быстро взглянуть на свой код, чтобы убедиться, что новая функция или исправление ошибки не вносят изменения в макет. Я иду по пути кода и вижу, сопровождается ли /* GET */ /* SET */ . Если это так, я либо переделываю путь к пакетным SET и GET вместе (чтобы свести к минимуму общее возникновение обмолота), либо я вообще избегаю реализации этой функции.

Во-вторых, Velocity работает с кэшированными значениями, когда это возможно, так что DOM не нужно запрашивать в начале каждой анимации. Например, Velocity определяет, когда несколько анимаций объединены в цепочку, и повторно использует конечные значения анимации предыдущего вызова Velocity в качестве начальных значений для последующего вызова. Это деликатный процесс, потому что нужно избегать работы с устаревшими значениями любой ценой, иначе анимация может выйти из строя. Velocity устраняет эту неопределенность, помечая каждый инициированный Velocity вызов анимации, а затем избегая кэширования значений, когда обнаруживает, что предыдущий вызов в цепочке не был инициирован Velocity (например, функции jQuery $.queue() или $.fade() были вводится между вызовами Velocity).

Третий, и последний, основной метод минимизации DOM, который использует Velocity, — это «вакуумный» подход к коэффициентам преобразования единиц. Логика преобразования единиц измерения — это код, который определяет единицу измерения в пикселях. Это необходимо, когда вы анимируете ширину элемента, например, до «+25%» — движок анимации должен определить, что это значение в пикселях, чтобы можно было выполнить инкрементальную математику, используя два значения одного и того же типа блока. Почему именно пиксели? Поскольку браузер возвращает значения свойств CSS в пикселях при запросе, независимо от того, какой тип блока был использован для установки свойства.

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

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

Во-вторых, Velocity полностью пропускает конвертацию, когда она не нужна. Например, когда начальное значение свойства равно 0, ноль равен нулю в каждом типе единиц измерения — преобразование не требуется.

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

Тем не менее, открытие, которое я получил во время разработки Velocity, заключается в том, что переразметка компоновки более эффективна, чем методика, которая использовалась до сих пор: выкорчевывание дерева DOM путем клонирования элемента анимации, вставка клона рядом с оригиналом, выполнение преобразования единиц на клон, затем удалив клон в целом. Этот метод желателен, потому что он избегает нетривиального процесса создания вакуума CSS, но он приводит к реструктуризации дерева DOM (влияя на его иерархию посредством вставки и удаления элементов), что в конечном итоге приводит к большим издержкам браузера, чем к чередованию Значения свойств CSS делают. (Я использовал jsPerf.com для подтверждения этих результатов во всех браузерах и на устройствах; как сообщают его сопровождающие, именно здесь jsPerf становится чрезвычайно мощным — когда вам необходимо сравнить истинные узкие места во многих средах.)

Собираем все вместе

Итак, что вы можете сделать со всей мощью Velocity.js?

Обе эти демонстрации работают исключительно в DOM. Нет WebGL. Нет холста.

С точки зрения повседневного веб-дизайна, недавний заметный пример производительности пользовательского интерфейса Velocity можно найти на Everlane.com . Просматривать вокруг; опыт невероятно плавный и отзывчивый.

Как вы только что убедились, DOM и JavaScript работают достаточно быстро. Вы просто должны быть внимательны к рекомендациям по производительности.

Проверьте ресурсы ниже, чтобы узнать больше.

Ресурсы веб-производительности