Как стать автором
Обновить

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

Блог компании VK Высокая производительность *Разработка веб-сайтов *Клиентская оптимизация *Браузеры
Перевод
Tutorial
Всего голосов 29: ↑29 и ↓0 +29
Просмотры 20K
Комментарии 71

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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


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

Какой плагин посоветуете? Мне не надо, чтобы он конвертировал фото из джипега в вебп. Мне надо просто возможность загружать вебп в вордпресс; типа мышкой перетащить файл вебп в медиафайлы.
Если совсем простой — WP Add Mime Types. А если хочется прям самому, то есть фильтры mime_types и upload_mimes, например так:

function mihdan_edit_upload_types( $existing_mimes = array() ) {
    // allow .woff
    $existing_mimes['woff'] = 'font/woff';
 
    // disallow .jpg files
    unset( $existing_mimes['jpg'] );
 
    return $existing_mimes;
}
add_filter( 'upload_mimes', 'mihdan_edit_upload_types' );
С 5.1 версии WP для SVG файлов надо еще на хуке wp_check_filetype_and_ext изменить проверку заголовка, так как иногда в svg может отсутствовать заголовок xml, ну или ручками добавлять везде тот заголовок

Тут подробней
Плагин Allow WebP image, так и называется. Пробовал в последней версии Wordpress.
Он с коробки специально фильтрует то что на 100% не поддерживается. Сделано это больше с целью обезопасить блогера, чтоб он не закидывал на сервер что не попадя.

Вот когда они начнут из коробки понимать WEBP, AVIF, HEIF…

Боюсь никогда, для генерирования этого (а WP из коробки как минимум нарезает миниатюры) всего надо в php добавлять библиотеки, и далеко не каждый хостер этим вообще будет заниматься.

Динамически можно определять список поддерживаемых

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

Никаким образом не затрудняет. Через html-элемент picture можно указывать ссылки на разные типы картинок. Браузер определяет, есть ли поддержка webp и качает картинку нужного типа. Вот пример, где я так сделал.

это чудесно и я это знаю, только причем тут метаданные?

Ок, а как именно "очень сильно затрудняет SEO по нынешним временам"?

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

Тогда понял, спасибо

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

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

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

Вот да. Я не понимаю, почему бы просто не изменить поведение IMG по умолчанию, а не заставлять писать десяток аттрибутов, которые теперь везде будут таскать как с писанной торбой.

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

Интересно сколько десятилетий нужно 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-ов).

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

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

Это уже есть

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

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

Самое главное не так это что они вообще нужны. Страницы уже и так на половину состоят из таких вот заклинаний.
В то время как очевидно, что вот такое вот решение:
<picture aspectratio="1/2">
  <sоurce src="..." width="1000"/>
  <sоurce src="..." width="700"/>
  <sоurce src="..." width="200"/>
</picture>

Решило бы 99% всех задач гораздо лучше.


Так оно вроде так и работает
<picture>
 <source srcset="mdn-logo-wide.png" media="min-width: 600px">
 <img src="mdn-logo-narrow.png" alt="MDN">
</picture>

Прописал условия и вуаля

У браузера итак есть всё что нужно:
  • он знает pixel density
  • он знает размер
  • он знает пропускную способность сети

Он ведь узнает это все когда загрузит картинку, а в вашем примере их три и получается надо грузить все три что узнать то что ему и не надо. Или я не правильно вас понял?

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

А зачем вам высеченный в скале шаблон, он же адаптивный должен быть и размера экрана как раз вполне хватает. Как правило выделяет два размера для ПК и моб, иногда еще один для планшетов(но чаще он совпадает с ПК). Я смотрю максимальную ширину для каждого шаблона и накидываю 20% чтоб не терялось качество, и вот под них пишем правила.
Все, браузер грузит только одну картинку. Гугл на 20% превышение ширины не ругается в своих оценках, скорость загрузки для мобильных заметно возрастает, контент не страдает.

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

А вот aspect-ratio и правда иногда не хватает, помню когда-то для магазина пришлось допиливать костыль на js

Если я правильно понял faiwer (и в этом с ним трудно не согласиться), то неудобство состоит в том, что значение атрибута media применяется к экрану и может быть никак не связано с размером самой картинки. Например, у картинки через стили может быть указано max-width:100px (или ширина ограничена неявно через стили родителя), но раз в media-атрибуте указано, что для экранов с шириной больше 600px нужно грузить большую картинку — браузер будет грузить большую картинку.

Так оно вроде так и работает

Нет. Оно так НЕ работает.


<sourсe srcset="mdn-logo-wide.png" media="min-width: 600px">

Вот тут min-width это не размер картинки. Это размер окна. По сути мусор.


и размера экрана как раз вполне хватает.

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


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

А вот это очень редкий кейс. Но да, для него уже что-то есть.


Я потратил много времени на написание нормального Picture компонента, который, используя ResizeObserver, работает как надо (за исключением того что ResizeObserver очень ненадёжная штука). И у меня бомбит от того, что из каждого утюга пишут про то какой <picture/> хороший, при том какой он плохой. Прямо стыдно за коммитет, что они такую херню утвердили.

