Привет, Хабр!

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

Мотивация

Основной мотивацией к оптимизации, как и ранее, являются:

  • Уменьшение нагрузки на интернет-канал.

  • Уменьшение потребления CPU при отрисовке изображений.

Также давайте вспомним, что у нас есть метрики Google’s Core Web Vitals (LCP, CLS, FID), на их значения существенно влияет оптимизация работы с изображениями.

Ленивая загрузка

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

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

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

<img src="..." loading="lazy" />

Сегодня атрибут loading поддерживается не всеми браузерами. Поэтому технику ленивой загрузки можно реализовать также с помощью data-атрибутов и JavaScript.

Ленивая отрисовка

Техника ленивой отрисовки тоже может значительно улучшить скорость загрузки вашего приложения. Суть её в том, что браузер отрисовывает DOM-элементы только тогда, когда они необходимы. Это отчасти похоже на вышеописанную ленивую загрузку, только здесь браузер откладывает на будущее не загрузку, а именно отрисовку. Техника применима не только к изображениям, но и к любому DOM-элементу.

При ленивой отрисовке для элементов, которые не попадают во viewport, браузер пропустит этапы Layout и Paint, что позволит сократить time to interactive (TTI).

Прием относительно свежий, так как возник благодаря добавлению в CSS нового свойства — content-visibility. При значении auto свойство информирует браузер о том, что позиционирование изображения в макете можно отложить до тех пор, пока оно не приблизится к отображаемой пользователю области. И по мере того, как пользователь будет листать страницу сайта, браузер будет определять, насколько изображение приближается к viewport. При достижении определенного расстояния DOM-элементы будут отрисованы, после чего будет загружено изображение.

<style>
  img {
    content-visibility: auto;
  }
</style>

Асинхронное декодирование

Рассмотрим такой пример:

<p>какой-то вступительный текст</p>
<img src="very-big.jpg" />
<p>очень важный для пользователя текст</p>

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

Чтобы избежать такой ситуации, можно использовать атрибут decoding co значением async. Это позволит браузеру декодировать изображение вне основного потока, избегая нагрузки на процессор и не блокируя дальнейшую отрисовку DOM-элементов. То есть процесс декодирования изображения будет отложен на будущее, а браузер в свою очередь сможет отрисовать всё содержимое, не дожидаясь загрузки изображения.

<p>какой-то вступительный текст</p>
<img src="very-big.jpg" decoding="async" />
<p>очень важный для пользователя текст</p>

Резервирование вертикального пространства

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

<style>
    img {
        max-width: 100%;
    		height: auto;
    }
</style>

<img width="1280" height="800" src="..."/>

Или, в качестве альтернативы, для резервирования вертикального пространства можно воспользоваться свойством aspect-ratio.

Адаптивность (srcset и sizes)

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

<img 
   src="picture.jpg" 
   srcset=" 
      small.jpg 240w, 
      medium.jpg 300w, 
      large.jpg 720w
   " 
   sizes="(min-width: 960px) 720px, 100vw"
/>

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

Blurry placeholder

Blurry placeholder — это техника резервирования пространства с помощью изображения-заглушки, чтобы дать пользователю некоторое представление об изображении, которое в конечном итоге будет загружено.

Пример Blurry placeholder.
Пример Blurry placeholder.

Одной из рекомендаций по созданию blurry placeholder является использование SVG-изображения в формате data-uri. Это намного практичнее, чем CSS-фильтры для создания эффекта размытия, потому что в данном случае он реализован на уровне самого SVG-файла. В результате это позволяет повысить производительность интерфейса и снизить нагрузку на процессор.

<style>
    img {
        background: cover url('data:image/svg+xml;charset=utf-8,...');
    }
</style>

<img src="..." />

