Описательный синтаксис

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

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

Описание плотности с помощью x

<img> с фиксированной шириной будет занимать одинаковую часть области просмотра в любом контексте просмотра, независимо от плотности дисплея пользователя — количества физических пикселей, составляющих его экран. Например, изображение с собственной шириной 400px будет занимать почти всю область просмотра браузера как на исходном Google Pixel, так и на гораздо более новом Pixel 6 Pro — оба устройства имеют нормализованную область просмотра шириной 412px пикселей .

Однако Pixel 6 Pro имеет гораздо более четкий дисплей: у 6 Pro физическое разрешение составляет 1440 × 3120 пикселей, а у Pixel — 1080 × 1920 пикселей, то есть количество аппаратных пикселей, составляющих сам экран.

Соотношение между логическими пикселями устройства и физическими пикселями — это соотношение пикселей устройства для этого дисплея (DPR). DPR рассчитывается путем деления фактического разрешения экрана устройства на количество CSS-пикселей области просмотра.

В окне консоли отображается DPR, равный 2.

Так, у оригинального Pixel DPR составляет 2,6, а у Pixel 6 Pro — 3,5.

iPhone 4, первое устройство с DPR больше 1, сообщает о соотношении пикселей устройства, равном 2 — физическое разрешение экрана вдвое превышает логическое разрешение. Любое устройство до iPhone 4 имело DPR, равный 1: один логический пиксель к одному физическому пикселю.

Если вы просматриваете это изображение шириной 400px на дисплее с DPR, равным 2 , каждый логический пиксель отображается в четырех физических пикселях дисплея: двух горизонтальных и двух вертикальных. Изображение не получит преимуществ от дисплея с высокой плотностью — оно будет выглядеть так же, как на дисплее с DPR, равным 1 . Конечно, все, что «нарисовано» механизмом рендеринга браузера — например, текст, фигуры CSS или SVG — будет отрисовано в соответствии с отображением с более высокой плотностью. Но, как вы узнали из раздела «Форматы изображений и сжатие» , растровые изображения представляют собой фиксированные сетки пикселей. Хотя это не всегда может быть очевидно, растровое изображение, масштабированное для соответствия дисплеям с более высокой плотностью, будет выглядеть с низким разрешением по сравнению с окружающей страницей.

Чтобы предотвратить такое масштабирование, визуализируемое изображение должно иметь внутреннюю ширину не менее 800 пикселей. При уменьшении для размещения пространства в макете шириной 400 логических пикселей этот источник изображения с разрешением 800 пикселей имеет вдвое большую плотность пикселей — на дисплее с DPR, равным 2 , он будет выглядеть красиво и четко.

Крупный план лепестка цветка, показывающий разницу в плотности.

Поскольку дисплей с DPR, равным 1 , не может использовать повышенную плотность изображения, его масштаб будет уменьшен до соответствия дисплею, а, как вы знаете, уменьшенное изображение будет выглядеть нормально. На дисплее с низкой плотностью изображение, подходящее для дисплеев с более высокой плотностью, будет выглядеть как любое другое изображение с низкой плотностью.

Как вы узнали в разделе «Изображения и производительность» , пользователю с дисплеем низкой плотности, просматривающему источник изображения, уменьшенный до 400px , понадобится только источник с собственной шириной 400px . В то время как гораздо большее изображение визуально подойдет всем пользователям, огромный источник изображения с высоким разрешением, отображаемый на маленьком дисплее с низкой плотностью, будет выглядеть как любое другое маленькое изображение с низкой плотностью, но будет восприниматься намного медленнее.

Как вы могли догадаться, мобильные устройства с DPR, равным 1, встречаются крайне редко , хотя они все еще распространены в контексте просмотра «на рабочем столе» . Согласно данным, предоставленным Мэттом Хоббсом , примерно 18% сеансов просмотра GOV.UK с ноября 2022 года сообщают о DPR, равном 1. Хотя изображения с высокой плотностью будут выглядеть так, как ожидают пользователи, они будут иметь гораздо более высокую пропускную способность и стоимость обработки — особенно беспокоит пользователей старых и менее мощных устройств, которые, скорее всего, будут иметь дисплеи с низкой плотностью отображения.

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

Атрибут srcset идентифицирует одного или нескольких кандидатов для рендеринга изображения, разделенных запятыми. Каждый кандидат состоит из двух вещей: URL-адреса, который вы бы использовали в src , и синтаксиса, описывающего этот источник изображения. Каждый кандидат в srcset описывается присущей ему шириной («синтаксис w ») или предполагаемой плотностью («синтаксис x »).

Синтаксис x является сокращением от «этот источник подходит для дисплея с такой плотностью» — кандидат, за которым следует 2x , подходит для дисплея с DPR, равным 2.

<img src="low-density.jpg" srcset="double-density.jpg 2x" alt="...">

