Анимация против лагов, или лучшая битва та, которой не было

в 12:31, , рубрики: javascript, анимация, веб-дизайн, Веб-разработка, лаги

image

Замечали, когда только открываете веб-сайт, первые секунды всё тормозит? Скрол как-то не ровно работает, параллаксы скачут, а из анимации словно вырезали львиную долю кадров. Но очень скоро всё нормализуется. Не замечали такого? Взгляните на демо-страницу плагина, и сразу поймёте, о чем я.

Проблема в том, что динамика не может нормально работать, пока страница лагает. В качестве решения предлагаю плагин «Afterlag.js». Плагин позволяет отслеживать событие окончания лагов. Лаги прошли, включайте анимацию, тормозить не будет. А пока страница лагает, нечего и динамику запускать, только вид портить.

Как использовать

Подключаете JS файл с плагином, пишете:

$.afterlag(function() {
  console.log(‘Ура, лаги прошли!’);
});

Как только на странице кончатся лаги, ваша консоль начнёт выражать радость по этому поводу. Если jQuery использовать не хотите, подключите нативный плагин и напишите:

afterlag = new Afterlag(); 
afterlag.do(function() {
  console.log(‘Ура, лаги прошли!’);
});

Результат будет тот же.

Это самое простое, что может сделать плагин. На самом деле у плагина есть API и куча других способов вызова, всё это в полном объеме описано в ридми репозитория на гитхабе. Там же и способы подключения, ссылки на CDN, название плагина в bower и модуля npm.

Почему всё тормозит

Давайте разберёмся, почему анимация лагает на старте. Рассмотрим анимацию кругляшка на демо-странице, его задача плавно подниматься вверх, затем опускаться вниз.

Анимация движения реализована с помощью метода animate библиотеки джэйквери. Анимируем мы единственный CSS атрибут top, его значение определяется заданной функцией от времени. Запустив анимацию, джэйквери старается каждое мгновение обновлять значение top. Если бы у джэйквери действительно получалось обновлять это значение так часто, то анимация была бы шикарно гладкой. Но у jQuery не получается.

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

Так вот, джэйквери хотела почаще обновлять значение top для нашего кругляшка. Но в момент загрузки страницы было очень много более приоритетных дел: страничку отрендерить, видео с ютуба подтянуть и так далее. Бедняга браузер был так занят, что успевал обновлять значение top только раз в 200–300 миллисекунд. За это время кругляшок исходя из заданной функции смещался уже пикселей на 60. В итоге круг не плавно подходит к своему новому положению, а телепортируется туда, создавая ощущение потерянных кадров, дёрганости и тормознутости. Потом, когда браузер доделал все свои важные дела, он начал своевременно обновлять значение top для круга, и анимация стала ровной.

Как работает «Afterlag.js»

Афтерлагу предстоит узнать, когда браузер перестанет быть таким занятым и сможет достаточно часто выполнять код, необходимый для гладкого воспроизведения анимации. Скажем, нас устроит, если задача анимации будет исполняться хотя бы раз в 30 миллисекунд (frequency). При инициализации афтерлаг запомнит текущее время и запустит интервал в 30 мс. Спустя 30 мс опять узнаем текущее время. Сравниваем текущее время с прежде зафиксированным, если действительно прошло 30 мс, значит лаги кончились. А если на самом деле прошло не 30 мс, а, скажем, 100 мс, значит на странице всё еще лаги и процедуру надо повторять до тех пор, пока интервал не начнёт работать так, как мы этого ожидаем.

Только я написал плагин по указанной выше схеме и решил, что он достаточно работоспособный. Но нет. Случается такое, что интервалу просто повезло. Бывало так, что первые две итерации ожидаемое значение прошедшего времени разнилось с реально прошедшим, на третью итерацию совпадало, а на четвертую опять разнилось. Решаем задачу в лоб: пускай ожидаемое время совпадёт с реально прошедшим 3 раза подряд (iterations).

«Ну вот, теперь всё наверняка работает как надо» —подумал я, и ошибся. В первые моменты могло случится такое, что браузер еще не был занят первые 90 мс, то есть 3 итерации по 30 мс, афтерлаг решал, что лаги кончились, а они на самом деле только начинались. Решаем опять в лоб: установим значение времени в течение которого нельзя доверять афтерлагу (delay) — 100 мс.

Теперь всё работает как надо. Есть еще один параметр, который я учел сразу, но решил сказать только сейчас. Это допустимая погрешность при сверке ожидаемого и реально прошедшего времени (scatter) — 5 мс. Не будем с браузером очень уж строгими.

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

Заключение

Анимация, реализованная при помощи CSS, гораздо меньше подвержена подобного рода лагам. Афтерлаг полезен только тогда, когда динамика сайта связана с JS, в том числе плавные параллаксы, анимация передвижения скрола и прочее. Мне было бы интересно узнать ваше мнение о полезности плагина.

Афтерлаг чаще всего срабатывает верно, но не даёт 100% гарантии. При изменении настроек приходится ловить баланс: большая надёжность и более долгое ожидание или меньшая надёжность и более короткое ожидание. Плагин придётся по душе тем разработчикам, которые любят, когда динамика работает красиво и так, как она была задумана.

Автор: iserdmi

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js