company_banner

Максимально оптимизированная веб-загрузка изображений в 2021 году

Автор оригинала: Malte Ubl
  • Перевод
  • Tutorial

В этой статье я расскажу про 8 методик оптимизации загрузки изображений, которые уменьшают необходимую пропускную способность сети и нагрузку на процессор при выводе на экран. Приведу примеры аннотированного HTML, чтобы вам было легче воспроизвести. Какие-то методики уже давно известны, а какие-то появились относительно недавно. В идеале, ваш любимый механизм публикации веб-документов (например, CMS, генератор статичных сайтов или фреймворк для веб-приложений) должен всё это реализовывать из коробки.

В совокупности методики оптимизируют все элементы Google Core Web Vitals с помощью:

  • минимизации основных проблем содержимого (Largest Contentful Paint (LCP)) за счёт уменьшения размеров, кеширования и ленивой загрузки;
  • сохранения нулевого накопительного сдвига макета (Cumulative Layout Shift (CLS));
  • уменьшения задержки первого ввода (First Input Delay (FID)) за счёт снижения потребления процессора (для основного потока исполнения).

Чтобы увидеть в действии работу всех методик, посмотрите на исходный код загрузки этого изображения:

https://www.industrialempathy.com/img/remote/ZiClJf.jpg
<img loading="lazy" decoding="async" style="background-size: cover; background-image: none;" src="/img/remote/ZiClJf.avif" alt="Sample image illustrating the techniques outlined in this post." width="4032" height="2268">


Методики оптимизации


Отзывчивый макет


Это понятная методика позволяет изображению занимать доступное горизонтальное пространство с сохранением соотношения сторон. В 2020-м браузеры научились резервировать корректное количество вертикального пространства под изображение до его загрузки, если в элементе img указаны атрибуты width и height. Это позволяет избежать накопительного сдвига макета.

<style>
 img {
   max-width: 100%;
   height: auto;
 }
</style>
<!-- Providing width and height is more important than ever. -->
<img height="853" width="1280" … />

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


Вторая методика сложнее. Новый CSS-атрибут content-visibility: auto говорит браузеру не думать о размещении изображения, пока оно не будет готово. У этого подхода несколько достоинств, главное из которых в том, что пока браузер не получит размытое изображение-заглушку или само изображение, он не будет его декодировать, экономя ресурсы процессора.

Больше не нужен contain-intrinsic-size


В более ранней версии статьи объяснялось, как с помощью contain-intrinsic-size избежать эффекта CLS при использовании content-visibility: auto. Но в Chromium 88 это больше не нужно в случае с изображениями, для которых указаны width и height. По состоянию на 27 января 2021 года content-visibility: auto ещё не реализован в других браузерных движках, вероятно, они последуют примеру Chromium. Так что да, теперь с этим гораздо проще!

<style>
 /* This probably only makes sense for images within the main scrollable area of your page. */
 main img {
   /* Only render when in viewport */
   content-visibility: auto;
 }
</style>

AVIF


AVIF — самый свежий графический формат, который получил поддержку в браузерах. Сейчас он поддерживается в Chromium и по флагу в Firefox. Safari с ним пока не работает, но поскольку Apple является участником группы, разработавшей формат, в будущем этот браузер тоже должен поддерживать AVIF.

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

Для реализации прогрессирующего расширения для AVIF можно воспользоваться элементом picture.

Фактически элемент img вложен в picture. Это может запутать, потому что img иногда называют запасным решением для браузеров, не поддерживающих picture, но по сути этот элемент помогает лишь с выбором src, а своего макета у него нет. Отрисуется именно элемент img, к нему вы и будете применять стиль.

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

<picture>
 <source
   sizes="(max-width: 608px) 100vw, 608px"
   srcset="
     /img/Z1s3TKV-1920w.avif 1920w,
     /img/Z1s3TKV-1280w.avif 1280w,
     /img/Z1s3TKV-640w.avif   640w,
     /img/Z1s3TKV-320w.avif   320w
   "
   type="image/avif"
 />
 <!-- snip lots of other stuff -->
 <img />
