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

Содержание:

  • Исходные данные и выбор основного формата

  • Изменение размеров

  • Оптимизация

  • WebP

  • Тег picture и srcset

  • Предзагрузка

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

  • Сдвиг макета

  • Object-fit

Исходные данные и выбор основного формата

Для того, чтобы статья была наглядной и последовательной, я взял из одного из проектов моей команды картинку размером 1600 x 484 пикселей. Оригинал в png весит 1,1 Mб, а оригинал в jpg весит 807 Kб. Здесь впору задать себе вопрос, нужен ли альфа-канал? Если нужен, берём png, в противном случае берём jpg, так как он меньше весит, а качество визуально идентично в подавляющем большинстве случаев. Для следующих двух пунктов нам потребуется инструмент для изменения размеров и оптимизации изображений. Существует колоссальное количество инструментов как онлайн, так и десктопных. Лично я использую iLoveIMG, но вас, разумеется, это никак не должно ограничивать. Для конвертации исходных изображений в формат WebP использую cloudconvert.

Изменение размеров

Начнём с того, зачем менять разрешение и когда это необходимо. Благодаря этому мы можем сделать картинку легче и тем самым повысить скорость её загрузки. Если мы не собираемся использовать нашу картинку как фон во весь экран, а, например, хотим показать в блоке с размерами 400x121, то необходимо уменьшить её до размера (400x121)*2, чтобы картинка не стала выглядеть хуже на дисплеях с увеличенной плотностью пикселей, таких как Retina. Чтобы потом не возвращаться к этой главе, сразу предлагаю сделать картинки для обычных дисплеев размером 400x121. Будем ли мы их использовать, зависит от многих «но» и «если», но об этом позже. Ниже приведена таблица с разрешением и весом после изменения разрешения.

Формат

Разрешение

Вес

png

1600 x 484

1,1 Mб

jpg

1600 x 484

807 Kб

png

800 x 242

279 Kб

jpg

800 x 242

245 Kб

png

400 x 121

74 Kб

jpg

400 x 121

66 Kб

Оптимизация

Оптимизация изображений это всегда риск того, что что-то пойдёт не так. По этой причине я не использую всевозможные плагины для оптимизации изображений при сборке статики через Webpack. За много лет разработки неоднократно натыкался на испорченные изображения после выкатки в production. Используя всё тот же онлайн-инструмент, прогоняем все шесть картинок, получившихся в предыдущем пункте, и визуально осматриваем их на предмет отсутствия всевозможных артефактов и значительного ухудшения качества. Вес изображений после оптимизации привёл в табличке ниже.

Формат

Разрешение

Вес

png

1600 x 484

312 Kб

jpg

1600 x 484

177 Kб

png

800 x 242

82 Kб

jpg

800 x 242

52 Kб

png

400 x 121

22 Kб

jpg

400 x 121

15 Kб

WebP

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

Разрешение

Вес

1600 x 484

89 Kб

800 x 242

23 Kб

400 x 121

7 Kб

Тег picture и srcset

Мы можем использовать picture для того, чтобы:

  1. Добавить запасной вариант для браузеров, не понимающих формат WebP.

  2. Дать браузеру возможность выбрать максимально подходящую картинку для отображения.

В пример ниже мы сообщаем браузеру о том, что ему следует использовать для дисплеев Retina изображения с разрешением 800x242, а для обычных — 400x121. Если браузер умеет работать с WebP, то использовать именно этот формат, иначе — png.

<picture>
  <source srcset="800x242.webp 2x, 400x121.webp 1x" type="image/webp"/>
  <img src="400x121.png" srcset="800x242.png 2x" alt="house"/>
</picture>

Но, как говорил Иван Васильевич: «есть нюанс». Об этом самом нюансе пойдёт речь в следующей главе. 

Предзагрузка

Есть такая метрика веб-производительности, как Largest Contentful Paint (LCP). Она означает длительность загрузки самого большого визуального элемента в той области, которую видит пользователь при попадании на страницу. И, конечно же, при обнаружении изображения Lighthouse собирает с него метрики. Чтобы улучшить этот показатель, мы с вами уже изменили размер изображения, оптимизировали его и даже добавили WebP. Но есть ещё способ улучшить метрику, а именно: предварительно загрузить изображение. Делается это с помощью тега link в заголовке страницы:

