Предписывающий синтаксис

Элемент <picture> сам по себе ничего не отображает, вместо этого выступает в роли механизма принятия решений для вложенного элемента <img>, указывая ему что отображать. <picture> работает по аналогии с элементами <audio> и <video>: это элемент-обёртка, содержащий отдельные элементы <source>.

<picture>
  <source …>
  <source …>
 <img …>
</picture …>

Вложенный <img> также даёт надёжный резервный шаблон для старых браузеров без поддержки адаптивных изображений: если элемент <picture> не распознаётся браузером пользователя, то он игнорируется. Элементы <source> также отбрасываются, поскольку браузер либо вообще их не распознает, либо не будет иметь для них значимого контекста без родительского элемента <video> или <audio>. Однако вложенный элемент <img> распознаётся любым браузером, так что источник, указанный в атрибуте src, будет показан как и ожидалось.

«Художественное обработка» с помощью <picture>

Изменение содержимого или соотношения сторон адаптивного изображения, в зависимости от его размера на странице, обычно называется «художественной обработкой». Атрибуты srcset и sizes разработаны так, чтобы действовать незаметно, плавно меняя источники в соответствии с указаниями браузера пользователя. Однако возникают ситуации, когда необходимо изменить источники в точках останова, чтобы лучше выделить содержимое, подобно тому, как вы адаптируете макеты страниц. Например, изображение заголовка на всю ширину страницы с небольшим центральным фокусом может прекрасно работать на широкой области просмотра:

Но при уменьшении масштаба для маленьких экранов центральный фокус изображения может быть потерян:

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

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

Каждый <source> содержит атрибуты, определяющие условия выбора этого элемента — media, который принимает медиа-запрос, и type, который принимает медиа-тип (ранее известный как «MIME-тип»). Выбирается первый <source> из списка источников, соответствующий текущему контексту просмотра пользователя, а содержимое атрибута srcset этого <source> будет использоваться для определения подходящих кандидатов для данного контекста. В примере ниже будет выбран первый <source> с атрибутом media, который соответствует размеру области просмотра пользователя:

<picture>
  <source media="(min-width: 1200px)" srcset="wide-crop.jpg">
  <img src="close-crop.jpg" alt="…">
</picture>
<style>img { max-width: 100%; }</style> <picture> <source media="(min-width: 1200px)" srcset="image/honeybee-wide.jpg"> <img src="image/honeybee-close.jpg" alt="…"> </picture>

Вы всегда должны указывать внутренний <img> последним в очереди — если ни один из исходных элементов не соответствует критериям media или type, изображение будет действовать как источник «по умолчанию». Если в медиа-запросах вы используете min-width, то первыми должны быть указаны самые большие источники, как показано в предыдущем коде. При использовании max-width первым следует поместить самый маленький источник.

<picture>
  <source media="(max-width: 400px)" srcset="mid-bp.jpg">
  <source media="(max-width: 800px)" srcset="high-bp.jpg">
  <img src="highest-bp.jpg" alt="…">
</picture>

Когда <source> выбирается по заданным вами критериям, его атрибут srcset передаётся в <img>, как если бы он был определён для самого <img> — это значит, что вы можете использовать размеры для оптимизации источников изображений.

<picture>
  <source media="(min-width: 800px)" srcset="high-bp-1600.jpg 1600w, high-bp-1000.jpg 1000w">
  <source srcset="lower-bp-1200.jpg 1200w, lower-bp-800.jpg 800w">
  <img src="fallback.jpg" alt="…" sizes="calc(100vw - 2em)">
</picture>

Конечно, изображение с пропорциями, которые могут меняться в зависимости от выбранного элемента <source>, вызывает проблемы с производительностью: <img> поддерживает только один атрибут width и один height, но их отсутствие может привести к заметному ухудшению пользовательского опыта. Чтобы учесть это, относительно недавнее, но хорошо поддерживаемое дополнение к спецификации HTML позволяет использовать атрибуты width и height в элементах <source>. Они работают для уменьшения смещения макета так же, как и в <img>, при этом в макете зарезервировано соответствующее пространство для любого выбранного элемента <source>.

<picture>
  <source
    media="(min-width: 800px)"
    srcset="high-bp-1600.jpg 1600w, high-bp-1000.jpg 1000w"
    width="1600"
    height="800">
  <img src="fallback.jpg"
    srcset="lower-bp-1200.jpg 1200w, lower-bp-800.jpg 800w"
    sizes="calc(100vw - 2em)"
    width="1200"
    height="750"
    alt="…">
</picture>

Важно отметить, что художественная обработка не только может использоваться для принятия решений, основанных на размере области просмотра, но и обязана, учитывая, что большинство таких ситуаций можно более эффективно решить с помощью srcset и sizes. Например, выбрать источник изображения, лучше подходящего для цветовой схемы, продиктованной предпочтениями пользователя:

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="hero-dark.jpg">
  <img srcset="hero-light.jpg">