</picture>

Загрузка правильного количества пикселей


В приведённом выше коде есть атрибуты srcset и sizes. С помощью селектора w они говорят браузеру, какой нужно брать URL в зависимости от физического количества пикселей, которое потребуется для отрисовки изображения на конкретном устройстве. Это количество зависит от ширины изображения, которая вычисляется на основе атрибута sizes (представляющего собой выражение из медиа-запроса).

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

Запасное решение


Браузерам, которые поддерживают только старые форматы изображений, можно с помощью srcset предоставить больше исходных элементов:

<source
 sizes="(max-width: 608px) 100vw, 608px"
 srcset="
   /img/Z1s3TKV-1920w.webp 1920w,
   /img/Z1s3TKV-1280w.webp 1280w,
   /img/Z1s3TKV-640w.webp   640w,
   /img/Z1s3TKV-320w.webp   320w
 "
 type="image/webp"
/>
<source
 sizes="(max-width: 608px) 100vw, 608px"
 srcset="
   /img/Z1s3TKV-1920w.jpg 1920w,
   /img/Z1s3TKV-1280w.jpg 1280w,
   /img/Z1s3TKV-640w.jpg   640w,
   /img/Z1s3TKV-320w.jpg   320w
 "
 type="image/jpeg"
/>

Кеширование и неизменяемые URL


Встраивайте в URL изображения хеш количества байтов, которые занимает картинка. В примере выше я сделал это с помощью Z1s3TKV. При изменении изображения поменяется и URL, а значит вы можете применять бесконечное кеширование картинок. Кеширующие заголовки должны выглядеть так: cache-control: public,max-age=31536000,immutable.

immutable — семантически правильное значение cache-control, но сегодня оно мало поддерживается браузерами (я смотрю на тебя, Chrome). max-age=31536000 — запасной способ кеширования в течение года. public нужен для того, чтобы ваш CDN кешировала изображение и доставляла его с границы сети. Но этот подход можно использовать только в том случае, если он не нарушает ваших правил соблюдения приватности.

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


Добавив loading=«lazy» в элемент img мы говорим браузеру начать извлекать изображение лишь тогда, когда оно готово быть отрисовано.

<img loading="lazy" … />

Асинхронная расшифровка


Добавив decoding=«async» в элемент img мы разрешаем браузеру расшифровывать изображение вне основного потока, чтобы эта процедура не помешала пользователю. Заметных недостатков у этого решения быть не должно, за исключением того, что оно не всегда применима по умолчанию в старых браузерах.

<img decoding="async" … />

Размытая заглушка


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

https://www.industrialempathy.com/img/blurry.svg


Несколько замечаний по реализации:

  • Заглушка инлайнится как background-image изображения. Эта методика позволяет отказаться от второго HTML-элемента буквально прячет заглушку, когда загружается основное изображение, для этого не нужен JavaScript.
  • URI данных основного изображения обёртывается в URI данных SVG-картинки. Это делается потому, что размытие выполняется на уровне SVG, а не с помощью CSS-фильтра. То есть размытие выполняется однократно для каждого изображения при растеризации SVG, а не для каждого макета. Это экономит ресурсы процессора.

<img
 style="
     …
     background-size: cover;
     background-image:
       url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http%3A//www.w3.org/2000/svg\'

