Привет, Хабр! Представляю вашему вниманию перевод статьи «How to optimize image loading on your website».

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

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

Проблема


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

В примере ниже я создал простой сайт с фоновым изображением, которое весит 4.8Мб. Как вы можете видеть, DOM загрузился за 1.14 секунды, т.е. фактически пользователь видит содержимое через 1.14 секунды, что довольно неплохо для 3G интернета. Однако, фоновое изображение загружается за 27.32 секунды. За это время пользователь может уже уйти с вашего сайта.



И от этого страдает не только пользовательский опыт. В 2010 Google заявили, что скорость загрузки — это один из факторов в их алгоритме ранжирования и я думаю, что в будущем этот фактор будет становиться все более важным.

Решение


Так, как же решить эту проблему? Ну, для начала мы можем просто сжать наше фоновое изображение, используя различные инструменты. Например, TinyPNG, ImageOptimizer или JPEGmini. Это будет легкой победой и уменьшит время загрузки до ~10 секунд. И хотя это выглядит, как огромный шаг в решении, но 10 секунд все еще слишком много.

Следующим шагом может стать загрузка изображения-заглушки перед загрузкой изначального изображения. Заглушка — это версия оригинального изображения, но с низким разрешением. Когда мы создаем такое изображение, то уменьшаем разрешение с 7372x4392 пикселей до 20x11. В итоге размер изображения уменьшается с 4.8Мб до 900 байтов.



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

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

(() => {
  'use strict';
  // Page is loaded
  const objects = document.getElementsByClassName('asyncImage');
  Array.from(objects).map((item) => {
    // Start loading image
    const img = new Image();
    img.src = item.dataset.src;
    // Once image is loaded replace the src of the HTML element
    img.onload = () => {
      item.classList.remove('asyncImage');
      return item.nodeName === 'IMG' ? 
        item.src = item.dataset.src :        
        item.style.backgroundImage = `url(${item.dataset.src})`;
    };
  });
})();


Этот код ищет элементы с классом asyncImage, после чего загружает все изображения, указанные в атрибуте data-src, а как только изображение загрузилось, заменит src для элемента <img>, а для остальных элементов установит фоновое изображения через css.

<div class="asyncImage" data-src="/images/background.jpg">
...
</div>

или

<img class="asyncImage" src="/images/background-min.jpg" data-src="/images/background.jpg" alt="Beautiful landscape sunrise">


Поскольку скрипт удаляет класс у элемента после загрузки изображения, то при желании мы можем сделать красивую анимацию с помощью CSS. Например, ease-in-out анимация позволит сделать исчезновение заглушки, когда заменяется изображение.

Заключение


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



Как вы можете видеть мы загружаем заглушку за 570 миллисекунд, как только она загрузится пользователь будет видеть заблюренную версию с низким разрешением нашего оригинального изображения, а как только загрузится оригинальное изображение, оно заменит версию с низким разрешением.

Посмотреть рабочий пример можно здесь

Ленивая загрузка изображений


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

Ленивая загрузка — это техника, когда изображения за пределами видимой области страницы не загружаются, пока изображение не приблизится к краю видимой области.

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

