Якорная ссылка на протеине с анимацией

от автора

Big image in HTML

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

Если вы очень переживаете за показатели Google Page Speed в разработке сайтов, и у вас подгорает за каждый лишний килобайт не стоит продолжать читать данную статью.

Тех же, кого не пугают большие размеры, и любит риск прошу под кат 😉

Задача бизнеса

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

Попытки, которые надо было сделать, что бы прийти к оптимальному варианту:

  1. Самый банальный способ — создаем ссылку из gif файла. У формата есть поддержка анимации, альфа канала и прочее… чего ещё надо?!

    Вариант не подошёл, оказывается, если на картинке много прозрачностей и градиентов, то это сильно портит качество, (gif это всего 256 цветов), ну и размер файла получается 19мБ, что плохо.

  2. Пошёл искать аналог, с хорошей поддержкой. В процессе серфинга попался APNG. Формат из себя представляет png с поддержкой анимации, по кроссбраузерности всё кроме IE11 у него хорошо. Готовый файл стал весить поменьше, (17Мб) но результат оказался всё ещё не удовлетворительный. В процессе анимации наблюдается пиксилизация изображения. Получше чем у gif, но всё ещё видно.

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

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

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

    Особенно если у вас однотонный фон.

    <video class="bl_video" loop="" muted="" autoplay="" poster="poster.jpg">   <source src="topbanner.webm" type="video/webm">   <source src="topbanner.mp4" type="video/mp4; codecs="avc1.42E01E, mp4a.40.2""> </video>  

  4. Что ж перейдем к выбранному варианту, который удовлетворил заявленным условиям задачи. А именно использование свойства:
    animation-timing-function: steps(…)
    Замечательное свойство, к сожалению, редко встречается на практике.;

Первое что необходимо иметь это — секвенция кадров (от англ. «sequence» (последовательность, ряд)), или по-простому – раскадровку. Находим в загашниках аниматоров, или если повезло, просим сделать последовательную раскадровку понравившейся анимации.

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

После этого, нам необходимо собрать всё это дело в спрайт(Один большой совмещенный файл).

Тут кому как удобнее – можете воспользоваться, скажем, плагином для gulp сборщика

gulp.spritesmith

Или же пойти моим путем, и воспользоваться уже готовым приложением компании Toptal, при разработке Chris Coyier генератором спрайтов.

Тут всё предельно просто. В поле padding-ов ставим 0, в поле “Choose files” перетаскиваем наши кадры.

! Важно чтобы элементы были правильно пронумерованы или названы в правильной последовательности;
! Все файлы должны быть одинаковых масштабных размеров.

Получаем готовый результат, который скачиваем.

Css sprites generator

В результате эксперимента было выяснено, что горизонтальный спрайт занимает меньше размер, чем вертикальный составленный из тех же кадров, хотя для меня и осталось это загадкой. (Если кто-то знает ответ, почему такой метод сжатия лучше, напишите в комментариях).

Сразу оговорюсь, тут есть жесткие ограничения по размеру получаемого.
Если ваш спрайт весит > 5Мб, вы автоматом получаете дополнительные проблемы.

Мой сформированный png спрайт получился огромного размера. (7,92Мб)
К сожалению, ни один из онлайн минификаторов изображения не согласился работать с картинкой такого размера. (Чаще всего использую TinyPNG или Squoosh)

Больше того, даже плагин gulp-imagemin поломался с ошибкой –
«Слишком большой размер обрабатываемого файла, умерь свои запросы.»

Ну что ж не беда, значит, сожмем кадры первоисточника по очереди, а потом сделаем спрайт.

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

После компрессии файл стал весить на 1.2мБ меньше, что уже хорошо.

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

.toContactForm_leprechaun {     width: 220px;     height: 290px;     box-sizing: border-box;     background: url(../image/leprechaun-sprite.png) 0 50%;     animation: play 3276ms steps(91) infinite; } @keyframes play {     to {         background-position: -20020px;      } } 

Надо сделать описание, что б всем было понятно, в чём тут магия.

1) Параметрами width и height задаем область видимости одного кадра (напомню, что все кадры у нас имели одинаковые width и height).

2) Задаем в фоновое изображение — адрес расположения спрайта позиционированного слева по оси Х и отцентрированного относительно Y.

3) Пишем вызов анимации. Анимация у нас линейная, бесконечно повторяемая.
Длительность анимации равна коррекционное время * количество кадров.
Значение steps равно количеству кадров.
Коррекционное время подбирается по собственным ощущениям и количеству кадров в наличии, в моем случаи, это в пределах 30мс – 45мс на кадр.

4) Для ключевого описания анимации достаточно прописать только конечное положение анимации.

