Отзывчивые изображения в Drupal 8

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

Теория

Отзывчивые изображения (responsive images) или еще можно встретить термин "адаптивные" (adaptive images) — это изображения, которые подстраиваются или "отзываются" на медиа запросы (media queries). Проще говоря, могут загружаться различные экземпляры изображения в зависимости от размеров экрана или других факторов. Это один из способов уменьшить объем передаваемых данных и увеличить скорость загрузки сайта.

Отзывчивые изображения нативно поддерживаются всеми актуальными браузерами. Пути к экземплярам изображения вместе с метаданными передаются в виде элементов source тега picture или атрибута srcset тега img. Далее мы рассмотрим это более подробно.

Поддержка отзывчивых изображений в Drupal

В Drupal 8 все необходимое уже присутствует в ядре, это модули Breakpoint и Responsive Image (оба выключены по умолчанию). Забегая вперед, скажу, что возможна работа как со стандартным типом поля Image так и с изображениями внутри контейнера Media.

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

Брейкпоинты

Брейкпоинт (breakpoints) или точка остановки — это значение ширины экрана для медиа запроса, при котором произойдет изменение каких-либо параметров. В случае с отзывчивыми изображениями будет показан другой экземпляр изображения.

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

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

Когда-то давно нужно было заботиться только о нормальном отображении сайта на разрешении 1024x768 — это было стандартное разрешение большинства компьютерных мониторов. Даже сейчас значение 1024 считается границей между мобильными и настольными экранами, но это было бы слишком просто. Наиболее популярны сейчас 4 группы разрешений, разделяемые 4 брейкпоинтами:

  • 320-767 — мобильные телефоны;
  • 768-991 — планшеты;
  • 992-1255 — большие планшеты и маленькие мониторы (старые);
  • 1256-∞ — ноутбуки, мониторы, телевизоры.

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

Давайте взглянем на статистику наиболее популярных разрешений экранов на июль 2019. Если рассматривать данные с целью выбора брейкпоинтов, то нужно перестроить график. Лидирующую позицию — 38.9% занимают "другие" разрешения, для простоты будем считать, что они равномерно распределены на всем диапазоне разрешений от 320 до 2560 (почему именно эта цифра, объясню чуть позже). Разделим этот диапазон на поддиапазоны по 100 пикселей и получится что "другие" разрешения займут 1.69% в каждом поддиапазоне. Добавим оставшиеся данные по разрешениям в свои поддиапазоны и получим вот такой график.

Распределение разрешений экрана по диапазонам

Из графика видно, что подавляющее большинство разрешений имеют ширину до 500 пикселей (а если быть точным, то до 480) — это мобильные телефоны. Потом идет небольшое количество планшетов, и с 1300 до 1700 основная масса настольных мониторов. К ним же относятся Full HD мониторы с шириной 1920.

Поэтому я использую дополнительные брейкпоинты 480 (тут заканчиваются телефоны) и 1520 (тут начинаются широкоформатные мониторы). Часто можно встретить и еще более точную градацию, например, брейкпоинты на 360-м и 568-м пикселе, но это уже скорее частные случаи, связанные с конкретным дизайном. Также иногда бывает актуальна поддержка отдельного дизайна для сверх широкоформатных мониторов 4K, 5K и выше. Для них можно использовать брейкпоинт 1920. Такое небольшое значение связанно с тем, что на таких мониторах обычно увеличивается плотность пикселей, и результирующее значение ширины (а именно оно используется в медиа запросе) высчитывается как фактическая ширина, деленная на коэффициент плотности пикселей. То есть для 4K монитора с разрешением 3840x2160 c двойной плотностью результирующим значением ширины будет 1920. Для 5K монитора — 2560 (вот откуда взялась цифра, которую я использовал для определения максимального значения диапазона разрешений).

Внимательный читатель заметит, что я использовал брейкпоинты 992 и 1256 вместо, казалось бы, логичных 1024 и 1280, соответствующих разрешениям. Дело в том, что для настольных операционных систем характерны полосы прокрутки внутри окна браузера и результирующее значение ширины считается за вычетом ширины этой полосы. Как правило ширина полосы прокрутки находится в районе 24 пикселей. Такая точность нужна, чтобы вовремя осуществить переход от мобильного вида к настольному, и обычно он происходит в одной из этих точек.