Заключение

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

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


  1. sinneren
    30.09.2021 13:48
    +5

    да всё это фигня, дробью по слону.

    Единственная проблема - ГТМ и яндексметрика. Вот этот булшит просто убивает всё. Без них почти сотка в PS и все метрики зеленые или около того. Как бы они не шли в пост-загрузку, как бы их не оборачивали и не оптимизировали.

    `content-visibility: auto;` добавил на длинную страницу с листингом товаров. Оставил только шапку и 2 товара первые во вьюпорте. tti не изменился вообще. PS вообще не изменился.

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

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

    Сегодня атрибут loading поддерживается не всеми браузерами. Поэтому технику ленивой загрузки можно реализовать также с помощью data-атрибутов и JavaScript.

    В контексте CWV нам пофигу на сафари(и вообще разве маководы не должны страдать?), все данные из хрома и то, 200 случайных.

    При этом CWV это про ранжирование только в гугле, еще и с упором mobile-first. В контексте работы самого DomClick вам не всё равно? У вас сервис с привлечением с рекламы, а не контент из поиска, а по мобилке и вовсе приложение.


  1. johnfound
    30.09.2021 15:18
    +1

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

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


    1. rexen
      30.09.2021 22:00

      Это сродни дилемме "грузить RAM или CPU" ещё с зари всего кодинга. По идее, руководствоваться нужно предпочтениями пользователя. Т.е. настройками браузера. У меня на ПК быстрый Интернет, но слабый проц - lazyloading меня здесь раздражает. На смартфоне - медленный 3G и там отрисовка только вьюпорта - добро, хотя при включении Wi-Fi расклад переворачивается - мне уже не нужно экономить трафик, но зато хочется листать страницы быстро -а значит они должны быть прогружены и отрисованы полностью.


      1. nidalee
        01.10.2021 03:19

        Да, согласен, тут все неоднозначно. Меня, например, до сих пор раздражает форсированный DASH Playback на YouTube, который принципиально не грузит все видео целиком и иногда начинает тупить.
        Я это видео могу скачать со своим каналом за секунды. Вместо этого получаю крохотные чанки на 10 секунд вперед и зависаю на ровном месте. Но я прекрасно понимаю, что многим устройствам это нужно. Однозначного ответа о «правильности» — нет.


      1. johnfound
        01.10.2021 09:17

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


        Я сам использую нетбук на А4-1200 (1GHz) и по моим наблюдениям, отрисовка никогда не является замедляющим фактором. А вот, когда пытаешься прокрутить страницу, а там начнется загрузка картинок (и JS) тогда все зависает в самом неподходящем моменте, когда пользователь активно взаимодействует с программой.


  1. korsetlr473
    30.09.2021 15:32
    +2

    1)

    не понял нужно и <img src="..." loading="lazy" /> и img { content-visibility: auto; } добавлять или только одно? или как?

    2)

    про резерв , если я добавлю фиксированые в css img { max-width: 100px; height: 100px; } это будет работать? или обязательно в html надо указывать?

    3)

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


    1. laisto
      30.09.2021 20:56
      +1

      1. либо css, либо html.

      2. это и есть css.

      3. нет, не нужно.


      1. rexen
        30.09.2021 22:07

        3 - Почему? Если я не ошибаюсь, то опции прогрессивной загрузки (существующие уже не один десяток лет) у этих наших JPEG|GIF|PNG можно выставлять именно что в заголовках самих файлов - при экспорте из ФотоШопа, например.


      1. mrBarabas
        01.10.2021 01:21

        1 пункт, Вы не правы, это разные вещи и применение могут быть абсолютно разными, «лениво» могут грузиться только внешние файлы или фреймы, а content-visibility это больше про блоки и верстку, чтобы не прогружалась условно «ненужная» часть документа при загрузке. А относительно картинок - тут есть две проблемы:

        • Размер, который легко решается новыми форматами и минификацией (в крайнем случае, в большинстве случаев, ещё и небольшим переделыванием дизайна с уменьшением размеров)

        • Временем до получения первого байта (ttfb), а это уже просто так не решить. Отложенная загрузка только один из вариантов, можно инлайнить картинки в документ (растёт размер документа из-за того, что бейз64 «весит» больше, но нет расходов на загрузку), можно использовать СДН (что может привести к еще большей задержке, если сервера медленные и далеко от целевой аудитории), использовать новые фишки, в частности http2, http3 (0-rtt), нормальная настройка https, предзагрузку ресурсов (осторожно, если файлы большие сделаете только хуже) и предзагрузку ДНС (очень помогает, но с СДН не всегда хорошо работает).

          Советы из статьи и даже больше можно увидеть в каждой подобной и в том числе на web.dev от гугла, все эти советы работают, но единственный недостаток их в том, что на каждой конкретной странице проблемы свои и что конкретно делать советы не говорят, то есть обо всем и ничего конкретного, а что выбирать для решения - это решать Вам и решать придётся обычно при помощи Lighthouse в Хроме или в чём-то похожем на WebPageTest. И тут уже на тестах будет становиться понятнее что делать и самое главное всегда нужно понимать, что ни один синтетический тест (даже от гугла) не даст точной информации и доверять можно реальным времени загрузки с реального сервера и времени генерации и загрузки html, из инструментов веб-мастера в браузере (и не только в хроме).


    1. pae174
      30.09.2021 21:57

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

      Это была картинка в формате Progressive JPEG : https://habr.com/ru/post/165645/


    1. sinneren
      01.10.2021 11:19

      По п2 дам нормальный ответ.

      Нужно добавить в атрибуты, т.к. при рендере страницы до загрузки стилей браузеру нужно знать размеры и зафиксировать место под картинку, даже до загрузки картинки. Размеры должны быть примерные как после рендера, обычно мы их знаем, например, карточки товара. Затем уже стилями перекрыть эти размеры на auto\нужное вам по ситуации


  1. Playa
    30.09.2021 21:26
    +2

    Есть способ это всё отключить на стороне клиента, чтобы картинки грузились как в старые добрые? Бесит, потому что при каждом скролле нужно ждать, пока подгрузится очередная картинка.


  1. Exclipt
    01.10.2021 02:28
    +2

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

    Я вот не понял этого, а раньше (20 лет назад), я картинке не мог задать размер? И плевать, что она 5 минут модемом грузится, ничего там сдвигаться не собиралось, если размер явно указан. Я уж молчу про пример с "асинхронным декодированием", прям вот браузер ждет загрузки картинки, а только потом показывает текст за ней? В общем я вообще не понял, что тут написано, старый, наверное, стал.


  1. SubarYan
    01.10.2021 15:08

    content-visibility во многих браузерах не поддерживается. Мы lazyload реализовывали в своем фреймворке через WebSocket и трекаем viewport через JS. Но зато теперь с легкостью подключаем lazyload на любом проекте.


    1. johnfound
      01.10.2021 16:05

      А как если JS отключить?


      1. SubarYan
        01.10.2021 16:15

        Этот вопрос из разряда, а что если ближний свет фар на ночной трассе выключить?


        1. johnfound
          01.10.2021 16:27

          Отнюдь. У меня например JS отключен и включаю его только на очень мало сайтов.


          Если сайт не работает, просто ухожу – примерно в 90% от случаев. Какой-то урон в получением информации не наблюдаю.


          Конечно, персонально я не критерий, но насколько я знаю, хороший тон веб-дизайна включает предоставление более-менее полный контент (но не функционал, конечно) потребителям без JS.


          1. SubarYan
            01.10.2021 16:57
            +1

            Если сайт не работает, просто ухожу – примерно в 90% от случаев. Какой-то урон в получением информации не наблюдаю.

            Прекрасно, можно вообще много чем себя ограничивать.

            Конечно, персонально я не критерий, но насколько я знаю, хороший тон веб-дизайна включает предоставление более-менее полный контент (но не функционал, конечно) потребителям без JS.

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

            Хороший тон это тогда когда где нужно применен JS, а где можно решить задачу на чистом CSS, который на caniuse валиден для большинства браузеров, то конечно нужно делать на CSS.


            1. johnfound
              01.10.2021 18:51

              Так для задач lazyload речь как раз о фунциональности

              Насколько я понял, вы делали собственный content-visibility/lazyload на JS. Поэтому похоже, что без JS никакие изображения не загрузятся.


              А если использовать браузер у которого не поддерживается content-visibility и lazyload, то все загрузиться нормально, только несколько по другому.


              1. SubarYan
                01.10.2021 19:10

                Я не для изображений делал. Я сделал это целиком для DOM-элементов. Чтобы можно было с легкостью сделать подгрузку сначала div'ов в виде каркаса серых блоков, а уже при попадании в область viewport'а отрисовать уже реальный контент. Как например на YouTube сделано. Да много где такое уже реализовано.


                1. johnfound
                  02.10.2021 20:28

                  То есть, выходит у вас еще хуже. Даже контента на будет. Хоть, плашку "Этот сайт не работает без JS" поставили? :D


                  1. SubarYan
                    03.10.2021 00:25
                    +1

                    Чем же хуже? Все современные сайты так работают. Что Facebook , что YouTube.


  1. noodles
    04.10.2021 19:25

    Рассмотрим такой пример:

    <p>какой-то вступительный текст</p>
    <img src="very-big.jpg" />
    <p>очень важный для пользователя текст</p>

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


    Это что-то новенькое? Картинки - это ж не блокирующий ресурс всегда был! Хоть 100мб пусть весит - то что ниже после картинки оно не блокирует, а качается себе в отдельном потоке тихонько.