Все, кто сталкивался с задачей сделать адаптивную графику, знает, что решений уже масса, но никакого единогласно принятого не существует. Зачастую выбор и применение решения для адаптивных изображений становятся головной болью фронтенд-разработчиков. Им приходится заменять src
картинок, подгружать однопиксельные изображения и лепить прочие костыли. Нас это не устроило и мы решили сделать свой мотороллер.
На типичных сайтах изображения могут появляться тремя способами.
- Быть элементами дизайна сайта (бекграунды, кнопки и т.д.).
- Загружаться через специальные модули (например, изображения в фотоальбом).
- Вставляться через WYSIWYG-редактор CMS (например, в текст статьи).
Мы захотели получить такое решение, которое было бы некой «надстройкой» над сайтом. Чтобы можно было не лезть в код CMS, через которую загружаются изображения на сайт, а также не готовить адаптивные картинки вручную.
Сначала на помощь приходит реализация Adaptive Images…
Метод Adaptive Images
Идея в том, чтобы с минимумом изменений в коде сайта предоставить эти самые адаптивные картинки. Алгоритм следующий:
- Небольшой яваскрипт записывает в куки максимальное значение из ширины/высоты устройства. Предполагается, что картинку больше данного размера показывать нет смысла.
- С помощью директивы в
.htaccess
идет рерайт всех картинок сайта на php-скриптadaptive-images.php
. - В php-скрипте есть конфиг разрешений (связанный с media queries стилей). Значение из куки подгоняется под ближайшее в большую сторону значение из этого конфига. Если изображение по запрашиваемому пути существует и его ширина больше требуемой — изображение пережимается и кладется в специальную папку кеша (если оно не было пережато заранее).
- Скрипт отдает картинку клиенту.
Плюсы Adaptive Images
- Не требуется менять код сайта, кроме вставки одной строки js.
- Не нужно пережимать картинки вручную, они сами пережмутся при необходимости.
- Нет лишних клиентских запросов.
- Поддерживается время жизни кешированных картинок — при обновлении оригинала рано или поздно кешированная будет обновлена.
- Легко внедрить, так же просто откатить, при необходимости изменения размеров картинок все решается правкой одного массива.
Минусы Adaptive Images
А теперь немного о грустном. Данное решение подразумевает, что все (вообще все) картинки на сайте будет отдавать не nginx, не apachе, а php-скрипт. Каждая картинка — это запуск интерпретатора php (даже если картинка уже пережата). Это и медленно, и идеологически неверно.
Мы захотели сохранить плюсы данного метода и избавиться от такого ужасного недостатка.
Наш вариант
Главная идея: не запускать php-скрипт, если изображение уже существует. Для этого apache в момент редиректа должен знать название пережатой под данное разрешение картинки. Это значит, что определение разрешения должно быть переложено с php на js. Таки образом js должен не просто вычислить максимальное значение из ширины/высоты устройства, а также определить требуемое разрешение, и именно его уже записать в куку.
Также, чтобы apache мог проверить наличие картинки, он должен знать правило, по которому сохраняются пережатые изображения (в частности, название папки кеша), которое вообще говоря определено в php-скрипте.
Тут мы, очевидно, теряем немного гибкости и получаем некое дублирование информации.
- Массив разрешений должен быть продублирован в js-скрипт.
- Папка кеша и правило сохранения картинок должно быть продублировано в
.htaccess
.
Что получается
Модификация js-скрипта: adaptive.js
Здесь js, как и раньше, берет максимальное значение из высоты и ширины устройства. Определяет, есть ли модификатор плотности пикселей (ретина/не ретина), и на основе этих данных записывает в куки resolution
.
Инструкции по рерайту: .htaccess
Рерайт стал чуть сложнее, но теперь проверяет наличие пережатой картинки до того, как обратиться к бекенду.
RewriteCond %{REQUEST_URI} ^/upload/iblock/.+.(?:jpe?g|gif|png)$
Правило работает только на картинки из директории /upload/iblock/
.
RewriteCond %{REQUEST_FILENAME} -f
Причем, только на реально существующие картинки, в отличие от оригинала.
RewriteCond %{HTTP:Cookie} (^|; *)resolution=([1-9][0-9]+)
Правило сработает только при наличии цифровой куки resolution
. Если ее нет, веб-сервер отдаст оригинал изображения.
RewriteRule .* /images_adaptive/%2%{REQUEST_URI} [L]
Осуществляем переход в папку с кешированными изображениями, полагая, что эта папка называется images_adaptive
. Дальше следуют разрешение и запрашиваемый путь оригинала. То есть, если пришел запрос на /images/photo.jpg
, а разрешение пользователя подсчитано как 1024, то адаптивная картинка будет расположена по пути /images_adaptive/1024/images/photo.jpg
.
RewriteCond %{REQUEST_URI} ^/images_adaptive/.+.(?:jpe?g|gif|png)$
Пришел запрос на адаптивную картинку — рерайт с предыдущего правила, или прямой запрос, которого, между прочим, быть не должно. То есть, нигде ссылки прямо на эту папку, естественно, ставить нельзя.
RewriteCond %{REQUEST_FILENAME} !-f
Если такого файла все еще нет, то есть картинка еще не была пережата в нужный размер (и тут мы убиваем ненужные запросы в php при повторных обращениях к картинке).
RewriteRule .* ai.php [L]
Направляем запрос в наш php-скрипт, который найдет, пережмет при необходимости, и отдаст нужную картинку.
Скрипт-обработчик запросов: ai.php
Убрано определение разрешения. Нужно помнить, что в этом скрипте массив разрешений должен совпадать с аналогичным в adaptive.js
, а путь к папке кеша должен совпадать с используем в правиле .htaccess
.
Минусы нашего форка
- Избыточность хранимых данных. Если у нас есть одна картинка и 5 желаемых размеров, в которые ее нужно пережать, то сервер будет хранить в самом плохом случае все 6 изображений (оригинал и 5 копий). При этом, даже если картинку пережимать не нужно (скажем, она 300×100, а минимальное разрешение 480), то картинка все равно будет «пережата», то есть скопирована, 5 раз. Под каждое разрешение, чтобы избежать отдачи статики через php.
- Обновление адаптивных изображений. Когда оригинал картинки обновится, скрипт-обработчик ничего об этом не узнает. Тут надо думать, подходит ли это для каждого конкретного случая, и как с этим бороться. Периодически очищать кеш вовсе, или что-то еще.
- Дублирование информации в php, js и .htaccess
Решение не стало идеальным, но несмотря на минусы, мы добились желаемого результата, освободив фронтенд-разработчиков от лишней работы.
Автор: Neznaikos