Что на самом деле делает атрибут decoding у img?



Сейчас будет сложно, а в оригинальной статье ещё сложнее, но когда-нибудь эти знания вам пригодятся.



Возможно, вы замечали, что иногда к изображениям зачем-то добавляют decoding=async. Возможно, вы даже сами добавляли такой атрибут по советам из статей по оптимизации перфоманса. А если вы пользуетесь компонентом Image из NextJS, то у вас в проекте точно по умолчанию для картинок включён этот атрибут. А он вообще важный?



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



Начнём с того, что у decoding может быть три значения по спеке:

- sync — просим браузер декодировать картинку синхронно;

- async — просим браузер декодировать асинхронно;

- auto — доверяем браузеру выбрать самостоятельно.



Пока понятней не стало. Но мы уже знаем, что у картинок есть этап декодирования. То есть когда браузер видит в коде страницы <img src='...'>, он сначала попросит сетевой кусок браузера сходить за картинкой в сеть или кэш, потом ещё один вспомогательный кусок попросит декодировать картинку (превратить из набора байтов в понятные для отрисовки данные, не просто так же кучу кодеков придумали сложных для сжатия), и только потом отрисует.



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



А ещё в Firefox по умолчанию у всех картинок применяется async. А у Chrome sync. А у WebKit в большинстве случаев sync, но для некоторых исключений всё же async. То есть сами браузеры до сих пор не договорились, какое поведение должно быть дефолтным.



На самом деле значительная разница появляется тогда, когда вы вставляете картинки при помощи JavaScript. А в современном SPA-мире такое сплошь и рядом. Джейк Арчибальд собрал демку, на которой в разных браузерах можно эту разницу заметить (ссылка в статье). Суть в том, что если внутри JS последовательно создать div с красным фоном, а потом внутрь него положить баааальшой img, загруженный через тот же JS, то в случае с sync случится подвисание страницы, потому что браузер увидит инструкцию декодировать синхронно и отложить отрисовку до окончания декодирования. А в случае с async вы сначала увидите красный фон у div, и только потом поверх нарисуется картинка, что тоже не ок, потому что моргание на экране.



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



В общем, после прочтения статьи хочется дать такие советы:

- Если вы и так загружаете картинки при помощи JS, то добавьте ещё await img.decode();. Эта конструкция всё равно асинхронная, зато перед отрисовкой будет всё готово, чтобы отрисовать быстро.

- async всё же предпочитетельнее для ситуаций, когда вам текстовый контент на странице важнее картинок. Будет два кадра отрисовки, но зато текст появится чуть-чуть раньше. На слабых устройствах это «чуть-чуть» будет заметно глазу.

- Для тонкого тюнинга Core Web Vitals, особенно LCP, пробуйте разное. Интуитивно кажется, что sync правильнее, чтобы не тратить время на лишнюю отрисовку, но браузер слишком умный, поэтому банально надо пробовать разное и сравнивать.

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



https://www.tunetheweb.com/blog/what-does-the-image-decoding-attribute-actually-do/