xmlns%3Axlink=\'http%3A//www.w3.org/1999/xlink\' viewBox=\'0 0 1280 853\'%3E%3Cfilter id=\'b\' color-interpolation-filters=\'sRGB\'%3E%3CfeGaussianBlur stdDeviation=\'.5\'%3E%3C/feGaussianBlur%3E%3CfeComponentTransfer%3E%3CfeFuncA type=\'discrete\' tableValues=\'1 1\'%3E%3C/feFuncA%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter=\'url(%23b)\' x=\'0\' y=\'0\' height=\'100%25\' width=\'100%25\'
       xlink%3Ahref=\'data%3Aimage/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAGCAIAAACepSOSAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAs0lEQVQI1wGoAFf/AImSoJSer5yjs52ktp2luJuluKOpuJefsoCNowB+kKaOm66grL+krsCnsMGrt8m1u8mzt8OVoLIAhJqzjZ2tnLLLnLHJp7fNmpyjqbPCqLrRjqO7AIeUn5ultaWtt56msaSnroZyY4mBgLq7wY6TmwCRfk2Pf1uzm2WulV+xmV6rmGyQfFm3nWSBcEIAfm46jX1FkH5Djn5AmodGo49MopBLlIRBfG8yj/dfjF5frTUAAAAASUVORK5CYII=\'%3E%3C/image%3E%3C/svg%3E');
   "
 …
/>

(Опциональная) JavaScript-оптимизация


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

<sсript>
 document.body.addEventListener(
   "load",
   (e) => {
     if (e.target.tagName != "IMG") {
       return;
     }
     // Remove the blurry placeholder.
     e.target.style.backgroundImage = "none";
   },
   /* capture */ true
 );
</sсript>

Дополнительно


Полезный инструмент, реализующий все описанные оптимизации: eleventy-high-performance-blog
Mail.ru Group
Строим Интернет

Похожие публикации