<link rel="preload" as="image" type="image/webp" href="/800x242.webp"/>

Эта подсказка браузеру повышает приоритет загрузки изображения, а также начинает его загрузку ещё до обнаружения элемента img в DOM. И тут мы как раз подходим к нюансу. Если у нас были разные изображения для Retina и прочих дисплеев, то с предварительной загрузкой на текущий момент времени мы должны чётко знать, что и зачем загружаем. Проблема в ограниченной поддержке атрибута srcset для тега link. В данном случае лучше предварительно загрузить WebP с двойной плотностью пикселей и использовать эту картинку как для Retina, так и для обычных дисплеев. А код с запасным вариантом будет выглядеть немного проще.

<picture>
  <source srcset="800x242.webp" type="image/webp"/>
  <img src="800x242.png" alt="house"/>
</picture>

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

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

  1. атрибут loading со значением lazy в теге img

  2. intersectionObserver API

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

Что касается intersectionObserver API, то процесс появления изображения лежит уже на наших плечах, и расстояние до контента задаём мы сами. А недостаток в том, что придётся написать пару строчек кода на JavaScript, к тому же этот вариант будет в любом случае подсовывать поисковикам либо неправильную ссылку на изображение, либо изображение будет отсутствовать в разметке вовсе. Но если вас не заботит SEO, то это не проблема.

Сдвиг макета

Ещё одна из важнейших, по моему мнению, метрик из семейства web performance: это Cumulative Layout Shift (CLS). Она разделяет второе место по значимости вместе с Largest Contentful Paint(LCP) и имеет вес в 25 баллов в Lighthouse v10. Взглянем на пример ниже.

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

Так вот, очень важно указывать изображениям ширину (width) и высоту (height), это верный способ избежать смещения макета. А также поможет ленивой загрузке правильно высчитать расстояние до картинки. В результате после добавления картинке значений ширины и высоты сдвиг макета более не побеспокоит пользователей.

Object-fit

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

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

Если важная часть изображения частично или полностью исчезла из области видимости, как на скриншоте выше, то мы можем позиционировать изображение при помощи свойства object-position.

