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

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

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

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

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

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

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

Фон

Очень короткая история плотности дисплея

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

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

Святым Граалем, конечно же, является дисплей, на котором пиксели совершенно невидимы. Что касается форм-фактора телефона, нынешнее поколение дисплеев Retina/HiDPI может быть близко к этому идеалу. Но новые классы оборудования и носимых устройств, такие как Project Glass, вероятно, будут продолжать способствовать увеличению плотности пикселей.

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

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

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

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

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

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

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

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

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

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

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

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

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

    devicePixelRatio = 180/150 = 1,2

Как рассчитывается устройствоPixelRatio.
Диаграмма, показывающая один эталонный угловой пиксель, чтобы проиллюстрировать, как рассчитывается devicePixelRatio.

Итак, теперь, когда браузеру нужно знать, как изменить размер изображения, чтобы оно соответствовало экрану в соответствии с идеальным или стандартным разрешением, браузер обращается к соотношению пикселей устройства, равному 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 даже на старые устройства с более низким разрешением. Вот несколько подходов для случая одного изображения:

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

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

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

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

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

Я провел несколько тестов, которые сгенерировали фрагменты изображений размером 1x и 2x с качеством JPEG 90, 50 и 20. Вот сценарий оболочки, который я использовал (с помощью ImageMagick ) для их создания:

Пример плитки 1.Пример плитки 2.Пример плитки 3.
Примеры изображений с разной степенью сжатия и плотностью пикселей.

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

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

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

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

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 , которые делают что-то подобное вышеописанному, и, к сожалению, ни одна из них не является особенно выдающейся.

Одним из больших недостатков этого подхода является то, что использование 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);
  }
}

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

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

Используйте новые возможности браузера

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

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

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

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

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

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

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

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

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

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

Это работает хорошо, за исключением браузеров, которые не поддерживают свойство image-set , в котором изображение вообще не отображается! Это явно плохо, поэтому для решения этой проблемы вам придется использовать запасной вариант (или серию запасных вариантов):

background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
    Also include other prefixed versions of this */
background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

Приведенное выше загрузит соответствующий ресурс в браузерах, поддерживающих набор изображений, и в противном случае вернется к ресурсу 1x. Очевидным предостережением является то, что, хотя поддержка браузерами image-set() невелика, большинство пользовательских агентов получат актив 1x.

В этой демонстрации используется image-set() для загрузки правильного изображения, возвращаясь к ресурсу 1x, если эта функция CSS не поддерживается.

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

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

Вот пример srcset:

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

Как вы можете видеть, в дополнение к объявлениям x, которые предоставляет image-set , элемент srcset также принимает значения w и h, соответствующие размеру области просмотра, пытаясь предоставить наиболее релевантную версию. Вышеупомянутый файл будет использоваться для устройств с шириной области просмотра менее 640 пикселей, Banner-phone-HD.jpeg для устройств с маленьким экраном и высоким разрешением, Banner-HD.jpeg для устройств с высоким разрешением и экраном более 640 пикселей, а также Banner.jpeg. ко всему прочему.

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

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

Если вы в конечном итоге используете -webkit-image-set , у вас может возникнуть соблазн использовать свойство CSS Background. Недостаток этого подхода заключается в том, что вам необходимо указать размер изображения, который неизвестен, если вы используете изображение, отличное от 1x. Вместо этого вы можете использовать свойство CSS содержимого следующим образом:

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

Это автоматически масштабирует изображение на основе параметра devicePixelRatio. Посмотрите этот пример описанной выше техники в действии с дополнительным запасным вариантом url() для браузеров, которые не поддерживают image-set .

Полизаполнение исходного набора

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

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

Вот демо-версия полифилла в действии.

Заключение

Не существует волшебного средства для решения проблемы изображений с высоким разрешением.

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

Подходы в JS, CSS и использовании серверной части имеют свои сильные и слабые стороны. Однако наиболее перспективным подходом является использование новых функций браузера. Хотя поддержка браузерами image-set и srcset все еще неполная, сегодня есть разумные альтернативы.

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

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