Браузерам, поддерживающим srcset будут представлены два кандидата: double-density.jpg , который 2x описывает как подходящий для дисплеев с DPR, равным 2, и low-density.jpg в атрибуте src — кандидат выбирается, если не найдено ничего более подходящего. в srcset . Для браузеров без поддержки srcset атрибут и его содержимое будут игнорироваться — содержимое src будет запрошено, как обычно.

Значения, указанные в атрибуте srcset , легко принять за инструкции. Это 2x сообщает браузеру, что связанный исходный файл пригоден для использования на дисплее с DPR, равным 2 — информация о самом источнике. Он не сообщает браузеру, как использовать этот источник, а просто сообщает браузеру, как можно использовать этот источник. Это тонкое, но важное различие: это изображение двойной плотности, а не изображение для использования на дисплеях двойной плотности.

Разница между синтаксисом «этот источник подходит для 2x дисплеев» и синтаксисом «использовать этот источник на 2x дисплеях» незначительна при печати, но плотность отображения — лишь один из огромного количества взаимосвязанных факторов, которые использует браузер. принять решение о кандидате на рендеринг, только некоторые из которых вы можете знать. Например: индивидуально вы можете определить, что пользователь включил настройку браузера для экономии трафика с помощью медиа prefers-reduced-data , и использовать это, чтобы всегда настраивать пользователей на изображения с низкой плотностью независимо от плотности их отображения — но если он не будет реализован последовательно каждым разработчиком на каждом веб-сайте, он не принесет большой пользы пользователю. Их предпочтения могут быть уважены на одном сайте, а на другом они могут столкнуться со стеной изображений, уничтожающей пропускную способность.

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

Описание ширины с помощью w

srcset принимает второй тип дескриптора для кандидатов в источники изображений. Это гораздо более мощный метод, и для наших целей его гораздо проще понять. Вместо того, чтобы помечать кандидата как имеющего соответствующие размеры для заданной плотности отображения, синтаксис w описывает внутреннюю ширину каждого источника-кандидата. Опять же, каждый кандидат идентичен, за исключением своих размеров: одинаковое содержание, одинаковое кадрирование и одинаковое соотношение сторон. Но в этом случае вы хотите, чтобы браузер пользователя выбирал между двумя кандидатами: small.jpg, источник с внутренней шириной 600 пикселей, и big.jpg, источник с внутренней шириной 1200 пикселей.

srcset="small.jpg 600w, large.jpg 1200w"

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

Описание использования с помощью sizes

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

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

Как и srcset , sizes предназначены для того, чтобы сделать информацию об изображении доступной сразу после анализа разметки. Так же, как атрибут srcset является сокращением от «вот исходные файлы и их собственные размеры», атрибут sizes является сокращением от «вот размер визуализированного изображения в макете ». То, как вы описываете изображение, зависит от области просмотра — опять же, размер области просмотра — единственная информация о макете, которую браузер получает при выполнении запроса изображения.

В печатном виде это может показаться немного запутанным, но на практике это гораздо легче понять:

<img
 sizes="80vw"
 srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
 src="fallback.jpg"
 alt="...">

Здесь это значение sizes сообщает браузеру, что пространство в нашем макете, которое занимает img , имеет ширину 80vw — 80% области просмотра. Помните, это не инструкция , а описание размера изображения в макете страницы. Там не говорится «заставить это изображение занимать 80% области просмотра», а «это изображение будет занимать 80% области просмотра после отображения страницы».

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

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

Вы сообщили браузеру, что это изображение будет занимать 80% доступной области просмотра — поэтому, если мы будем отображать это img на устройстве с областью просмотра шириной 1000 пикселей, это изображение будет занимать 800 пикселей. Затем браузер возьмет это значение и разделит на него ширину каждого из кандидатов на источник изображения, которые мы указали в srcset . Наименьший источник имеет собственный размер 600 пикселей, поэтому: 600÷800=0,75. Наше среднее изображение имеет ширину 1200 пикселей: 1200÷800=1,5. Наше самое большое изображение имеет ширину 2000 пикселей: 2000÷800=2,5.

Результаты этих вычислений ( .75 , 1.5 и 2.5 ), по сути, представляют собой параметры DPR , специально адаптированные к размеру области просмотра пользователя . Поскольку браузер также имеет под рукой информацию о плотности отображения пользователя, он принимает ряд решений:

При таком размере области просмотра кандидат small.jpg отбрасывается независимо от плотности отображения пользователя — при рассчитанном DPR ниже 1 для этого источника потребуется масштабирование для любого пользователя, поэтому он не подходит. На устройстве с DPR, равным 1 , medium.jpg обеспечивает наиболее близкое соответствие — этот источник подходит для отображения с DPR, равным 1.5 , поэтому он немного больше, чем необходимо, но помните, что уменьшение масштаба — это визуально непрерывный процесс. На устройстве с DPR, равным 2, large.jpg является наиболее близким, поэтому он выбирается.