Еще одним важным параметром, о котором я уже упоминал, является плотность пикселей. Большая плотность позволяет экрану иметь большее количество пикселей, которые физически имеют меньшие размеры. При этом сайт визуально не уменьшается, а становится четче. Ради этой четкости в общем-то и весь сыр-бор. Среди мобильных устройств широко распространены экраны с коэффициентами плотности от 1 до 3:

  • 1.5 — у бюджетных Android смартфонов;
  • 2 — у большинства средних Android, iPhone, iPad;
  • 2.5 — у некоторых Android;
  • 3 — у iPhone plus, у компактных Android;

У топовых Android смартфонов бывает и коэффициент 4, а также бывают устройства с коэффициентами не кратными 0.5.

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

Современные Ноутбуки MacBook и настольные компьютеры iMac имеют так называемые Retina экраны, это ничто иное как двойная плотность пикселей для результирующего разрешения экрана. macOS поддерживает несколько значений масштаба, но сами элементы интерфейса ОС (и соответственно сайтов в браузере) могут отрисовываться только с двойной (или одинарной) плотностью. Масштабирование производится за счет интерполяции между пикселями самого устройства, проще говоря, использования не нативного разрешения, когда один пиксель монитора не точно соответствует одному пикселю изображения. Из-за этого изображение может казаться не абсолютно четким, без звенящей резкости.

В Windows 10 и Ubuntu Linux (а, возможно, и в других дистрибутивах) помимо двойной плотности, поддерживаются и другие значения, такие как 1.25, 1.5 (в Windows используются процентные значения вида 125%, 150%) и прочие, и даже значения меньше 1. При этом перерисовываются именно элементы интерфейса, и интерполяции не происходит. Хотя использование не нативного разрешения, как в macOS тоже возможно.

Похожее явление происходит и в браузере, где также используются значения плотности и масштаба. Большинство элементов, из которых обычно состоит сайт, например, тексты, рамки, тени, по сути, являются векторными, и браузер отрисовывает их с учетом коэффициента плотности. Растровые изображения браузер тоже старается отобразить в соответствии с коэффициентом плотности, если это позволяет исходное изображение. То есть, если для устройства с двойной плотностью предоставить картинку с вдвое большими размерами, то она отобразится максимально четко. Если передать картинку "размер в размер", то браузер покажет ее как есть, но из-за того, что каждый результирующий пиксель представлен четырьмя физическими, изображение как бы растянется и может казаться "пиксельным". Если передать картинку с большим разрешением, то браузер ее интерполирует (проще говоря, сожмет), что тоже может выглядеть не идеально четко, но обычный глаз этого не заметит. Заведомо большие изображения не так критичны для четкости, чем меньшие, но более критичны с точки зрения объема передаваемых данных.

Я все это так детально расписал, чтобы было понятно, что невозможно обойтись каким-то одним универсальным изображением, для всех устройств, как невозможно (и не нужно) делать отдельное изображения для каждого устройства. Конечно, можно всегда использовать изображения в большом разрешении, и пусть браузер сам разбирается как его показать (и он будет это делать), но, поверьте, пользователи этого не оценят. Работать это будет медленно. В идеале нужно стараться соблюдать баланс между качеством и скоростью. Для этого можно предоставлять несколько изображений, покрывающих основные диапазоны разрешений с учетом плотности. Эти диапазоны и будут определяться брейкпоинтами.

Объявление брейкпоинтов

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

Для себя я использую следующие значения (на названия можете не обращать внимания):

  • ss: 360px;
  • ms: 480px;
  • xs: 568px;
  • sm: 768px;
  • md: 992px;
  • lg: 1256px;
  • xl: 1520px;
  • ll: 1920px.

Чтобы объявить брейкпоинты, нужно в своей теме оформления создать файл MYTHEME.breakpoints.yml. Для каждого брейкпоинта указываются машинное имя, название, вес, применяемый диапазон "от", "до" или сразу оба, в формате медиа-запроса.

Для поддержки коэффициента плотности в Drupal используется понятие "множитель" (multiplier). Для смартфонов я объявляю множители от 1 до 3 с шагом 0.5, для всех остальных устройств — только 1 и 2.

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

