Кельник
Компания
35,13
рейтинг
27 февраля 2014 в 15:01

Дизайн → Адаптивные изображения без шаманства

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

На типичных сайтах изображения могут появляться тремя способами.
  1. Быть элементами дизайна сайта (бекграунды, кнопки и т.д.).
  2. Загружаться через специальные модули (например, изображения в фотоальбом).
  3. Вставляться через WYSIWYG-редактор CMS (например, в текст статьи).


Мы захотели получить такое решение, которое было бы некой «надстройкой» над сайтом. Чтобы можно было не лезть в код CMS, через которую загружаются изображения на сайт, а также не готовить адаптивные картинки вручную.

Сначала на помощь приходит реализация Adaptive Images

Метод Adaptive Images


Идея в том, чтобы с минимумом изменений в коде сайта предоставить эти самые адаптивные картинки. Алгоритм следующий:
  1. Небольшой яваскрипт записывает в куки максимальное значение из ширины/высоты устройства. Предполагается, что картинку больше данного размера показывать нет смысла.
  2. С помощью директивы в .htaccess идет рерайт всех картинок сайта на php-скрипт adaptive-images.php.
  3. В php-скрипте есть конфиг разрешений (связанный с media queries стилей). Значение из куки подгоняется под ближайшее в большую сторону значение из этого конфига. Если изображение по запрашиваемому пути существует и его ширина больше требуемой — изображение пережимается и кладется в специальную папку кеша (если оно не было пережато заранее).
  4. Скрипт отдает картинку клиенту.

Плюсы 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
Кельник
рейтинг 35,13
Компания прекратила активность на сайте

Похожие публикации

