Привет, меня зовут Денис, я руковожу направлением разработки в Домклик. Дополнительно несу ношу лидера 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 для того, чтобы:
Добавить запасной вариант для браузеров, не понимающих формат WebP.
Дать браузеру возможность выбрать максимально подходящую картинку для отображения.
В пример ниже мы сообщаем браузеру о том, что ему следует использовать для дисплеев 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>
Ленивая загрузка
Теперь зайдём с другой стороны. Если картинка находится не в той области которую видит пользователь, заходя на страницу веб-приложения, то имеет смысл подумать о том, чтобы загрузить её попозже. Например, в тот момент, когда пользователь будет близко к изображению. На текущий день самые простые способы добиться такого поведения, это:
Как можно заметить, оба этих способа имеют довольно хорошую поддержку в браузерах. А вот что лучше использовать — решать вам. Могу лишь перечислить преимущества и недостатки. Например, первый вариант проще реализовать и он 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)
Spaceoddity
11.09.2023 09:40-1Что-то статья направлена на разработчиков, чей уровень сопоставим со слушателями онлайн-курсов. Где вы таких находите...
По поводу веса изображений и оптимизации - какие-то онлайн-сервисы, непонятно как работающие... В индустрии давным-давно есть "софтовый стандарт" - Фотошоп. В котором до сих пор работает сочетание Ctrl+Shift+Alt+S. И в котором наглядно можно выбрать уровень "компрессии" для любого формата.
Spaceoddity
11.09.2023 09:40А кто тут так активно минусует комментарии с критикой? Те самые "фронтенд-разработчики ", которым надо рассказывать про тэг picture, css-свойство object-fit и атрибут "loading"? Так вы не с того начали! Вы расскажите про тэги вообще, что такое html, css и т.п.
Вы что своим пиаром Хабр превратили?
jquery_dlya_slabih Автор
11.09.2023 09:40+4А если объективно, то вы предлагаете каждому фронтендеру покупать лицензию фотошопа? Более того, статья не про выбор инструмента, чем сжимать и ресайзить фотографии, а в целом о подходе и правилах работы с картинками, на что наличие фотошопа не влияет.
Spaceoddity
11.09.2023 09:40Ну я не знаю, можете, конечно, отказаться от ФШ, Фигмы и условного XD - но в чём вы макеты тогда делаете?))
Более того, статья не про выбор инструмента, чем сжимать и ресайзить фотографии
А надо бы было рассказать. Рассказали бы в каких случаях выигрыш в весе даст жпег, в каких пнг, где с webp придётся повозиться, а где только svg.
Упомянули бы уж тогда, что с нынешним зоопарком разрешений экранов гаджетов, не стоит привязывать размеры растровых картинок к размеру вьюпорта.
Про случаи, когда <img> "вдруг" начинает растягивать флекс-контейнер.
Ну и т.п. По этой теме можно тонны действительно полезной инфы выдать. Вместо этого вы выдали такие азы... Признайтесь честно - это социальная нагрузка? Сейчас ваша очередь в корпоративный блог "техническую статью минимум на 1000 знаков" писать?
jquery_dlya_slabih Автор
11.09.2023 09:40+2При всем уважении, но зачем вы приплетаете svg сюда и уходите в дебри? Анализируя все ваши "замечания", у меня складывается впечатление что название статьи и вступление полностью проигнорировано.
Spaceoddity
11.09.2023 09:40-2но зачем вы приплетаете svg сюда и уходите в дебри
За тем, чтобы не пихали растровые изображения куда не надо!
ScorpAL
11.09.2023 09:40+4Да норм. Лишний раз напомнить не помешает.
Буквально пару недель назад осматривая функционал и код соседней команды наткнулся на подобный случай где простейшая картинка весила овердофига.Простейший прогон этой картинки через какой-нибудь tinypng.com (tinyjpg.com) (пондочек любят все :) ) без потери качества эффективно ужал картинку почти вдвое.
При этом ребята молодцы. Но вот среди космических кораблей бороздящих просторы вселенной про банальные вещи как-то подзабы(И)ли.
А потом плачемся что приложение фонарика на телефоне весит 100 мегабайт.
kirill-pavlovskii15
11.09.2023 09:40+6Хорошая статья, про сдвиг макета прям актуальная проблема, не раз сам в такие кейсы попадался и переходил на сайт рекламы тыкая на баннер.
Pavel1114
11.09.2023 09:40+4Уверен, там это не баг, а фича. Эталонный пример - яндекс Погода для андроид. Реклама не грузится сразу при открытие приложения (наверняка специально откладывают). И когда вы уже прям замахнулись тыкнуть на "сегодня" чтобы посмотреть более подробный прогноз, подгружается реклама как раз в эту область, сдвигая всё вниз.
PrinceKorwin
11.09.2023 09:40-2И вот после таких статей люди начинают выкладывать скриншоты с кучей текста в JPG с лютой компрессией. Ну и что, что текста уже не видно. Зато как мало весит картинка!
PrinceKorwin
11.09.2023 09:40+1Почему минусы? Я в статье не нашел в критериях выбора компрессию изображений с текстом (это не только скриншоты, но и сканы).
cssfish
11.09.2023 09:40+2нужен ли альфа-канал? Если нужен, берём png, в противном случае берём jpg, так как он меньше весит, а качество визуально идентично в подавляющем большинстве случаев.
сильное заявление, конечно.
andrejsharapov
11.09.2023 09:40+1Полезно. Спасибо что напомнили.
Все это давно уже изучал, но по факту и правда частенько забывается, особенно когда думаешь о дизайне в целом и о более важных моментах вёрстки и функциональности проекта. Думаешь картинку поставил и ладно, потом к ней вернёшься, а нет )))
homm
Что такое оригинал в jpg?
Это таблица чего и о чем? Вы даже ссылку на картинку не дали. Что таблица должно сказать читателю?
jquery_dlya_slabih Автор
Оригинальное изображение взятое из макета без каких-либо модификаций. К сожалению я не понимаю, какую ценность даст читателю ссылка на картинку, добавить её что бы что?
Таблица служит показателем того, что произошло с весом картинки после ресайза и оптимизации.
Spaceoddity
Таблица не служит показателем... ничего!
Во-первых, что значит "png"? PNG8 или PNG24? Это разные форматы, на минуточку.
Во-вторых, с каким уровнем компрессии вы жмёте жпег? Там вес картинки в разы может отличаться при сопоставимом визуальном качестве.
В-третьих, наконец всё зависит от конкретного изображения. Многие картинки в lossless png24 весят меньше чем в jpeg.
homm
Это вообще не форматы, а какие-то странные названия из фотошопа. Формат один — PNG. Он может быть с одним каналом оттенком серого, тремя каналами RGB или четырьмя RGB + alpha или вообще может быть с палитрой. Как видите, вариантов как минимум не два.
homm
Как взятое? Какой субсемлинг, квантование?
Что такое «оптимизация»? У меня такое чувство что вы вообще не понимаете что такое JPEG и как работает сжатие с потерями. За всю статью вы ни разу не упомянули, что размер JPEG зависит не от формата, а от вашего желания, то есть он настраивается. Для вас эта магия называется «оптимизация».
Таблица служит показателем того, что произошло с весом какой-то рандомной картинки, которую читатель даже не видел, после после ресайза и непонятной вам магии. Так что эта таблица должно сказать читателю?
homm
Это ещё одна проблема. Вы даже не понимаете, что разные форматы лучше подходят для разных изображений.