Выводы

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

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


  1. homm
    11.09.2023 09:40
    +3

    Оригинал в png весит 1,1 Mб, а оригинал в jpg весит 807 Kб.

    Что такое оригинал в jpg?

    Ниже приведена таблица с разрешением и весом после изменения разрешения.

    Это таблица чего и о чем? Вы даже ссылку на картинку не дали. Что таблица должно сказать читателю?


    1. jquery_dlya_slabih Автор
      11.09.2023 09:40
      +5

      Оригинальное изображение взятое из макета без каких-либо модификаций. К сожалению я не понимаю, какую ценность даст читателю ссылка на картинку, добавить её что бы что?

      Таблица служит показателем того, что произошло с весом картинки после ресайза и оптимизации.


      1. Spaceoddity
        11.09.2023 09:40
        +6

        Таблица не служит показателем... ничего!

        Во-первых, что значит "png"? PNG8 или PNG24? Это разные форматы, на минуточку.

        Во-вторых, с каким уровнем компрессии вы жмёте жпег? Там вес картинки в разы может отличаться при сопоставимом визуальном качестве.

        В-третьих, наконец всё зависит от конкретного изображения. Многие картинки в lossless png24 весят меньше чем в jpeg.


        1. homm
          11.09.2023 09:40
          +1

          PNG8 или PNG24. Это разные форматы, на минуточку.

          Это вообще не форматы, а какие-то странные названия из фотошопа. Формат один — PNG. Он может быть с одним каналом оттенком серого, тремя каналами RGB или четырьмя RGB + alpha или вообще может быть с палитрой. Как видите, вариантов как минимум не два.


      1. homm
        11.09.2023 09:40

        Оригинальное изображение взятое из макета без каких-либо модификаций.

        Как взятое? Какой субсемлинг, квантование?

        Таблица служит показателем того, что произошло с весом картинки после ресайза и оптимизации.

        Что такое «оптимизация»? У меня такое чувство что вы вообще не понимаете что такое JPEG и как работает сжатие с потерями. За всю статью вы ни разу не упомянули, что размер JPEG зависит не от формата, а от вашего желания, то есть он настраивается. Для вас эта магия называется «оптимизация».

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


      1. homm
        11.09.2023 09:40

        я не понимаю, какую ценность даст читателю ссылка на картинку, добавить её что бы что

        Это ещё одна проблема. Вы даже не понимаете, что разные форматы лучше подходят для разных изображений.


  1. Spaceoddity
    11.09.2023 09:40
    -1

    Что-то статья направлена на разработчиков, чей уровень сопоставим со слушателями онлайн-курсов. Где вы таких находите...

    По поводу веса изображений и оптимизации - какие-то онлайн-сервисы, непонятно как работающие... В индустрии давным-давно есть "софтовый стандарт" - Фотошоп. В котором до сих пор работает сочетание Ctrl+Shift+Alt+S. И в котором наглядно можно выбрать уровень "компрессии" для любого формата.


    1. Spaceoddity
      11.09.2023 09:40

      А кто тут так активно минусует комментарии с критикой? Те самые "фронтенд-разработчики ", которым надо рассказывать про тэг picture, css-свойство object-fit и атрибут "loading"? Так вы не с того начали! Вы расскажите про тэги вообще, что такое html, css и т.п.

      Вы что своим пиаром Хабр превратили?


    1. jquery_dlya_slabih Автор
      11.09.2023 09:40
      +4

      А если объективно, то вы предлагаете каждому фронтендеру покупать лицензию фотошопа? Более того, статья не про выбор инструмента, чем сжимать и ресайзить фотографии, а в целом о подходе и правилах работы с картинками, на что наличие фотошопа не влияет.


      1. Spaceoddity
        11.09.2023 09:40

        Ну я не знаю, можете, конечно, отказаться от ФШ, Фигмы и условного XD - но в чём вы макеты тогда делаете?))

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

        А надо бы было рассказать. Рассказали бы в каких случаях выигрыш в весе даст жпег, в каких пнг, где с webp придётся повозиться, а где только svg.

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

        Про случаи, когда <img> "вдруг" начинает растягивать флекс-контейнер.

        Ну и т.п. По этой теме можно тонны действительно полезной инфы выдать. Вместо этого вы выдали такие азы... Признайтесь честно - это социальная нагрузка? Сейчас ваша очередь в корпоративный блог "техническую статью минимум на 1000 знаков" писать?


        1. jquery_dlya_slabih Автор
          11.09.2023 09:40
          +2

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


          1. Spaceoddity
            11.09.2023 09:40
            -2

            но зачем вы приплетаете svg сюда и уходите в дебри

            За тем, чтобы не пихали растровые изображения куда не надо!


    1. ScorpAL
      11.09.2023 09:40
      +4

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

      Простейший прогон этой картинки через какой-нибудь tinypng.com (tinyjpg.com) (пондочек любят все :) ) без потери качества эффективно ужал картинку почти вдвое.

      При этом ребята молодцы. Но вот среди космических кораблей бороздящих просторы вселенной про банальные вещи как-то подзабы(И)ли.

      А потом плачемся что приложение фонарика на телефоне весит 100 мегабайт.


  1. kirill-pavlovskii15
    11.09.2023 09:40
    +6

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


    1. Pavel1114
      11.09.2023 09:40
      +4

      Уверен, там это не баг, а фича. Эталонный пример - яндекс Погода для андроид. Реклама не грузится сразу при открытие приложения (наверняка специально откладывают). И когда вы уже прям замахнулись тыкнуть на "сегодня" чтобы посмотреть более подробный прогноз, подгружается реклама как раз в эту область, сдвигая всё вниз.


  1. PrinceKorwin
    11.09.2023 09:40
    -2

    И вот после таких статей люди начинают выкладывать скриншоты с кучей текста в JPG с лютой компрессией. Ну и что, что текста уже не видно. Зато как мало весит картинка!


    1. PrinceKorwin
      11.09.2023 09:40
      +1

      Почему минусы? Я в статье не нашел в критериях выбора компрессию изображений с текстом (это не только скриншоты, но и сканы).


  1. cssfish
    11.09.2023 09:40
    +2

    нужен ли альфа-канал? Если нужен, берём png, в противном случае берём jpg, так как он меньше весит, а качество визуально идентично в подавляющем большинстве случаев.

    сильное заявление, конечно.


  1. andrejsharapov
    11.09.2023 09:40
    +1

    Полезно. Спасибо что напомнили.

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