Чтобы предсказать, какой вариант адаптивного изображения будет выбран, определите ширину CSS по sizes, умножьте её на плотность пикселей устройства и сравните результат с srcset. Затем проверьте выбранный URL: окончательное решение остаётся за браузером.

Это пошаговое руководство предназначено для тех, кто проверяет созданную веб-страницу в Chrome на Mac. Вы рассчитаете вероятные варианты, проверите локальную страницу с исходными абстрактными фигурами и отличите вводящее в заблуждение объявление размера от обычного поведения браузера при выборе.

Что сообщают браузеру sizes, srcset и DPR?

При использовании дескрипторов ширины srcset перечисляет доступные ресурсы и их исходную ширину в пикселях. Дескриптор вроде 800w должен описывать изображение фактической шириной 800 пикселей. Дескрипторы ширины и дескрипторы плотности, например 2x, следует указывать в разных наборах источников; смешивать их в одном srcset нельзя. Эти требования описаны в справочнике MDN по элементу изображения.

sizes задаёт предполагаемую ширину отображения, используемую при выборе ресурса. Браузер проверяет условия по порядку и использует первую совпавшую запись. Условие без ограничений указывайте последним. В качестве размеров источника можно задавать значения вроде 480px, 50vw и calc(100vw - 32px); процентные значения, например 50%, не являются допустимой шириной слота. Использование адаптивных изображений в HTML

Размеры отображаемого изображения задаются CSS. Считайте CSS и явный список sizes двумя описаниями, которые должны соответствовать друг другу: одно определяет макет, другое помогает выбрать ресурс. Одно лишь изменение sizes не задаёт соответствующий макет. Стандарт HTML: атрибуты sizes

DPR описывает соотношение физических пикселей и пикселей CSS. Смотрите фактическое значение window.devicePixelRatio, а не предполагайте его по модели Mac или названию дисплея. Масштаб страницы может изменить это значение; оно может измениться и при перемещении окна между дисплеями. Масштабирование жестом не меняет DPR. MDN: Window.devicePixelRatio

Как рассчитать вероятный вариант?

Для разрешённого размера источника S, ширины ресурса W и DPR D используйте любой из этих двух равнозначных расчётов:

Целевая ширина ресурса = S × D
Эффективная плотность варианта = W ÷ S

Алгоритм обработки в HTML нормализует дескрипторы ширины, деля ширину каждого варианта на размер источника. MDN также описывает практический способ прогнозирования: умножить подсказку о размере на DPR. Стандарт HTML: нормализация плотности источников, MDN: HTMLImageElement.sizes

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

Рассмотрим такой список размеров источника в сочетании с вариантами 480w, 800w, 1200w и 1440w:

(max-width: 600px) calc(100vw - 32px),
(max-width: 1200px) calc(50vw - 32px),
568px

100vw обозначает ширину области просмотра, а 50vw — половину этой ширины. Эти единицы относятся к области просмотра, а не к родительскому контейнеру изображения. Значения CSS и единицы измерения: длины в процентах от области просмотра

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

Ширина области просмотра, CSS px DPR Разрешённый размер источника, CSS px Целевая ширина ресурса, px Предсказанный вариант
600 1 568 568 800w
600 2 568 1,136 1200w
601 2 268.5 537 800w
1,000 1 468 468 480w
1,000 2 468 936 1200w
1,400 2 568 1,136 1200w
1,400 3 568 1,704 1440w, меньше целевого

Переход от 600 к 601 CSS-пикселю особенно полезно проверить. При 600 первое условие задаёт слот почти во всю ширину. При 601 второе условие задаёт гораздо более узкий слот. Поэтому при большей ширине области просмотра целевое изображение становится меньше. Это прямо следует из заданных условий макета.

При ширине области просмотра 1 000 пикселей и DPR 2 размер источника равен 468 CSS-пикселям. Эффективная плотность четырёх вариантов составляет приблизительно 1.03x, 1.71x, 2.56x и 3.08x. По нашему правилу прогнозирования вариант 1200w первым превосходит целевую плотность 2x.

Как провести полный локальный тест?

Используйте Mac с установленным Chrome и Python версии 3.8 или новее. Сохраните приведённую ниже полную программу в текстовом редакторе без форматирования под именем responsive_demo.py. Она создаёт четыре PNG-файла с одинаковыми прямоугольником и кругом, записывает соответствующую страницу и запускает локальный сервер. Все созданные файлы помещаются в новую временную папку, поэтому существующие файлы не перезаписываются.

