Продолжаю делиться опытом, сегодня речь пойдет о изображениях
Маленькая картинка выглядит на 5к мониторе жутко и размазано. Я обычно употребляю слово "мыльное кинцо".
4к картинка с двойным уменьшением размера изображения в браузере, по отношению к оригинальному размеру на 5к мониторе уже выглядит вполне достойно. Но весит зачастую много и для мобильного девайса, или обычного планшета это избыточно.
Чтобы смасштабировать такое изображение до определенного размера по экрану телефона или планшета, девайс пользователя производит огромную работу и time to Interactive растёт, что играет отрицательную роль на UX, понижая продажи.
Как быть? Где золотая середина?
Предложение решения проблемы от WHATWG:
Более сложный вариант, с применением тега picture:
Какие популярные (и нет) форматы изображений, используемых в web-браузерах мы знаем и в чём их преимущества и недостатки?
- JPEG (формат файла image.jpeg, image.jpg, mime-тип image/jpeg) - формат хорош для отображения фотографий. Явный минус формата jpeg - большой размер изображения и большие затраты процессора на масштабирование. Также добавим сюда отсутствие прозрачности и пиксельные артефакты во время сжатия.
- JPEG2000 (формат файла image.jp2, mime-тип image/jp2) - лучше своего предшественника. Имеет значительно меньший размер и гораздо меньше артефактов, нежели дед. Также подходит для фотографий, но уже имеет гораздо меньшую поддержку платформами. На данный момент поддерживается исключительно Safari и Mobile Safari (iOS).
- GIF (формат файла image.gif, mime-тип image/gif) - популярен для анимаций (хотя может использоваться и не только для анимации). Проблемы gif - ограниченная цветовая палитра и полная потеря качества. В 2019 году подходит для анимированных вставок в сообщения, не требующих качества изображения
- JPEG XR (формат файла image.jxr, mime-тип image/jxr) - альтернатива JPEG2000 c поддержкой прозрачности, высоким качеством сжатия и 48-битной глубиной цвета. Отлично подходит для фотографий. Главный минус - проприентарность. Поддерживается исключительно Edge, IE и IE Mobile.
- Progressive JPEG (формат файла image.pjpg, mime-тип image/pjpeg либо просто image/jpeg) - в первую очередь превосходит JPEG тем, что браузер может отрисовывать изображение по частям или по слоям. Размер PJPEG файлов зачастую ниже чем у JPEG. Поддерживается всеми стандартными браузерами. Отлично подходит для изображений, которые нужно отобразить быстро. Из-за небольшой потери качества не рекомендуется для UHD фотографий.
- PNG (формат файла image.png, mime-тип image/png) - формат изображения с поддержкой прозрачности. Может быть в нескольких вариациях: PNG8 - 8 бит прозрачность и набор цветов из палитры, PNG32 - 24 бита глубина цвета и 8-бит альфа-канал. PNG8 отлично подойдет для иконок сайтов, где нужна прозрачность, но не особо важно качество изображения. Минус PNG - большой вес файла. PNG8 как и PNG32 поддерживается абсолютно всеми устройствами.
- APNG (формат файла image.png или image.apng, mime-тип image/png, но этот момент, как и сам формат, довольно спорный, некоторые используют video/png) - PNG с поддержкой анимации. Имеет ограниченную поддержку браузеров. Также как и PNG имеет большой вес файла.
- WebP (формат файла image.webp, mime-тип image/webp). Может быть как с потерей качества, так и без потери (WebP Lossless). Формат изображения активно продвигаемый компанией Google, специально для веб-приложений. Поддерживает анимацию, поддерживает прозрачность. В сравнении с PNG меньше на 24% при полном сохранении качества. Более быстро рендерится. Подойдёт для фотографий и практически любых изображений. Имеет ограниченную поддержку браузерами в настоящее время.
- SVG (формат файла image.svg, инлайн-тег <svg>, mime-тип image/svg+xml) - универсальный и программируемый формат изображений для векторной графики. Подойдет для иконок, небольших рисунков и векторных изображений. Также подойдет для анимаций и шрифтов. Минусом является то, что SVG представляет из себя XML-файл и в нём сложно хранить полноценные изображения, так как их обработка займёт достаточно длительное время. Имеет широкую поддержку браузеров и отображается всегда максимально качественно, адаптируясь под дисплей пользователя.
Предположим, что отдав пользователю google chrome webp фотографию, вместо jpeg, мы значительно сэкономим его и свои ресурсы, а также ускорим у него наш сайт. Но пользователи Edge обрадуются например jpeg xr. И как быть?
Возможный вариант решения проблемы - определять на сервере браузер пользователя по user-agent или загружать картинки через JS. Остановимся пока на этом. Углубляться в реализацию не будем. Обсудим ещё некоторые моменты, которым можно уделить внимание.
Если у пользователя простой экран 1000х1000, с невысоким PPI, на котором требуется отобразить фотографию размера 500х500. Его браузерный DPR (Device Pixel Ratio) - составляет 1. Гораздо разумнее будет отдать ему картинку размером 500x500.
1. это полностью его устроит, задействовав все возможности его дисплея
2. это сэкономит наш и его трафик
3. его девайс быстро справится с задачей масштабирования, так как её попросту можно будет пропустить, а значит изображение отобразиться максимально корректно и быстро, освободив поток документа для других задач.
Если же пользователь предпочитает более качественный монитор и его браузер имеет Device Pixel Ratio 2 и выше, то грамотно было бы отдать ему картинку шириной 500 умножить на его DPR (1000 для dpr === 2). Браузеру пользователя не потребуется "размазывать" изображение по пикселям и пользователь увидит качественную картинку.
Как определить DPR?
1. через javascript window.devicePixelRatio || 1
2. оставить это браузеру через srcset атрибут (старый браузер автоматически задействует src):
3. через заголовок DPR (имеет ограниченную поддержку браузеров)
На выходе сразу скажу что самым оптимальным на данный момент лично я считаю именно вариант с window.devicePixelRatio.
Какие проблемы нужно решить при отдаче изображения
- в каком формате и с какой степенью сжатия пользователю отдать изображение?
- с какой шириной должна быть картинка?
- бонус - с каким приоритетом картинка должна загружаться и в какой момент?
С первыми двумя проблемами лично я справляюсь при помощи CDN. Многие CDN умеют работать с изображениями, но существуют также и специализированные CDN для изображений. Мой любимый, проверенный временем CDN для изображений это imgix. Вы можете поиграть со своими фотографиями в песочнице
Как я загружаю фотографии
Для работы с фотографиями я использую собственные компоненты, часто под react. На входе компонент принимает атрибут src. На выходе даёт тег <div><img /></div>. Первоначально <div> имеет фиксированную ширину, рассчитанную заранее и задний план, залитый цветом, например серым.
Тег img обязательно должен иметь width и желательно height, установленные через CSS или атрибут style. Во первых это понижает нагрузку на поток документа, а во вторых это снижает подёргивания во время первоначального рендеринга. Также хорошим тоном будет поставить alt для каждого изображения, если вам не безразличны пользователи с плохим зрением.
Первоначальный width может быть "резиновым". И уже с загрузкой javascript можно поменять его на рассчитанный.
Для вычисления height относительно width необходимо знать соотношение сторон загружаемого изображения.
Зная соотношение сторон, например 1.5, можно построить div с таким CSS для первоначального рендеринга:
Первым делом загрузим максимально сжатую, заблюреную версию изображения. Если вы будете использовать imgix, то это можно выразить например как набор параметров q=20&w=60&blur=60&auto=format,compress&cs=strip
После полной инициализации JS мы вычисляем, что width допустим 300px, а DPR 2. Ждём пока blured-версия изображения загрузится и отрендерится и запрашиваем уже HD-версию. Например в imgix это может выглядеть как fit=max&q=70&dpr=2&usm=15&chromasub=444&auto=format&w=300
Ожидание загрузки должно сопровождаться двумя event listener'ами - load и error. Пример кода может выглядеть следующим образом:
В примере кода выше вы можете управлять отсроченное загрузкой изображения используя setTimeout (delayed thread). Это хороший тон, особенно для изображений, которые находятся внизу экрана, за его пределами.
Если с памятью и заголовком Cache-Control от CDN всё в порядке, то загруженный Image кешируется браузером и его src спокойно можно вставлять в другие <img src=... />.
Даже не пытайтесь играть с background-image вместо img тега. Их обработка происходит по разному и на работу с background-image браузер затратит значительно большие ресурсы.
CDN сам выберет оптимальный формат и выполнит необходимые преобразования над изображением, после чего его закеширует у себя на память.
Не пихайте в <head> много информации, так как перед началом рендеринга <body> браузер вынужден обработать всю информацию оттуда.
Если вы точно знаете размер изображения и на нём производится акцент внимания пользователя, то можете сразу начать его загрузку, ещё до рендеринга <body>, используя тег:
Отлично сочитается с preconnect, до rel=preload:
Preconnect произведет соединение с сервером и также проверит сертификат, на что необходимо какое-то время и что не особо влияет на поток документа.
То же самое можно проделать и через http2 server push, используя заголовок Link, например:
Link: </6864.woff2>; rel="preload"; as="font"; type="font/woff"; crossorigin, </7c93.woff2>; rel="preload"; as="font"; type="font/woff"; crossorigin, </bundle.js>; rel="preload"; as="script", </assets/images/logo.svg>; rel="preload"; as="image"; type="image/svg+xml"
Немного про SVG
Предположим у нас в приложении на картинках можно ставить лайки. Лайки это SVG (потому что это позволяет корректно им отображаться на различных экранах и позволяет управлять их окрашиванием/анимированием). У нас есть изображение в формате SVG готового сердечка для лайка. Как с ним поступить? Может быть сделать компонент лайк и туда вставишь <img src= или как-то еще?
Правильный подход
- очевидно что лайк будет использоваться многократно на странице, поэтому для снижения нагрузки нам его нужно разделить на defs и use.
- defs содержать baseProfile full, так как мобильные устройства могут по умолчанию отключать поддержку таких моментов как use.
- компонент с defs будет отрендерен вместе с начальной страницей перед закрывающим тегом </body>
Лайк если было полезно.
Напоследок: веб-сайты уже давно спокойно серфятся с телефонов и малоопытный верстальщик никогда не будет задумываться о FPS или о том как правильнее что-либо сделать, потому что у него попросту нет такой задачи. Его задача сверстать то, что он видит на картинке, поэтому доверять верстку приложений, от которых зависят твои деньги, лучше профессионалам.
Комментарий недоступен
обновил проверяйте
Материал недели
Комментарий недоступен
куда жать?
Нужно нажать «+» и выбрать там <>
Комментарий недоступен
Поправлю вечерком, спасибо
Может еще картинку какую добавишь в текст? Так читать легче и в ленте красивее смотрится
Не большой, WebP для фоток не лучше.
Минус PNG - большой вес файлаПо сравнению с чем и на каких примерах?
В сравнении с PNG меньше на 24% при полном сохранении качества.Для каких картинок и в каком режиме? Что будет с картинками, где градиент, текст?
Украду фотографию из одного поста на хабре
https://hsto.org/files/ab8/559/9ab/ab85599abc72442c8a0064fcacf933ad.jpg
ТЖ вебп жрать не хочет. В общем пнг получился на 1,2 килобайта меньше, чем вебм. Оба в режиме без потерь.
ну надо смотреть по ситуации да
ВебП, скорее, подходит для смешнных изображений, где есть какая-то сложная фотка и одновременно текст на простом фоне. В случаях, где можно самому конкретно посмотреть и выбрать между жпегом или пнг, вебп не лучше или хуже.
Поэтому я и люблю auto=format в cdn imgix
jpeg наверное лучше сравнивать с jpeg2000 или jpeg xr
Может, не смотрел их.
например по сравнению с png
Зачем фотки в пнг? Он для другого
Комментарий недоступен
Этот момент не понял. Подозреваю что вы ошибаетесь.
1. у нас установлено соединение через preconnect
2. если таких картинок на странице много это http2 и они все загружаются за раз
3. отправляйте на страницу вместе с кодом - таким образом вы нарушаете поток документа и драгоценное время уйдёт на обработку ваших кодов. Обратное декодирование сожрет ресурсы процессора.