Простой пример:

MYTHEME.min-xs:
  label: "min-mobiles"
  mediaQuery: ''
  weight: 0
  multipliers:
    — 1x
MYTHEME.sm:
  label: "min-tablets"
  mediaQuery: 'all and (min-width: 768px)'
  weight: 1
  multipliers:
    — 1x
MYTHEME.md:
  label: "min-desktops"
  mediaQuery: 'all and (min-width: 992px)'
  weight: 2
  multipliers:
    — 1x

Рабочий файл, с группами, множителями и реально используемыми медиа-запросами из темы оформления Material Base:

material_base.breakpoints.yml

Для Drupal 7 формат объявления отличается, но принцип тот же:

material_base.info

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

Модуль Responsive Images

Модуль Responsive Images (или Picture для Drupal 7) использует брейкпоинты, так что их нужно предварительно объявить.

Проще всего представить себе этот модуль как обертку или набор стандартных стилей изображения, но со своим форматером и интерфейсом для управления логикой выбора нужного стиля в зависимости от брейкпоинта. Этот набор и есть отзывчивый стиль изображения (Responsive image style, или Picture mapping для Drupal 7).

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

Интерфейс настройки отзывчивых стилей изображения

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

Есть два способа указания стилей изображения:

  1. Указать несколько стилей изображения и аттрибут "sizes" (Select multiple image styles and use the sizes attribute);
  2. Указать один стиль изображения (Select a single image style).

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

Пример разметки:

<img srcset="image-1.jpg 240w, image-2.jpg 480w" sizes="(min-width: 480px) 240px, 100vw" src="image-1.jpg">

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

Второй вариант для меня более очевидный, а главное контролируемый. Каждому брейкпоинту соответствует один стиль изображения. Этот вариант реализован в виде тега picture, пути к изображениям передаются в виде элементов source. Так же этот вариант позволяет указать изображения принципиально разных размеров и пропорций (по-разному обрезанных с помощью стилей) для разных брейкпоинтов, это назвается art direction.

Пример разметки:

<picture>
  <source srcset="image-2.jpg" media="all and (min-width: 480px)" type="image/jpeg">
  <source srcset="image-1.jpg" type="image/jpeg">
  <img src="image-1.jpg">
</picture>

Пример разметки с несколькими множителями:

<picture>
  <source srcset="image-2.jpg 1x, image-2x1.5.jpg 1.5x, image-2x2.jpg 2x" media="all and (min-width: 480px)" type="image/jpeg">
  <source srcset="image-1.jpg 1x, image-1x1.5.jpg 1.5x, image-1x2.jpg 2x" type="image/jpeg">
  <img src="image-1.jpg">
</picture>

Главная причина, по которой я предпочитаю этот вариант в том, что мне чаще приходится иметь дело с изображениями, размеры которых зависят в основном от контейнера, в котором они находятся. Само изображение вписывается в этот контейнер, независимо от исходного разрешения. Назовем этот контейнер image box. Размеры image box могут меняться даже в рамках одного брейкпоинта, при этом размер изображения указывается как 100% от ширины контейнера, а высота обычно высчитывается автоматически. В первом же варианте нужно заранее определить размер отображаемого изображения в абсолютных или относительных единицах, но нельзя использовать проценты относительно контейнера, а только относительно экрана.

Определение брейкпоинтов, размеров и стилей изображений

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

Сразу скажу, что это не самая интересная в мире задача, и тут будет много цифр.

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

Наиболее распространенные и актуальные брейкпоинты:

  • до 480;
  • от 768;
  • от 992;
  • от 1256.

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

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

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

Для примера я приведу таблицу значений размеров изображений, которые мы использовали для "превьюшек" на сайте нашей студии 4D design:

Breakpoint / Multiplier 1x 1.5x 2.x
× 460x288 690x432 920x575
480 720x450 920x575 ×
768 460x288 × 920x575