На странице намеренно используется изображение с немедленной загрузкой и явные размеры источника. Значения ширины CSS соответствуют трём вариантам из таблицы расчётов. Сервер использует документированные ThreadingHTTPServer и обработчик запросов на основе каталогов из Python. Python: http.server

from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import struct
import tempfile
import zlib


def chunk(kind, data):
    return (
        struct.pack("!I", len(data)) + kind + data
        + struct.pack("!I", zlib.crc32(kind + data))
    )


def make_png(width):
    height = width // 2
    rows = []
    for y in range(height):
        row = bytearray()
        for x in range(width):
            color = (242, 244, 248)
            if width // 8 <= x < width // 2 and height // 4 <= y < 3 * height // 4:
                color = (70, 100, 160)
            if (x - 3 * width // 4) ** 2 + (y - height // 2) ** 2 < (height // 5) ** 2:
                color = (220, 160, 60)
            row.extend(color)
        rows.append(b"\x00" + row)
    header = struct.pack("!IIBBBBB", width, height, 8, 2, 0, 0, 0)
    return (
        b"\x89PNG\r\n\x1a\n"
        + chunk(b"IHDR", header)
        + chunk(b"IDAT", zlib.compress(b"".join(rows)))
        + chunk(b"IEND", b"")
    )


page = '''<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shape A candidate test</title>
<style>
  body { margin: 0; padding: 16px; font-family: sans-serif; }
  #shape { display: block; width: calc(100vw - 32px); height: auto; }
  @media (min-width: 600.01px) {
    #shape { width: calc(50vw - 32px); }
  }
  @media (min-width: 1200.01px) {
    #shape { width: 568px; }
  }
</style>
<p>Shape A: rectangle and circle</p>
<img id="shape" src="shape-a-800.png" width="1440" height="720"
     alt="Blue rectangle and amber circle on a pale background"
     srcset="shape-a-480.png 480w, shape-a-800.png 800w,
             shape-a-1200.png 1200w, shape-a-1440.png 1440w"
     sizes="(max-width: 600px) calc(100vw - 32px),
            (max-width: 1200px) calc(50vw - 32px), 568px">
</html>
'''

root = Path(tempfile.mkdtemp(prefix="responsive-shapes-"))
for width in (480, 800, 1200, 1440):
    (root / f"shape-a-{width}.png").write_bytes(make_png(width))
(root / "index.html").write_text(page, encoding="utf-8")
handler = partial(SimpleHTTPRequestHandler, directory=str(root))
with ThreadingHTTPServer(("127.0.0.1", 0), handler) as server:
    print(f"Files: {root}", flush=True)
    print(f"Open http://127.0.0.1:{server.server_port}/", flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass

В Терминале перейдите в папку, где сохранена программа, и выполните команду:

python3 responsive_demo.py

Откройте в Chrome локальный адрес, который выведет программа. Во время проверки не закрывайте Терминал; закончив, нажмите там Control+C. Программа запрашивает свободный порт, поэтому используйте выведенный ею адрес, а не рассчитывайте на фиксированный номер порта.

Для тестирования на рабочем столе в качестве порогов CSS используются 600.01px и 1200.01px, чтобы проверить целые значения ширины области просмотра. Проверьте точные целые значения из таблицы. Если адаптируете пример для реального макета, синхронизируйте настоящие контрольные точки CSS и условия размеров источника, включая поведение при дробной ширине.

Как проверить выбор в Chrome на Mac?

Щёлкните правой кнопкой мыши по фигуре и выберите Inspect, чтобы открыть DevTools с выбранным элементом изображения. В документации Chrome описан этот способ непосредственного изучения элемента и применённых к нему стилей. Открыть Chrome DevTools

  1. Нажмите Toggle device toolbar и в разделе Dimensions выберите Responsive.
  2. Установите ширину области просмотра 1000.
  3. В меню More options панели инструментов выберите Add device pixel ratio, затем укажите DPR 2.
  4. При необходимости выберите More options, затем Add device type и укажите Desktop.
  5. Откройте Network, включите Disable cache и перезагрузите страницу, оставив DevTools открытыми.
  6. Найдите запрошенное изображение, отфильтровав список по shape-a.

В руководстве Chrome по режиму эмуляции устройств описаны настройки области просмотра, DPR и типа устройства, а в справочнике по функциям Network — настройка кэша.

После загрузки страницы выполните в Console инструментов DevTools следующий полный диагностический фрагмент:

(() => {
  const image = document.getElementById("shape");
  if (!image) throw new Error("The Shape A image was not found.");

  const viewport = window.innerWidth;
  const slot = window.matchMedia("(max-width: 600px)").matches
    ? viewport - 32
    : window.matchMedia("(max-width: 1200px)").matches
      ? viewport / 2 - 32
      : 568;

  console.table({
    viewportCSSPixels: viewport,
    devicePixelRatio: window.devicePixelRatio,
    declaredSlotCSSPixels: slot,
    renderedWidthCSSPixels: image.getBoundingClientRect().width,
    targetResourcePixels: slot * window.devicePixelRatio,
    selectedURL: image.currentSrc,
    naturalWidthCSSPixels: image.naturalWidth
  });
})();

Этот фрагмент рассчитывает объявленный слот специально для данного примера; это не универсальный парсер sizes. Сравните объявленный размер слота с отображаемой шириной, а затем сопоставьте имя выбранного файла с таблицей прогнозов.

currentSrc сообщает полный URL, выбранный браузером. Это не подтверждает, что ресурс успешно загрузился, поэтому также проверьте запрос изображения и результат на экране. Чтение src в этом примере показало бы только атрибут с резервным вариантом. MDN: HTMLImageElement.currentSrc

Не считайте naturalWidth шириной PNG в пикселях. Это ширина с поправкой на плотность, выраженная в CSS-пикселях. В этом контролируемом примере ширину ресурса можно определить по размерам созданного файла и его имени. MDN: HTMLImageElement.naturalWidth

Повторите тест при ширине 600 и 601 CSS-пиксель, перезагружая страницу для каждого случая. Запишите ширину области просмотра, показанное значение DPR, размер слота, имя выбранного файла и сведения об успешном завершении запроса. Эти записи помогут отличить воспроизводимый результат после свежей загрузки от последовательности решений, принятых при изменении размера уже загруженной страницы.

Что проверить, если прогноз не совпал?

Прежде чем менять список вариантов, проверьте исходные данные:

  1. Проверьте тип дескрипторов. При использовании дескрипторов плотности, например 1x и 2x, не применяйте расчёт для дескрипторов ширины через sizes.
  2. Проверьте первое совпавшее условие. Более поздние записи не учитываются, если совпало предыдущее условие.
  3. Сравните объявленную и отображаемую ширину. Значение 100vw может завышать размер слота, который CSS ограничивает узкой колонкой.
  4. Узнайте фактическое значение DPR. Проверьте его снова после изменения масштаба страницы или дисплея.
  5. Сравните результаты контролируемых перезагрузок. Повторяйте тест при одинаковых ширине области просмотра, DPR и условиях загрузки.
  6. Проверьте доступность ресурса. Выбор URL и успешное отображение изображения — разные наблюдения.

Если явные размеры источника отсутствуют или заданы неверно, парсер HTML может использовать значение по умолчанию 100vw. Это может объяснить неожиданно большое целевое изображение при узком слоте. Задайте точный список размеров источника, а не рассчитывайте, что браузер определит предполагаемую ширину колонки по CSS. Стандарт HTML: разбор атрибута sizes

Если на странице используется элемент picture, сначала определите, какая ветвь источника подходит, и только потом рассчитывайте плотность. Сначала браузер выбирает источник из соответствующей ветви, а затем — один из его вариантов; для разных кадров используется этот механизм управления направлением изображения. Использование адаптивных изображений в HTML

Какие ограничения поддержки влияют на этот метод?

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

sizes="auto" допустимо использовать с изображениями, загружаемыми отложенно, чтобы при выборе учитывалась их ширина в макете. Если применяете его, укажите явные резервные размеры после auto для браузеров, которые не поддерживают это ключевое слово, и задайте размеры изображения. Не подставляйте его в пример с немедленной загрузкой, не выполнив эти условия. Справочник MDN по элементу изображения

Настройки устройств Chrome позволяют проверить поведение Chrome в эмулируемых условиях. Они не показывают, какие варианты выберут Safari или Firefox. Chrome также указывает, что режим эмуляции устройств является приближённым, поэтому, если важно поведение другого браузера, проверьте его отдельно. Chrome DevTools: ограничения режима эмуляции устройств

Полезный прогноз завершается записью, которую можно проверить: подходящий набор источников, разрешённый размер источника, показанное значение DPR, расчётная цель и наблюдаемый currentSrc. Если эти исходные данные соответствуют заданному макету, можно понятно объяснить выбор ресурса и определить, что следует изменить: CSS, sizes или доступные ширины изображений.

Источники