</picture>
<style> @media( prefers-color-scheme: dark ) { body { background: #12073F; } }</style> <picture> <source media="(prefers-color-scheme: dark)" srcset="image/w-dark.svg"> <img srcset="image/w-light.svg"> </picture>

Атрибут type

Атрибут type позволяет использовать механизм принятия решения по единственному запросу элемента <picture>, чтобы передавать форматы изображений только тем браузерам, которые их поддерживают.

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

До появления элемента <picture> наиболее жизнеспособные решения для обслуживания новых форматов изображений требовали, чтобы браузер запрашивал и пытался разобрать файл изображения, прежде чем определить, стоит его отбрасывать и загружать резервный вариант. Распространённым примером был следующий сценарий:

<img src="image.webp"
  data-fallback="image.jpg"
  onerror="this.src=this.getAttribute('data-fallback'); this.onerror=null;"
alt="...">

В этом шаблоне запрос на image.webp всё равно будет выполняться в каждом браузере, что означает напрасную передачу данных для браузеров без поддержки WebP. Браузеры, которые не понимают WebP, вызывают событие onerror и меняют значение атрибута data-fallback на src. Это расточительное решение, но, опять же, подобные подходы были единственным доступным вариантом. Помните, что браузер начинает делать запросы к изображениям ещё до того, как пользовательский скрипт успевает выполниться, поэтому мы не могли упредить этот процесс.

Элемент <picture> явно предназначен для того, чтобы избежать подобных лишних запросов. Хотя браузер без запроса всё ещё не может распознать формат, который он не поддерживает, атрибут type заранее предупреждает браузер об исходном кодировании, чтобы браузер мог решить, стоит ли вообще делать запрос.

В атрибуте type вы указываете медиа-тип (ранее MIME-тип) источника изображения, указанного в атрибуте srcset каждого <source>. Это даёт браузеру всю необходимую информацию, чтобы сразу определить, можно ли декодировать изображение не выполняя никаких внешних запросов. Если медиа-тип не распознан, <source> и все его кандидаты игнорируются, после чего браузер идёт дальше.

<picture>
  <source type="image/webp" srcset="pic.webp">
  <img src="pic.jpg" alt="...">
</picture>

Здесь любой браузер, поддерживающий формат WebP, распознаёт медиа-тип image/webp, указанный в атрибуте type элемента <source>, и выбирает этот <source>. Поскольку мы указали единственного кандидата в srcset, браузер инструктирует внутренний <img> для запроса, передачи и отображения pic.webp. Любой браузер без поддержки WebP будет игнорировать <source> и при отсутствии каких-либо противоположных инструкций будет отображать содержимое <img>, как это делалось с 1992 года. Второй элемент <source> с type="image/jpeg" здесь указывать, конечно, не нужно — вы можете предположить, что JPEG поддерживается всеми браузерами.

Независимо от контекста просмотра пользователя, всё это достигается с помощью единственной передачи файла, без траты полосы пропускания на источники изображений, которые не могут быть отображены. Это дальновидный подход: поскольку новые и более эффективные форматы файлов будут иметь собственные типы мультимедиа, мы сможем воспользоваться ими благодаря <picture> — без JavaScript, без серверных зависимостей и всего остального, влияющего на скорость <img>.

Будущее адаптивных изображений

Все обсуждаемые здесь шаблоны разметки были тяжёлым испытанием с точки зрения стандартизации: изменение функциональности чего-то столь устоявшегося и важного для Интернета, как элемент <img>, было немалым подвигом, а набор проблем, которые эти изменения были призваны решить, мягко говоря, был обширным. Если вы поймали себя на мысли, что эти шаблоны разметки ещё можно улучшить, то абсолютно правы. С самого начала эти стандарты были призваны обеспечить основу для будущих технологий.

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

Однако, поскольку эти возможности были представлены на веб-платформе, был введён собственный метод отсрочки запросов изображений. Элементы <img> с атрибутом loading="lazy" не запрашиваются до тех пор, пока не станет известен макет страницы, чтобы отложить запросы изображений за пределами исходной области просмотра пользователя до более поздних этапов процесса отображения страницы, потенциально избегая ненужных запросов. Поскольку браузер полностью понимает макет страницы во время выполнения этих запросов, атрибут size="auto" был предложен в качестве дополнения к спецификации HTML, чтобы избежать рутинной работы с атрибутами sizes, написанными вручную в этих случаях.

На горизонте также появятся дополнения к элементу <picture>, чтобы соответствовать некоторым исключительно интересным изменениям в том, как мы оформляем макеты страниц. Хотя информация об области просмотра является надёжной основой для принятия решений по макету на высоком уровне, она не позволяет нам полностью использовать подход к разработке на уровне компонентов — то есть компонент, который можно поместить в любую часть макета страницы со стилями, которые реагируют на пространство, которое занимает сам компонент. Эта проблема привела к созданию контейнерных запросов: метода стилизации элементов на основе размера их родительского контейнера, а не области просмотра.

Хотя синтаксис контейнерных запросов только что устоялся (а поддержка браузеров на момент написания статьи очень ограничена), добавление технологий браузера, которые его поддерживают, позволит элементу <picture> делать то же самое: потенциальный атрибут контейнера, который позволяет использовать критерии выбора <source> на основе пространства, которое занимает <img> элемента <picture>, а не на основе размера области просмотра.

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

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

Автор: Мэт Маркиз
Последнее изменение: 20.05.2024