Считается оно из следующей формулы = (-1px)*количество кадров*width каждого кадра.
*Если вы допустите неточность в данном подсчете, то вы очень быстро увидите свою ошибку. Тут всё должно быть до пикселя.

Собственно анимация готова. Можно продолжать радоваться жизни.

Половина дела сделана, можно идти пить чай. Если у вас простенькая анимация, и размер файла не превышаем 100кБ, не стоит заморачиваться.

Добавляйте ваш якорь как можно ближе к закрывающему тегу body или подгружайте лениво, после того как отрендерился DOM и отработали все нужные скрипты, этого будет достаточно.

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

*Спойлер

Когда выкатил в продакшен вариант, описанный выше, показатель Performance от Google Lighthouse рухнул от 80 до 20 за пару часов.
Так что пока у нас нет общедоступных 5G сетей, а Илон Маск продолжает запускать спутники Neuralink прийдеться что-то выдумывать что бы достигать приемлемых результатов.

Процесс оптимизации слона

Первое что пришлось сделать, это поумерить свою хотелку и пересобрать анимацию с 91кадра до 73кадров. Спрайт остался всё ещё тяжелым (5,17Мб), но с ним, оказывается, может теперь работать Squoosh (Там стоит какое-то ограничение по ширине загружаемого файла <16300px)

Это привело к дилемме, рационально ли пожертвовать пользователями IE и людьми, которые пользуются Safari, но сделать хорошо для всех остальных?
Ответ очевиден. Переводим спрайт в формат webP
(Размер спрайта уменьшился до 2,47Мб Squoosh предлагал ужать и больше, до 1,67Мб, но там уже посыпались артефакты, а нам это не надо);

*Тут внимательный читатель мог бы предложить определять window.navigator.userAgent, а потом отдавать png спрайт как альтернативу обладателям уникальных браузеров, что бы все видели анимацию, но идея не получила одобрения в силу здравого смысла.

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

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

Чтобы избежать данного эффекта, ставим блоку обертке на начальном этапе значение display:none; и только когда изображение загрузилось, показываем его пользователю. Код выглядит приблизительно так.

HTML

<div class="wrapper">   <div class="myAnimation"></div>   <p>Я загрузился!</p> </div>

CSS

body {   margin: 0; }  .wrapper {   display: none;   padding: 10px;   border: 1px solid #000; }  .wrapper.active {   display: block; }  .myAnimation {   display: inline-block;   width: 247px;   height: 187px;   background: url("https://gyazo.com/f5d013014306342a2241f8d3b8fb11ea.png") 50% 50% no-repeat; }  p {   display: inline-block;   width: 150px;   vertical-align: top; }

JS

awaitBgImgLoad(); function awaitBgImgLoad() {   var div = document.querySelector('.myAnimation');   var src = window.getComputedStyle(div).backgroundImage;   console.log(src); // адрес хранится в виде `url("src")` — надо удалить лишние символы      src = src.replace(/url\(|\)|"/g,"")   loadAndRun(src, onload);      /***/      function onload() {     console.log("Я загрузился!");     document.querySelector('.wrapper').style.display = "block";   } }  /*****/  function loadAndRun(src, resolve, reject) {   var img = new Image();   img.onload = resolve;   img.onerror = reject || function(){     console.log("Не загрузилась " + src)   };   img.src = src; } 

Если вам хочется что б ваша анимация загружалась пораньше (не знаю, зачем это может понадобиться), можно добавить в тег ‹head›

<link rel="preload" href=" bigAnimationImg.webP" as="image" />

И вместо генерации и отслеживания копии изображения средствами JS, можно разместить копию данного изображения со значениями стилей:

<img class="animation-image" width="220" height="290" src="bigAnimationImg.webP" loading="eager" alt="animation"/>

.animation-image{ position:absolute; top:0; left:calc(-100%+1px); } 

Где-то перед якорной ссылкой, повыше к основному контенту.

В процессе рендеринга страницы, браузер сначала “увидит” обращение к картинке и пойдет за ней по src, а уже потом из кеша будет подставлять это же изображение в свойство background-image для второго элемента. (Приоритет выдачи картинок для тега img обычно выше, чем для background-image)
* Автор понимает, что это очень экзотический метод обмана браузера, и не рекомендует его к использованию.

С готовой сборкой можно ознакомиться на сайте компании Netgame Entertainment по ссылке.

logo NetGame Entertainment

Атрибуции

Благодарю руководство компании Netgame Entertainment за возможность использования в своей статье материалов компании, а также за предоставление раскадровки персонажа анимации Максимом Черноусом.

Также благодарю OPTIMUS PRIME за подсказку с кодом в оптимизации.

Сделаем web лучше.

ссылка на оригинал статьи https://habr.com/ru/post/492436/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *