Изображения с высоким разрешением и переменной плотностью пикселей

Борис Смус
Boris Smus

Опубликовано: 22 августа 2012 г., Последнее обновление: 14 апреля 2025 г.

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

  • Большой выбор устройств разного форм-фактора.
  • Ограниченная пропускная способность сети и время автономной работы.

Что касается изображений, цель веб-разработчиков — максимально эффективно предоставлять изображения наилучшего качества. Здесь мы рассмотрим некоторые полезные методы, которые помогут сделать это сейчас и в будущем.

По возможности избегайте изображений

Прежде чем предположить, что вам нужно включить изображение, помните, что в Интернете существует множество мощных технологий, которые в значительной степени не зависят от разрешения и DPI. В частности, текст, SVG и большая часть CSS «просто работают» благодаря функции автоматического масштабирования пикселей в Интернете с помощью devicePixelRatio .

Тем не менее, не всегда можно избежать растровых изображений. Например, вам могут быть предоставлены ресурсы, которые будет довольно сложно воспроизвести в чистом SVG или CSS. Возможно, вы имеете дело с фотографией. Хотя вы можете автоматически преобразовать изображение в SVG, векторизация фотографий не имеет особого смысла, поскольку увеличенные версии обычно выглядят не очень хорошо.

История плотности пикселей

В первые дни компьютерные дисплеи имели плотность пикселей 72 или 96 точек на дюйм (DPI) .

Плотность пикселей постепенно улучшалась, во многом благодаря развитию мобильных устройств, поскольку пользователи обычно подносят свои телефоны ближе к лицу, что делает пиксели более заметными. К 2008 году телефоны с разрешением 150 точек на дюйм стали новой нормой. Продолжалось увеличение плотности дисплея, и сегодняшние телефоны имеют дисплеи с разрешением 300 точек на дюйм.

На практике изображения с низкой плотностью должны выглядеть на новых экранах так же, как и на старых, но по сравнению с четкими изображениями, которые привыкли видеть пользователи с высокой плотностью, изображения с низкой плотностью выглядят резкими и пикселизированными. Ниже приведена приблизительная симуляция того, как изображение 1x выглядит на дисплее 2x. Напротив, изображение 2x выглядит вполне хорошо.

1x пикселей 2x пикселей
Бабуин: плотность пикселей 1x.Плотность пикселей Baboon 2x.
Бабуины с разной плотностью пикселей.

Пиксели в сети

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

Спецификация HTML решила эту проблему, определив эталонный пиксель, который производители используют для определения размера пикселя CSS.

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

Рассчитать соотношение пикселей устройства

Предположим, у мобильного телефона есть экран с физическим размером пикселей 180 пикселей на дюйм (ppi). Расчет соотношения пикселей устройства состоит из трех шагов:

  1. Сравните фактическое расстояние, на котором находится устройство, с расстоянием для эталонного пикселя.

    Согласно спецификации, мы знаем, что при 28 дюймах идеальное разрешение — 96 пикселей на дюйм. Мы знаем, что с мобильным телефоном лица людей находятся ближе к их устройствам, чем с ноутбуками и настольными компьютерами. Для следующих уравнений мы оцениваем это расстояние как 18 дюймов.

  2. Умножьте соотношение расстояний на стандартную плотность (96 пикселей на дюйм), чтобы получить идеальную плотность пикселей для данного расстояния.

    IdealPixelDensity = (28/18) * 96 = 150 пикселей на дюйм (приблизительно)

  3. Возьмите отношение физической плотности пикселей к идеальной плотности пикселей, чтобы получить соотношение пикселей устройства.

    устройствоPixelRatio = 180/150 = 1,2

Один эталонный угловой пиксель, чтобы проиллюстрировать, как рассчитывается соотношение пикселей устройства.

Итак, теперь, когда браузеру нужно знать, как изменить размер изображения в соответствии с размером экрана в соответствии с идеальным или стандартным разрешением, браузер обращается к соотношению пикселей устройства 1,2. Это означает, что на каждый идеальный пиксель в этом устройстве приходится 1,2 физических пикселя. Формула перехода между идеальными (согласно веб-спецификациям) и физическими (точки на экране устройства) пикселями следующая:

physicalPixels = window.devicePixelRatio * idealPixels

Исторически сложилось так, что поставщики устройств имеют тенденцию округлять значения devicePixelRatios (DPR). iPhone и iPad от Apple сообщают о DPR, равном 1, а их эквиваленты Retina — о 2. Спецификация CSS рекомендует, чтобы

Пиксельная единица относится к целому числу пикселей устройства, которое лучше всего соответствует эталонному пикселю.

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

Однако в действительности ситуация с устройствами гораздо более разнообразна, и телефоны Android часто имеют DPR 1,5. Планшет Nexus 7 имеет DPR примерно 1,33, который был получен путем расчета, аналогичного предыдущему примеру. Ожидайте увидеть больше устройств с переменным DPR в будущем. По этой причине никогда не следует предполагать, что у ваших клиентов есть целочисленные DPR.

Методы изображения HiDPI

Существует множество методов решения проблемы максимально быстрого показа изображений наилучшего качества, которые в целом можно разделить на две категории:

  1. Оптимизация отдельных изображений.
  2. Оптимизация выбора между несколькими изображениями.

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

  • Сильно сжатое изображение HiDPI
  • Совершенно потрясающий формат изображений
  • Прогрессивный формат изображения

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

  • JavaScript
  • Доставка на стороне сервера
  • Медиа-запросы CSS
  • Встроенные функции браузера ( image-set() , <img srcset> )

Сильно сжатое изображение HiDPI

Изображения уже занимают колоссальные 60% трафика, затрачиваемого на загрузку среднего веб-сайта. Предоставляя изображения HiDPI всем клиентам, мы увеличиваем это число. Насколько больше он может вырасти?

Я провел несколько тестов, которые генерировали фрагменты изображений 1x и 2x с качеством JPEG 90, 50 и 20.

Шесть версий одного изображения, различающихся степенью сжатия и плотностью пикселей.Шесть версий одного изображения, различающихся степенью сжатия и плотностью пикселей.Шесть версий одного изображения, различающихся степенью сжатия и плотностью пикселей.

Судя по этой небольшой, ненаучной выборке, сжатие больших изображений обеспечивает хороший компромисс между качеством и размером. На мой взгляд, сильно сжатые изображения 2x на самом деле выглядят лучше, чем несжатые изображения 1x.

Тем не менее, предоставление низкокачественных, сильно сжатых изображений 2x на устройства 2x хуже, чем предоставление изображений более высокого качества, и такой подход повлечет за собой ухудшение качества изображения. Если вы сравните quality: 90 изображений с quality: 20 изображений, резкость изображения упадет, а зернистость увеличится. Артефакты с quality:20 могут быть неприемлемы там, где важно высокое качество изображений (например, приложение для просмотра фотографий) или для разработчиков приложений, которые не желают идти на компромисс.

Это сравнение было проведено исключительно со сжатыми файлами JPEG. Стоит отметить, что существует множество компромиссов между широко распространенными форматами изображений (JPEG, PNG, GIF), что подводит нас к…

WebP: Совершенно потрясающий формат изображений.

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

Один из способов проверить поддержку WebP — использовать JavaScript. Загрузите изображение размером 1 пиксель с помощью data-uri , дождитесь события загрузки или ошибки, а затем проверьте правильность размера. Modernizr поставляется с таким сценарием обнаружения функций , который доступен в Modernizr.webp .

Однако лучший способ сделать это — непосредственно в CSS с помощью функции image() . Итак, если у вас есть изображение WebP и резервный вариант JPEG, вы можете написать следующее:

#pic {
  background: image("foo.webp", "foo.jpg");
}

С этим подходом есть несколько проблем. Во-первых, image() не получил широкого распространения. Во-вторых, хотя сжатие WebP вытесняет JPEG, это все равно относительно постепенное улучшение — примерно на 30% меньше, если судить по этой галерее WebP . Таким образом, одного лишь WebP недостаточно для решения проблемы высокого разрешения.

Прогрессивные форматы изображений

Форматы прогрессивных изображений, такие как JPEG 2000, Progressive JPEG, Progressive PNG и GIF, имеют преимущество (несколько спорное), заключающееся в том, что изображение становится на место до того, как оно полностью загрузится. Они могут повлечь за собой некоторые накладные расходы, хотя данные об этом противоречивы. Джефф Этвуд заявил , что прогрессивный режим «добавляет около 20% к размеру изображений PNG и около 10% к размеру изображений JPEG и GIF». Однако Стоян Стефанов утверждает , что для больших файлов прогрессивный режим более эффективен (в большинстве случаев).

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

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

Одним из способов решения этой проблемы является использование запроса HTTP Range , который позволяет браузерам указывать диапазон байтов для выборки. Умный браузер может сделать запрос HEAD, чтобы получить заголовок, обработать его, решить, какая часть изображения действительно необходима, а затем получить его. К сожалению, HTTP Range плохо поддерживается веб-серверами, что делает этот подход непрактичным.

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

Используйте JavaScript, чтобы решить, какое изображение загружать

Первый и наиболее очевидный способ решить, какое изображение загружать, — использовать JavaScript в клиенте. Такой подход позволяет вам узнать все о вашем пользовательском агенте и поступить правильно. Вы можете определить соотношение пикселей устройства с помощью window.devicePixelRatio , получить ширину и высоту экрана и даже потенциально выполнить анализ сетевого подключения с помощью navigator.connection или выдать поддельный запрос, как это делает библиотека foresight.js . Собрав всю эту информацию, вы сможете решить, какое изображение загрузить.

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

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

Решите, какое изображение загрузить на сервер

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

К сожалению, User-Agent не обязательно предоставляет достаточно информации, чтобы решить, должно ли устройство получать изображения высокого или низкого качества. И следует избегать любого решения, которое использует User-Agent для принятия решений по стилю.

Используйте медиа-запросы CSS

Будучи декларативными, медиа-запросы CSS позволяют вам заявить о своем намерении и позволить браузеру сделать правильные действия от вашего имени. В дополнение к наиболее распространенному использованию медиа-запросов — сопоставлению размера устройства — вы также можете сопоставить devicePixelRatio . Связанный медиа-запрос представляет собой соотношение пикселей устройства и, как и следовало ожидать, имеет соответствующие варианты минимального и максимального значений.

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

#my-image { background: (low.png); }

@media only screen and (min-device-pixel-ratio: 1.5) {
  #my-image { background: (high.png); }
}

Когда смешаны все префиксы поставщиков, ситуация становится немного сложнее, особенно из-за больших различий в размещении префиксов «min» и «max»:

@media only screen and (min--moz-device-pixel-ratio: 1.5),
    (-o-min-device-pixel-ratio: 3/2),
    (-webkit-min-device-pixel-ratio: 1.5),
    (min-device-pixel-ratio: 1.5) {

  #my-image {
    background:url(high.png);
  }
}

При таком подходе вы возвращаете преимущества упреждающего анализа, которые были потеряны при использовании решения JavaScript. Вы также получаете гибкость выбора адаптивных точек останова (например, вы можете иметь изображения с низким, средним и высоким разрешением), которая была потеряна при подходе на стороне сервера.

К сожалению, он все еще немного громоздкий и приводит к странному виду CSS или требует предварительной обработки. Кроме того, этот подход ограничен свойствами CSS, поэтому невозможно установить <img src> , и все ваши изображения должны быть элементами с фоном. Наконец, строго полагаясь на соотношение пикселей устройства, вы можете оказаться в ситуации, когда ваш мобильный телефон с высоким разрешением в конечном итоге загружает массивное изображение с двукратным увеличением при подключении EDGE . Это не лучший пользовательский опыт.

Поскольку image-set() — это функция CSS, она не решает проблему тегов <img> . Введите @srcset , который решит эту проблему. Следующий раздел углубляется в image-set и srcset .

Функции браузера для поддержки высокого разрешения

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

Теперь, когда image-set и srcset широко поддерживаются, они являются лучшими решениями. Существуют дополнительные рекомендации, которые могут приблизить нас к старым браузерам.