Комментарии (22)


  1. Denai
    04.01.2020 14:03
    +1

    В посте картинка 7372x4392, в примере уже 3 686px ? 2 196px
    Откуда такие числа? Не лучше ли сделать нормальную картинку для фона и забыть про эти ниндзя-техники?


    1. gkozlenko
      04.01.2020 14:29

      Я так понимаю это сделано в угоду ретине. Но если для мелких иконок это норм, то для больших джепегов это уже перебор.


    1. Grey83
      04.01.2020 14:29
      +3

      Ну как же?!
      Все юзвери смотрят сайт на теликах 50" 4к и будут агриться на любое разрешение фона меньше, чем дефолтное у экрана потому, что смотрят его с расстояния меньше полуметра и видно КВАДРАТИКИ.
      И делают всё это они это через мобильный интернет.

      И ещё желательно делать фоновую картинку повырвиглазней, чтобы текст похуже читался.

      Кстати
      В примере по ссылке вот такие параметры изображения:
      Image properties
      Image type: JPEG (image/jpeg)
      Dimensions: 3686 ? 2196 px
      Screen Size: 728 ? 434 px
      File size: 892.1 kB (892,138 bytes)
      Address: i.imgur.com/EWx6Ao5.jpg
      Т.е. вместо 5МБ уже 1МБ.


      1. Denai
        04.01.2020 14:36

        Все юзвери смотрят сайт на теликах 50" 4к

        Ну да, как же я мог о них забыть)
        Если серьёзно, то для таких маньяков srcset изобрели.


    1. dom1n1k
      04.01.2020 17:35

      Блин, ну это просто демо-пример, для большего контраста. В реальности картинки будут меньше, но зато не одна, а несколько.

      Вот за что действительно автору нужно попенять, так это за слишком современный js-код со стрелочными функциями, шаблонными строками и прочими финтифлюшками. Потому что: подмена картинки на LQIP подразумевает временную поломку контента. Всё что связано с поломкой контента, должно быть железобетонно надежно и работать во всех сколько-нибудь (полу)живых браузерах. Тут не свалишь на graceful degradation или progressive enhancement.


      1. Denai
        04.01.2020 17:40

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


  1. EvilGenius18
    04.01.2020 15:16
    +2

    В этом году появилась автоматическая «ленивая загрузка». Достаточно всего лишь добавить атрибут loading="lazy"

    <img src="image.png" loading="lazy">

    Картинки будут загружаться автоматически когда они попадут в область видимости. И не нужно будет вручную создавать Intersection Observer


    1. dom1n1k
      04.01.2020 17:40

      Штука вроде и хорошая, но:
      а) Нет никакой гибкости управления (это бывает нужно, а бывает и не нужно — когда как).
      б) Поддерживается только в Хроме. IE и FF — черт с ними. Но мы ведь понимаем, что эти техники в первую очередь предназначены для мобильного рынка? Важнейший сегмент яблочных устройств оказывается за бортом.


  1. Get-Web
    04.01.2020 15:38

    Я бы в качестве селектора для поиска атрибута использовал сам атрибут, возможно с другим названием, но так вроде понятнее и не надо лишний класс прописывать:
    const objects = document.querySelector('[data-src]');


  1. nikolau
    04.01.2020 16:09

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


    1. TheGodfather
      04.01.2020 16:22

      Может, я что-то не понимаю, но какая разница, какая картинка будет проиндексирована? title\alt же правильные, поисковики максимум показывают небольшую превьюшку, где низкого разрешения более чем достаточно


      1. nikolau
        04.01.2020 16:54

        Пользователи же по картинкам тоже ищут. Если находят нужные им фото, то переходят на сайт. Вряд-ли их заинтересуют очень низкого качества. Плюс, часто видел как ссылались на мой сайт и вставляли фото с него.


      1. Denai
        04.01.2020 17:13

        Поисковики показывают нормальную картинку и более того позволяют искать с учётом её размеров и по содержимому


  1. maaGames
    04.01.2020 19:35

    А если микро-картинку заинлайнить в html, то ещё быстрее первичная загрузка пройдёт.
    И в png такая малышка вообще 600 байт у меня заняла (я эту же картинку уменьшил и сохранил).


  1. polearnik
    04.01.2020 20:27
    +1

    Еще в комментариях не озвучили способ положить рядом картинку в webp и через вебсервер определять если браузер поддерживает webp то отсылать ему webp версию. Экокномия может быть очень существенной


  1. leahch
    04.01.2020 20:36

    А элемент picture из html5 не подходит?


  1. MorskoyZmey
    06.01.2020 12:14

    Уже давно придумана такая штука, как interlaced image. В большом изображении зашита его мини версия, которую браузер первым делом подгружает и показывает. Вы могли встречать это при сохранении png в photoshop.


  1. OZR
    06.01.2020 17:10
    +1

    LOL, это стёб такой? background фотографией размером в 5мб? Да, сейчас не 2000 год и это изображение не качается несколько ночей с BBS. Зачем ?!!! Если мне действительно нужно это изображение, я на него нажму. Оно никому не нужно просто так. Особенно где-либо с GPRS. Делаем preview {320,640,1024}. И всё решение занимает 2 строчки.

    ffmpeg -i image.png -s 320x240 -c:a copy image_320x240.png
    leanify image320x240.png

    Оптимизаторы [цензура]…

    Так же чуть выше писали про формат webp. Так вот, я потратил некоторое время на оптимизацию изображений в lossless и единственный вывод для себя: Leanify — это круто. Пользуйтесь Leanify. Leanify выше крыши для 99% кейсов с изображениями. webp почти всегда выдаёт изображение с большим весом.

    Всё! Простое и надёжное решение в 2 строчки. Можно ещё третьей строчкой watermark добавить и больше нет никаких проблем с оптимизацией изображений. Больше уже либо Google, либо совсем жёсткий перфекционизм.


    1. dom1n1k
      06.01.2020 23:00

      Если WebP весит больше «почти всегда», то похоже с процессом конвертации что-то не так. В нормальных условиях такое бывает иногда, но обычно WebP весит всё-таки меньше, чем PNG.


      1. drWhy
        07.01.2020 00:13

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


        1. dom1n1k
          07.01.2020 00:55

          Кэп?
          Разумеется, в моем комментарии речь о подходящих для PNG изображениях и lossless WebP. Иные случаи обсуждать бессмысленно.
          Так вот даже при этих условиях WebP выигрывает и весьма ощутимо.


          1. drWhy
            07.01.2020 01:07

            Речь в комментариях выше шла о сжатии фотографий, для чего PNG не подходит никак.