Комментарии 44

    +2
    А что WEBP? Что-то вы его не упомянули? Уже устарел?
      +1

      Я недавно попробовал avif. Экспортировал из последнего GIMP. При уровнях качества около 80-90% изображение содержало заметные артифакты по сравнению с jpeg, а галочка lossless выдала картинку, чей объем на диске чудовищно превосходил исходник в jpeg.

        0

        Может, стоило попробовать конвертировать из PNG (оригинал) в JPEG и AVIF, и уже такие результаты сравнивать? Ведь JPEG -> AVIF предсказуемо наложит только новые артефакты на уже имеющиеся в JPEG. А размер lossless сравнивать тоже с PNG оригиналом, поскольку качества пиксель-в-пиксель в JPEG нет именно из-за размеров.

          0

          lossless оригиналов у меня не было.
          Я пробовал на других PNG. Результаты были разные, кое-какие ужались в объеме очень здорово, а кое-какие… совсем не очень:(
          Но в целом если комбинировать webp и avif по мере надобности, выигрыш хороший.

        0
        Помимо AVIF, там ещё JPEG XL и WebPv2 грядут. Похоже, что намечается небольшая войнушка форматов. Но тут забавно то, что в разработку всех этих форматов вовлечён Google.
          +1
          Не заслужено упустили webP. А по тихому произошло крайне важное событие — уже 92% всех браузеров пользователей поддерживают его. Почти пропал смысл использовать на сайтах png/jpeg
            +1
            У WebP есть проблема: он не всегда лучше JPEG или PNG. Google с ним слишком поспешил. Лучше дождаться JPEG XL, который уже гораздо более продуман. Даже lossless режим для обычного JPEG предусмотрен (то есть существующие JPEG можно дожать без потери качества). Формат JPEG XL недавно был заморожен, идёт стандартизация, разработчики Chromium уже занялись реализацией его поддержки.
              0
              Проблема не в браузерах, как может показаться, а в CMS. Вот когда они начнут из коробки понимать WEBP, AVIF, HEIF… Сейчас просто нет физической возможности пользователю загрузить в тот же Вордпресс картинку в чем-то ином нежели png,gif,jpg,bmp
                0

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


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

              +5
              Самое главное правило забыли: использовать картинку только нужного размера. А то как вставят 4к изображение в элемент 150px шириной — так тут никакая оптимизация скорости не поможет, тут надо, простите, мозги сначала починить.
                –1
                Для этого и есть упомянутый в статье srcset.
                0

                Спасибо за статью. До этого использовал либу, чтобы инициализировать lazy загрузку. А оказывается все уже и без нее работает))

                  0

                  "Cumulative Layout Shift (CLS)" — спасибо, теперь буду знать, что у этого супер-бесячего эффекта есть название.

                    +5
                    верните меня на 10 лет назад, где чтобы отобразить картинку достаточно было написать
                    <img src="..." />
                      0
                      Вот да. Я не понимаю, почему бы просто не изменить поведение IMG по умолчанию, а не заставлять писать десяток аттрибутов, которые теперь везде будут таскать как с писанной торбой.
                        +1

                        А как надо изменить? Чтобы это было управляемо разработчиками? Чтобы это приносило больше пользы чем проблем?

                          0

                          Для начала дефолтным поведением должно быть самое желаемое. То есть ленивая загрузка, отрисовка и подстройка по высоте. Чтобы среднестатистическая вставка картинки была как <img src="..." />, и только в редких случаях использовались изыски с ручным управлением.
                          Во вторых да, управляемость стандартному лези не помешала бы.

                            +1

                            Lazy loading для своей работы требует height и width. Если их не будет, то будет происходить пересчет layout'а слишком часто, а это вызывает проблемы с позиционированием в других местах. А брать эти значения из самого изображения нельзя без загрузки самого изображения, то есть руками все же надо указывать метаинформацию.
                            Но это все ещё не решает проблему с объемом изображения, который сейчас решается через srcset.


                            А что вам надо подстраивать в lazy loading стандартном?


                            Ps, если включить lazy load по умолчанию тогда поломается светлое механизм пикселей аналитики. Так что в ближайшие несколько десятков лет включать это поведение по умолчанию будет просто невыгодно самим браузерам.

                              0
                              А что вам надо подстраивать в lazy loading стандартном?

                              То, на сколько заранее он грузит, ещё что-то там. Сейчас я не связан с фронтендом, но когда эту штуку внедряли, то замена JS cкрипта на нативный лези замедляла скорость загрузки страницы.
                      0
                      Размытая заглушка

                      Зачем в svg прятать png? Почему просто png туда не замостить?
                        0
                        Типа для размытия.
                          0

                          ехал data через data… видит data в реке data

                            0
                            Потому что png микроскопический (9х6) содержит просто набор разноцветных пикселей. А svg используется ради наложения эффекта размытия.

                            У меня другой вопрос. Зачем их инлайново вставлять?

                            И еще возможно лучше вместо png использовать собственно svg для создания цветных пятен, которые необходимо размыть. Или так будет хуже размытие и общий вид заглушки?
                              0
                              Зачем их инлайново вставлять?

                              Чтобы было меньше запросов к серверу. Несмотря на всякие HTTP2, оно всё ещё тормозит.
                              И еще возможно лучше вместо png использовать собственно svg для создания цветных пятен, которые необходимо размыть.

                              А как вы будете создавать такие SVG? PNG делается в пару строчек.
                                0
                                А заглушка предполагается, что будет генерироваться сервером для каждого изображения индивидуально на основе оригинального изображения? Я думал, что речь о какой-то одной для всех изображений заглушке просто для обозначения того, что тут будет загружена картинка и для того, чтоб это выглядело симпатично. Потому счёл, что сделать один файл, который при первой загрузке закешируется будет лучше.
                                  0
                                  В этом и суть, дать представление об изображении. Поэтому пара цветных пятен, которые сделали размытием уменьшенной до 10х10 пикселей оригинала, вполне себе подходят.
                                0

                                А зачем не-инлайново их вставлять?

                              0

                              Интересно сколько десятилетий нужно web-у чтобы сделать <img/> или <picture/> действительно адаптивным. А не то что есть сейчас. Сейчас все эти srcset-ы скачут от размера окна, что само по себе весьма бредовая идея, т.к. она предполагает что вы, размещая картинку, всегда в деталях знаете её точный размер и размер окна. Я даже не знаю где это за пределами блогов\лендингов нужно.


                              В то время как очевидно, что вот такое вот решение:


                              <picture aspectratio="1/2">
                                <sоurce src="..." width="1000"/>
                                <sоurce src="..." width="700"/>
                                <sоurce src="..." width="200"/>
                              </picture>

                              Решило бы 99% всех задач гораздо лучше. У браузера итак есть всё что нужно:


                              • он знает pixel density
                              • он знает размер <img/>
                              • он знает пропускную способность сети

                              А мы в свою очередь можем предоставить ему недостающее:


                              • список файлов и их размеры
                              • aspect ratio (одинаковый для всех файлов)

                              А все эти полумеры с srcset, sizes это прямо какой-то детский сад с очень ограниченным применением (мне пока ни разу не пригодилось, т.к. не было высеченных в скале layout-ов).

                                0

                                Было бы гораздо проще если бы браузер использовал width и height, как пропорции и сам закладывал высоту блока пропорционально, и учитывая max-width: 100% для img

                                  0

                                  Это уже есть, недавно завезли css-свойство aspect-ratio. Моё сообщение совсем о другом.

                                    0
                                    Это уже есть

                                    Этого нет, только CSS костыли.
                                      0

                                      А что не так с CSS-костылями? Ну кроме малой поддержки браузерами на данный момент? Если речь про отсутствие attribute-а, то <img style=" никто не отменял.

                                        0
                                        Самое главное не так это что они вообще нужны. Страницы уже и так на половину состоят из таких вот заклинаний.
                                +2

                                Либо я чего-то не понимаю, но как быть вот с этим при адаптивном дизайне


                                <!-- Providing width and height is more important than ever. -->
                                <img height="853" width="1280" … />

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

                                  0

                                  И еще вопрос про srcset и sizes, какой в этом смысл. Если уже давно число пикселей расчитывается сложнее


                                  Число физических (реальных) пикселей равно числу CSS-пикселей, умноженному на соотношение пикселей (pixel ratio).


                                  Век экранов, у которых число реальных и CSS-пикселей совпадает, давно кончился. Помните, когда-то Apple революционно вышла на рынок с ретина-дисплеями? Это когда pixel ratio — 2. На Samsung Galaxy S6 2015 года соотношение физических пикселей к CSS-пикселям 4! Большинство китайских смартфонов сегодня — это pixel ratio 2-3.


                                  А значит, если у нас на мобилке картинка занимает 360px, нет смысла загружать иллюстрацию шириной меньше, чем 360×2=720px! А обладатель корейского флагмана был бы не против и все 1440px загрузить.


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

                                    0
                                    Это проблема браузера, какую картинку грузить. Указываются CSS-пиксели просто для простоты, а браузер без труда может выбрать картинку как и равную получившимся физическим пикселям, так и самую дешманскую, если я вдруг вышел через 3G.
                                      0
                                      А обладатель корейского флагмана был бы не против и все 1440px загрузить.

                                      Особенно когда кроме EDGE ничего не ловится

                                      0

                                      И w — дескриптор ширины. Это очень удобно, если запомнить, что w — это реальные пиксели, а px — CSS-пиксели, и тогда работа srcset/sizes станет еще понятнее.

                                        0

                                        Обычно вебмастера этого и не понимают

                                        0
                                        loading=«lazy» пока не починят в хроме применять не стоило бы.
                                          0
                                          На хром лучше забить для будущего веба.
                                            0

                                            Браузеры вообще — зло

                                            0

                                            А что с ним не так, простите

                                              +1
                                              В текущей версии не проверял, но в предыдущих, на медленных соединениях, хром грузил 7 экранов с изображениями, вместо одного.

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

                                          Самое читаемое