Комментарии (19)

  • +2
    Вариант с nginx location internal не рассматривали?
    • 0
      Порт на nginx планируем, пока без необходимости было, тут он не стоял в качестве проксировщика. По идее без проблем переписываться на nginx должно.
      • 0
        Я про то, что можно проверять наличие нужного размера и если необходимо ресайзить в php а потом делать «волшебный» редирект 'X-Accel-Redirect' и nginx сам будет отдавать картинку
        • 0
          Это хорошая идея, но это если nginx стоит. В целом один раз отдать каждую картинку прямо скриптом — не так уж страшно, и не надо завязываться еще и в php на то, кто там отдает nginx или apache. Apache что-то подобное умеет?
          • 0
            Про apache не знаю, вряд ли. Nginx нужен лишь в режиме обратного прокси.
  • 0
    Nginx умеет ресайзить крупать и кешировать.
    • 0
      image_filter вот тут про это писали: http://habrahabr.ru/post/157457/ оно вам не подходит?
      • 0
        Для нагруженных решений, как тут (https://speakerdeck.com/bobrik/node-dot-js-for-millions-of-images) есть смысл морочиться. А в условиях разработки сайтов в рамках интернет-агенств все чуть по-другому :D
    • 0
      Правда Ваша, но далеко не все хостят свои проекты там, где nginx
      a) есть
      б) собран с ngx_http_image_filter_module
      в) есть возможность менять его конфиг

      И вообще нет уверенности, что этот модуль кеширует то, что жмет. Больше похоже, что он это делает на лету на каждый файл.
      • 0
        Ну да, кеш надо дополнительно настраивать. Nginx все это может сделать, но не у всех он есть.
      • 0
        Кеширует если настроить — проверено. Натсройка всего там строчек на 10, разобраться по документации очень просто.
        Просто решение совершенно безгеморойное. Можно например настроить 1 сервер для кучи проектов, сделать там поддомены img.site1.com img.site2.com.
        А далее обращаться как-то вот так: img.site1.com/200x400/background_big_image.jpg и все остальное разруливать яваскриптом.

        Хотя конечно это все не прокатит, если речь идет о куче мелких сайтов, которые просто передаются заказчику.
        • 0
          О том и речь. Сайты, мб, и не очень маленькие, но заказчики их во многих случаях забирают, и там уже действительно «не прокатит».
  • +1
    1. Можно не копировать, а прокидывать симлинки. Оно и быстрее будет.
    2. К имени файла картинки невредно добавлять номер версии. В тех (редких) случаях, когда вообще подразумевается замена картинок. Сходу и не придумаю, зачем это может быть нужно.
    3. Как вариант, из php парсить .htaccess и писать в js. Ориентируясь на дату обновления, например. Если .htaccess моложе .js Для простоты нужный кусок .htaccess сделать include.
    • 0
      1. Спасибо, да, стоит сделать так.
      2. Редко бывает, поэтому не заморачивались на эту тему
      3. Не охота на каждый вызов php читать лишний файл
  • 0
    Правило работает только на картинки из директории /upload/iblock/.

    Битрикс?
    Если так, то почему не воспользовались стандартным методом CFile::ResizeImageGet()? Он вам и картинку создаст нужных размеров и сам ее найдет при втором обращении, а при обновлении оригинала обновит и измененные изображения при новых обращениях.
    • 0
      Потому что Битрикс — это частный случай

      Мы захотели получить такое решение, которое было бы некой «надстройкой» над сайтом. Чтобы можно было не лезть в код CMS, через которую загружаются изображения на сайт, а также не готовить адаптивные картинки вручную.
  • 0
    почему бы в таком случае не переложить работу веб-сервера на js? Сгенерировать урл(ну типа там resized_image_1024_768.jpg), попытаться его загрузить(на том же js), на fail — уже отправить запрос напрямую в php и получить сгенерированную картинку?
    и ещё дальше — почему бы не переложить работу по сжиманию картинки на js? В том числе и с отправкой кэша на сервер(рисково правда давать права на запись)?
    И не надоест ли клиенту ждать пока его картинки подгрузятся?
    PS с пережимом картинок сам сталкивался и писал небольшую функцию которая отслеживает все связи. Это была фото-галерея на собственном движке, нужно было хранить мини-картинку, побольше превьюху, ещё больше одну и оригинал. Делал всё на загрузке картинки в базу. Правда там было известно что где показывать.
    • 0
      В чем профит переложения на js опроса на существования картинки? Минусы понятны, плюсы — нет.

      Как js должен сжимать? Сначала загрузить полную, потом пережать на клиенте и отдать нам? Грузите все, что хотите?))

      Пережатие картинки происходит раз для каждого разрешения и не так долго, как можно подумать.
      • 0
        >> В чем профит переложения на js опроса на существования картинки?
        Меньше нагрузка на веб-сервер, меньшее количество настроек, потенциально — меньше шансов запороть всю систему с помощью кривых рук.
        Ну и не вижу особой разницы между получением 404 и редиректом с JS и тем же, но реализованным на стороне сервера. Хоть в случае с JS действительно будет медленнее за счёт обслуживания http.
        >> Как js должен сжимать?
        canvas -> scale. Очень быстро сжимает прямо на GPU. Профит в том что время серверное вообще не тратиться.
        О поддержке canvas можете не заикаться — необходимый функционал работает везде, кроме ie 8(пользователи которого не оценят ваш адаптивный дизайн).
        Кроме того, возможно реализовать любой «интеллектуальный» алгоритм сжатия.
        >> Сначала загрузить полную, потом пережать на клиенте и отдать нам? Грузите все, что хотите?))
        Если нет уже сжатой картинки — скачать полную и сжать. После этого(или вместе с этим) послать асинхронный запрос на сервер на сжатие этой картинки в кэш.
        При такой схеме никаких проблем с правами на запись не будет.
        Преимущества того или иного подхода нужно оценивать в реальных системах.
        Я думаю если будет много графики и разных клиентов — имеет смысл распределить загрузку.
        >> Пережатие картинки происходит раз для каждого разрешения и не так долго, как можно подумать.
        А так, представьте себе, можно будет сжимать картинку не под определённые разрешния а под конкретно пользовательские!

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

Самое читаемое Дизайн