Если то же изображение отображается в окне просмотра шириной 600 пикселей, результат всех математических вычислений будет совершенно другим: 80vw теперь равно 480 пикселей. Когда мы разделим на это ширину наших источников, мы получим 1.25 , 2.5 и 4.1666666667 . При этом размере области просмотра small.jpg будет выбран на устройствах 1x, а medium.jpg будет соответствовать устройствам 2x.

Это изображение будет выглядеть одинаково во всех этих контекстах просмотра: все наши исходные файлы одинаковы, за исключением их размеров, и каждый из них отображается настолько четко, насколько позволяет плотность отображения пользователя. Однако вместо того, чтобы предоставлять large.jpg каждому пользователю, чтобы разместить самые большие области просмотра и дисплеи с самой высокой плотностью, пользователям всегда будет предлагаться самый маленький подходящий кандидат. Используя описательный синтаксис, а не предписывающий, вам не нужно вручную устанавливать точки останова и учитывать будущие области просмотра и DPR — вы просто предоставляете браузеру информацию и позволяете ему определять ответы за вас.

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

К счастью, здесь вы можете использовать calc() — любой браузер со встроенной поддержкой адаптивных изображений также будет поддерживать calc() , что позволит нам смешивать и сопоставлять единицы CSS — например, изображение, занимающее всю ширину пользовательского пространства. область просмотра, за вычетом поля в 1em с каждой стороны:

<img
    sizes="calc(100vw-2em)"
    srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w, x-large.jpg 2400w"
    src="fallback.jpg"
    alt="...">

Описание точек останова

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

Допустим, у вас есть изображение, которое должно занимать 80 % области просмотра, за вычетом одного em отступа с каждой стороны, в области просмотра более 1200 пикселей — в меньших областях просмотра оно занимает всю ширину области просмотра.

  <img
     sizes="(min-width: 1200px) calc(80vw - 2em), 100vw"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Если область просмотра пользователя превышает 1200 пикселей, calc(80vw - 2em) описывает ширину изображения в нашем макете. Если условие (min-width: 1200px) не соответствует, браузер переходит к следующему значению. Поскольку к этому значению не привязано конкретное условие носителя, по умолчанию используется 100vw . Если бы вы написали этот атрибут sizes , используя медиа-запросы max-width :

  <img
     sizes="(max-width: 1200px) 100vw, calc(80vw - 2em)"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Простым языком: «Соответствует ли (max-width: 1200px) ? Если нет, двигайтесь дальше. Следующее значение — calc(80vw - 2em) — не имеет уточняющего условия, поэтому выбрано именно оно.

Теперь, когда вы предоставили браузеру всю эту информацию о вашем элементе img — потенциальные источники, внутреннюю ширину и то, как вы собираетесь представить изображение пользователю — браузер использует нечеткий набор правил для определения того, что с этим делать. информация. Если это звучит расплывчато, что ж, это потому, что так было задумано. Алгоритм выбора источника, закодированный в спецификации HTML, явно не дает четкого представления о том, как следует выбирать источник. После анализа источников, их дескрипторов и способов визуализации изображения браузер волен делать все, что пожелает — вы не можете знать наверняка, какой источник выберет браузер.

Синтаксис, говорящий «использовать этот источник на дисплее с высоким разрешением», был бы предсказуем, но он не решил бы основную проблему с изображениями в адаптивном макете: сохранение пропускной способности пользователя. Плотность пикселей экрана лишь косвенно связана со скоростью подключения к Интернету, если вообще связана. Если вы используете современный ноутбук, но просматриваете веб-страницы через лимитное соединение, привязанное к телефону или используете нестабильное Wi-Fi-соединение в самолете, возможно, вы захотите отказаться от источников изображений с высоким разрешением. , независимо от качества вашего дисплея.

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

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

Использование sizes и srcset

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

srcset — явный пример автоматизации. Редко вам придется вручную создавать несколько версий ваших изображений для производственной среды, вместо этого автоматизируя процесс с помощью средства запуска задач, такого как Gulp, компоновщика, такого как Webpack, стороннего CDN, такого как Cloudinary, или функций, уже встроенных в ваш CMS на выбор. Имея достаточно информации для создания наших источников, система будет иметь достаточно информации, чтобы записать их в жизнеспособный атрибут srcset .

sizes немного сложнее автоматизировать. Как вы знаете, единственный способ, которым система может вычислить размер изображения в визуализированном макете, — это отрисовать макет. К счастью, появилось множество инструментов разработчика, позволяющих абстрагировать процесс написания атрибутов sizes от руки — с эффективностью, которую невозможно достичь вручную. respImageLint , например, представляет собой фрагмент кода, предназначенный для проверки точности атрибутов sizes и предоставления предложений по улучшению. Проект Lazysizes жертвует некоторой скоростью ради эффективности, откладывая запросы изображений до тех пор, пока макет не будет установлен, что позволяет JavaScript генерировать значения sizes за вас. Если вы используете полностью клиентскую среду рендеринга, такую ​​как React или Vue, существует ряд решений для создания и/или создания атрибутов srcset и sizes , которые мы обсудим далее в разделе CMS и Frameworks .