Чем эти двое отличаются? Итак, image-set() — это функция CSS, которую можно использовать в качестве значения свойства CSS фона. srcset — это атрибут, специфичный для элементов <img> , со схожим синтаксисом. Оба этих тега позволяют указать объявления изображений, но атрибут srcset позволяет также настроить, какое изображение загружать в зависимости от размера области просмотра.

Лучшие практики для набора изображений

Синтаксис image-set() принимает одно или несколько объявлений изображений, разделенных запятыми, которые состоят из строки URL или функции url() за которой следует соответствующее разрешение. Например:

image-set(
  url("image1.jpg") 1x,
  url("image2.jpg") 2x
);

/* You can also include image-set without `url()` */
image-set(
  "image1.jpg" 1x,
  "image2.jpg" 2x
);

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

Помимо загрузки правильного изображения, браузер соответствующим образом масштабирует его. Другими словами, браузер предполагает, что 2 изображения в два раза больше, чем изображения 1x, и поэтому масштабирует изображение 2x в 2 раза, так что изображение на странице выглядит того же размера.

Вместо указания 1x, 1,5x или Nx вы также можете указать определенную плотность пикселей устройства в DPI.

Если вас беспокоят старые браузеры, которые не поддерживают свойство image-set , вы можете добавить резервный вариант, чтобы гарантировать отображение изображения. Например:

/* Fallback support. */
background-image: url(icon1x.jpg);
background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

Этот пример кода загружает соответствующий ресурс в браузерах, поддерживающих набор изображений, и в противном случае возвращается к ресурсу 1x.

На этом этапе вы можете задаться вопросом, почему бы просто не полифилить (то есть создать прокладку JavaScript для) image-set() и положить этому конец? Как оказалось, реализовать эффективные полифилы для функций CSS довольно сложно. (Подробное объяснение почему можно найти в обсуждении в стиле www ).

Исходный набор изображений

В дополнение к объявлениям, которые предоставляет image-set , элемент srcset также принимает значения ширины и высоты, которые соответствуют размеру области просмотра, пытаясь предоставить наиболее подходящую версию.

<img alt="my awesome image"
  src="banner.jpeg"
  srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">

В этом примере используется banner-phone.jpeg для устройств с шириной области просмотра менее 640 пикселей, banner-phone-HD.jpeg для устройств с маленьким экраном и высоким разрешением, banner-HD.jpeg для устройств с высоким разрешением и экранами более 640 пикселей, а banner.jpeg для всего остального.

Используйте image-set для элементов изображения

Может возникнуть соблазн заменить элементы img на <div> с фоном и использовать подход с набором изображений. Это работает, но с оговорками. Недостаток здесь в том, что тег <img> имеет долгосрочное семантическое значение. На практике это важно для доступности и веб-сканеров.

Вы можете использовать свойство CSS содержимого, которое автоматически масштабирует изображение на основе devicePixelRation . Например:

<div id="my-content-image"
  style="content: -webkit-image-set(
    url(icon1x.jpg) 1x,
    url(icon2x.jpg) 2x);">
</div>

Источник полифилла

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

Этот полифил поставляется с модульными тестами , чтобы гарантировать его максимальное соответствие спецификации . Кроме того, существуют проверки, которые не позволяют полифилу выполнять какой-либо код, если srcset реализован изначально.

Заключение

Лучшее решение для изображений с высоким разрешением — выбрать SVG и CSS. Однако это не всегда реалистичное решение, особенно для веб-сайтов с большим количеством изображений.

Подходы в JavaScript, CSS и серверных решениях имеют свои сильные и слабые стороны. Наиболее перспективный подход — использовать image-set и srcset .

Подводя итог, мои рекомендации таковы:

  • Для фоновых изображений используйте image-set с соответствующими запасными вариантами для браузеров, которые его не поддерживают.
  • Для изображений контента используйте полифил srcset или отмените использование image-set (см. выше).
  • В ситуациях, когда вы готовы пожертвовать качеством изображения, рассмотрите возможность использования сильно сжатых изображений 2x .