В данном примере оригиналы изображения имели размеры 920x575, изображения показываются в одну или две колонки с отступами по бокам. Максимальная ширина содержимого — 944 пикселя. Отмечу, что максимальным разрешением на диапазоне до 480 на самом деле было 448x280, но из-за того, что оно было очень близко к разрешению для настольных экранов, мы решили не создавать отдельный стиль, а использовать один для обоих случаев, хоть он и немного больше, чем нужно.

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

  • Image thumb 1x (460x288);
  • Image thumb 2x (920x575);
  • Image thumb SM 1x (720x450);
  • Image thumb XS 1.5x (690x432);

Еще один совет по поводу эффекта для стилей изображений: во избежании всяких неожиданностей, я рекомендую всегда использовать эффект "Scale and crop" или комбинацию эффектов, приводящую к аналогичному результату — то есть гарантированному соответствию изображения заданным размерам. Еще один полезный эффект реализован в модуле Image Scale and Crop Without Upscale. В отличии от обычного "Scale and crop", этот эффект не будет увеличивать изображение, если оригинал имеет меньшие размеры чем заданы в настройках, но выдаст наибольшее возможное изображение с соблюдением пропорций.

Поддержка отзывчивых изображение другими модулями

Модуль Responsive images использует собственный форматер (с аналогичным названием), доступный для полей типа Image. Однако не все сторонние модули, использующие поле Image поддерживают этот форматер. По крайней мере пока.

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

Модули Views и Media

Ядерные модули Views и Media, а также все модули, которые позволяют выбрать форматер для поля Image (например Paragraphs), автоматически имеют поддержку форматера Responsive image.

Поле типа Media выводит изображения через собственный форматер Thumbnail, однако за этот вывод отвечает режим отображения (view mode) соответствующего типа медиа, в настройках которого нужно вместо стандартного форматера Image, выбрать Responsive image.

Поддержка WebP

В статье про WebP я писал о модуле WebP и о патче для него, реализующем поддержку формата в модуле Responsive image. Тогда у меня не получилось использовать этот модуль, так как в моем случае изображения выводились не отдельно, а в виде галереи. Забегая немного вперед, скажу, что эта проблема решена, и сейчас самое время по подробнее рассмотреть работу этого модуля.

Модуль WebP не требует дополнительных действий, после его включения, помимо обычных элементов source, на каждый такой элемент создается дополнительный, с объявлением изображений в формате WebP. Соответственно работать это будет только со вторым вариантом, когда указывается только один стиль изображения и применяется тег picture. Сами WebP изображения, создаются модулем автоматически.

Пример разметки:

<picture>
  <source srcset="image-2.webp 1x, image-2x1.5.webp 1.5x, image-2x2.webp 2x" media="all and (min-width: 480px)" type="image/webp">
  <source srcset="image-1.webp 1x, image-1x1.5.webp 1.5x, image-1x2.webp 2x" type="image/webp">
  <source srcset="image-2.jpg 1x, image-2x1.5.jpg 1.5x, image-2x2.jpg 2x" media="all and (min-width: 480px)" type="image/jpeg">
  <source srcset="image-1.jpg 1x, image-1x1.5.jpg 1.5x, image-1x2.jpg 2x" type="image/jpeg">
  <img src="image-1.jpg">
</picture>

Создания WebP экземпляров для png изображений почему-то отключено, для включения нужен вот этот патч.

Модули Blazy и Slick Carousel

Оба этих замечательных модуля, и Blazy и Slick Carousel имеют собственные форматеры для полей Image, но в настройках позволяют выбрать как классические стили изображений так и отзывчивые.

Для модуля Blazy требуется включить опцию "Support Responsive image" в подмодуле Blazy UI. В Slick Carousel все работает по умолчанию. В случае использования Slick через Views или Paragraphs (через модули Slick Views и Slick Paragraphs соответственно) собственный форматер Slick не будет использоваться и выбрать форматер Responsive image можно будет в настройках самих модулей Views и Paragraphs.

Для тех, кто не знает, что это за модули, поясню, что Blazy — это продвинутый модуль для ленивой загрузки изображений, а Slick Carousel, пожалуй, самый мощный слайдер, который я когда-либо встречал.

Модуль PhotoSwipe

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

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

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

Данная схема не работает во Views, если выводить поле c PhotoSwipe форматером через шаблон подстановки.

Также несмотря на описание на странице модуля Blazy PhotoSwipe, эту связку мне не так и не удалось заставить работать.

Итоги

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

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

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