Я правильно понял: вы хотите чтобы браузер грузил url картинки в зависимости от ширины контейнера в котором находится эта картинка?

А вот если ширина контейнера не задана тогда что грузить, что-то по дефолту?

Идем дальше. Рядом с картинкой идет текст без переносов и он не помещается в свой блок, браузер начинает адаптировать его и уменьшает контейнер с вашей картинкой, что тогда, грузить еще одну миниатюру?

Ни разу не хватает. Я даже больше скажу это вообще не тот фактор который играет роль. Мне глубоков плевать какого размера окно.

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

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

Я потратил много времени на написание нормального Picture компонента, который, используя ResizeObserver, работает как надо (за исключением того что ResizeObserver очень ненадёжная штука). И у меня бомбит от того, что из каждого утюга пишут про то какой <picture/> хороший, при том какой он плохой. Прямо стыдно за коммитет, что они такую херню утвердили.

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

Браузер не может ждать пока отрендерится весь контент, чтоб точно знать ширину контейнера и определять, что ему грузить. Если бы так было то мы бы вернулись в 2000-е к IE который сначала текст отрисовывал, а потом по очереди грузил нужные картинки.

Собственно я это к чему все написал: во время загрузки ширина окна не меняется, она заранее известна и можно сходу понять какую картинку загрузить на основании media-правил, не ожидая когда оно там все отрисует.
Я правильно понял: вы хотите чтобы браузер грузил url картинки в зависимости от ширины контейнера в котором находится эта картинка?

Под контейнером я подразумеваю сам тег <img/>. Не его родительский условный <div/>, а сам. Т.е. тот самый прямоугольник в котором браузер будет рисовать картинку.


А вот если ширина контейнера не задана тогда что грузить, что-то по дефолту?

Нет. Браузер знает фактический размер контейнера в котором рисует картинку (<img/>, <picture/>). Как он задан — второстепенный вопрос. Главное что итоговый размер известен браузеру.


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


и оно само там как-то как догадывалось заранее кокой ширины будет контейнер.

Оно знает размер контейнера. Это браузер. Это render фаза. Весь CSS + HTML layout уже просчитан. Ему не нужно догадываться. Это вводные данные.


и можно сходу понять какую картинку загрузить на основании media-правил,

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


То есть вы сами говорите, что это не тривиальная задача, но хотите чтоб это встроили в ядро

Да. Я хочу чтобы это встроили в ядро. Вместо этого убожества, что есть сейчас. Заодно я хочу content queries. Надеюсь я доживу до того дня, когда их наконец внедрят.

Вам намекают на то, что сейчас браузер может начать грузить изображения сразу после разбора HTML. А если сделать зависимость от контейнера, то загрузка откладывается на этап после рендеринга, а это значительно позже.
Хотя лично я бы всё таки ввёл загрузку от размера контейнера img, только рекомендовал бы использовать её для изображений на втором и последующих экранах, которые так и так нужно грузить отложенно (по умолчанию, да).
Вам намекают на то, что сейчас браузер может начать грузить изображения сразу после разбора HTML

Интересный момент. Не думал о нём, т.к. пишу только SPA последние много-много лет. Т.е. мне совсем не актуально. Но ок, представим себе ситуацию:


  • у нас есть множество файлов изображений с одинаковым aspect-ratio
  • мы хотим чтобы браузер грузил нужный файл ещё ДО того как он смог организовать render
  • для этого нам нужно гарантированно задать изображению точные размеры

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

Нет. Браузер знает фактический размер контейнера в котором рисует картинку (, <picture/>). Как он задан — второстепенный вопрос. Главное что итоговый размер известен браузеру.

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

Вот к примеру визуализация данной страницы, когда уходит запрос и когда закончен рендеринг. Запросы на картинки ушли на 2,2 сек, а применение стилей закончилось только 2,8 сек, а бывает что подключение стилей объявляют в футере и они отрендерятся (будет известен точный размер блока картинки) еще позже.

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

А дальше откройте консоль к примеру этой странице на закладке network и посмотрите когда отработало событие DOMContentLoaded и load. В браузере большинство мелких картинок уже загружено еще до события DOMContentLoaded.

О, теперь понял вас. Вы про ситуацию когда стили ещё не загружены. Тысячу лет с таким не сталкивался, т.к. последние годы работал только с SPA. Для SPA это чаще не актуально (хотя зависит от имплементации). Т.к. HTML уже инжектится на страницу со всеми необходимыми стилями. Т.е. размеры известны сразу после appendChild. И DOMContentLoaded давно позади.


Касательно immediate загрузки без стилей. Ну тут нужно думать. Варианты есть, но уже не так всё однозначно. Например отложить загрузку пока изображение равно 0х0. Меня бы вполне устроило. Это точно лучше чем грузить что попало.

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


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

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

И еще вопрос про 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 загрузить.


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

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

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

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

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

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

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

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

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

Я тут один помню времена, когда не было flex и grid и колонки делались через float:left и это было очень прогрессивно, так как многие еще и таблицы использовали :).
Или для видео надо было мудрить флеш-плейер с js. А сейчас оно из коробки может показывать.
А уж про адаптацию под три-четыре браузера вообще вспоминать не хочется (хотя иногда приходится под сафари что-то доделывать)

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