Одна из ключевых проблем с адаптивным веб-дизайном, которая в последние годы является предметом многочисленных дискуссий, заключается в том, как обращаться с изображениями. Установка максимальной ширины для элементов изображения позволяет дизайнерам адаптировать их размер к размерам страницы, но сам по себе такой подход может привести к загрузке гораздо больших изображений, чем требуется.
Одним из решений этой проблемы являются «исходные наборы» — ссылки на отдельные файлы изображений с различными размерами (и, соответственно, размерами файлов), которые необходимо запрашивать и отображать с различными разрешениями. Действительно, атрибут srcset реализуется Webkit . Вы можете использовать подобный подход сразу же и в кросс-браузерно-совместимой манере с помощью Javascript; Одним из таких методов является плагин Picturefill .
По сути, Picturefill позволяет указывать разные атрибуты src
для изображения, каждый файл изображения соответствует своему медиа-запросу. Таким образом, большое изображение будет извлечено, если — и только если — размер экрана требует этого, а также оптимизированная для мобильных устройств версия изображения будет извлечена и отображена соответствующим образом.
Однако этот подход требует дополнительных усилий — сами изображения необходимо создавать в соответствующих размерах. Это тема этой статьи.
Что мы создадим
Я собираюсь продемонстрировать простое приложение для создания адаптивных изображений или производных изображений — то есть различных версий / размеров — изображений по требованию.
По сути, мы собираемся «перехватить» запросы на конкретную версию изображения, когда запрошенный файл не существует. Если этого не произойдет, мы создадим его и сохраним в файловой системе для последующих запросов.
Начиная
Я основываю этот пример на SlimBootstrap , который представляет собой скелетное приложение, использующее Slim Framework . Кроме того, вы можете клонировать или скачать полный код этого руководства с Github .
После того, как вы клонировали или загрузили скелетное приложение, есть еще несколько шагов, прежде чем начать кодирование.
В этом примере я использую ImageMagick , поэтому вам нужно убедиться, что он установлен вместе с расширением Imagick PHP — обратитесь к инструкциям по установке для получения подробной информации в соответствии с вашей операционной системой. Если вы предпочитаете, вы всегда можете переписать пример, используя GD или библиотеку, такую как Imagine .
Затем вам нужно скачать библиотеку Picturefill . Я поместил два соответствующих файла — matchmedia.js и picturefill.js — в public / js / lib.
Создайте файл конфигурации config / config.php — в той же папке есть пример скелета — и добавьте следующее:
'images.dir' => $basedir . 'public/img/', 'logs.dir' => $basedir . 'logs/'
Наконец, убедитесь, что каталог images доступен для записи как веб-сервером, так и он может создавать подкаталоги — chmod 775
с задачей. Рекомендуется сделать то же самое для каталога журналов — так что, если вы столкнетесь с какими-либо ошибками, они будут там удобно напечатаны.
Использование Picturefill
Picturefill берет «исходный набор», относящийся к различным версиям изображения, и выбирает, какой из них загружать и отображать, используя медиазапросы. На Github есть простая демонстрация .
Чтобы использовать его, вам нужно создать элемент <span>
со следующей структурой:
<span data-picture data-alt="Image description goes here"> <span data-src="img/small.jpg"></span> <span data-src="img/medium.jpg" data-media="(min-width: 400px)"></span> <span data-src="img/large.jpg" data-media="(min-width: 800px)"></span> <span data-src="img/extralarge.jpg" data-media="(min-width: 1000px)"></span> <!-- Fallback content for non-JS browsers. Same img src as the initial, unqualified source element. --> <noscript> <img src="img/small.jpg" alt="Image description goes here"> </noscript> </span>
Атрибут data-src
содержит URL-адрес каждой производной изображения, а атрибут data-media принимает любой допустимый медиа-запрос, например, min-, max-width или min-resolution.
Пути к изображениям и маршрутизация
Вот пример пути, который мы собираемся предоставить:
/img/large/uploads/blog/photo.jpg
Сломать это;
img
это каталог изображений
large
— производная; в следующем примере это
также может быть small
, medium
или extralarge
uploads/path
— это подкаталог, в данном примере двухуровневый, для организации изображений
test.jpg
— это имя файла
Если файл, на который ссылается этот путь, существует, браузер просто извлечет его, минуя любые настроенные нами маршруты. Но это тот маршрут, который нам нужно определить, чтобы перехватывать запросы для производных, которые еще не существуют.
Мы можем сопоставить все такие запросы, используя следующий шаблон:
$app->get( '/img/:parts+', function ($parts) use ($app, $c) {
Приведенный выше путь приводит к тому, что переменная $ parts заполняется следующим образом:
array(4) { [0]=> string(5) "large" [1]=> string(7) "uploads" [2]=> string(4) "blog" [3]=> string(9) "photo.jpg" }
Извлечь соответствующие части легко, убрав этот массив:
// Grab the filename $filename = array_pop($parts); // Grab the derivative $derivative = array_shift($parts); // Get the path $path = implode("/", $parts);
Поскольку мы взяли производную и имя файла из массива, не имеет значения, сколько уровней находится в файле, поскольку мы просто взрываем оставшийся массив, чтобы создать путь.
Следующим шагом является проверка того, что каталог назначения существует, а если нет, то его создание.
// assemble the destination path $destination_path = $c['config']['images.dir'] . $derivative . '/' . $path; // Create the directory, if required if (!file_exists($destination_path)) { mkdir($destination_path, 0777, true); }
Третий аргумент в пользу mkdir важен; это будет необходимо для рекурсивного создания каталогов.
Конфигурирование производных
Давайте создадим гибкую конфигурацию, которая для каждой производной определяет имя (которое будет являться частью URL), соответствующий размер и соответствующий медиа-запрос.
Вот пример в JSON:
{ "jpeg_compression": 80, "sizes": { "small" : { "width" : 180 }, "medium" : { "width" : 375, "query" : "(min-width: 400px)" }, "large" : { "width" : 480, "query" : "(min-width: 800px)" }, "extralarge" : { "width" : 768, "query" : "(min-width: 1000px)" } } }
В этом примере я определил только ширину, но функция изменения размера, которую я собираюсь использовать, позволяет вам определять ширину или высоту — или и то, и другое. Он также предоставляет функции обрезки, но для простоты я просто буду использовать scaleImage — подробности см. В документации .
Следующие шаги
Следующим шагом является поиск файла для обработки, загрузка конфигурации и изменение размера изображения.
// Now get the source path $source_path = $c['config']['images.dir'] . $path; // grab the config $config = json_decode(file_get_contents('../config/images.json'), true); // get the specs from the config $specs = $config['sizes'][$derivative]; // Create a new Imagick object $image = new Imagick(); // Ping the image $image->pingImage($source_path . '/' . $filename); // Read the image $image->readImage($source_path . '/' . $filename); // Resize, by width & height OR width OR height, depending what's configured $image->scaleImage( (isset($specs['width'])) ? $specs['width'] : 0, (isset($specs['height'])) ? $specs['height'] : 0 );
Обратите внимание, что функция scaleImage
может принимать ширину, высоту или оба значения.
Теперь, когда мы соответствующим образом изменили размер изображения, нам нужно сделать две вещи.
Во-первых, нам нужно сохранить изображение в соответствующем месте, что означает, что любой последующий запрос просто захватит само изображение — в обход нашей функции изменения размера.
Во-вторых, нам все еще нужно соблюдать исходный запрос, который означает вывод изображения, устанавливая соответствующие заголовки по пути.
// save the file, for future requests $image->writeImage($destination_path . '/' . $filename); // set the headers; first, getting the Mime type $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime_type = finfo_file($finfo, $destination_path . '/' . $filename); $app->response->headers->set('Content-Type', $mime_type); // Get the file extension, so we know how to output the file $path_info = pathinfo($filename); $ext = $path_info['extension']; // output the image echo $image; // Free up the image resources $image->destroy();
Это все, что нужно сделать! Я прорезал несколько углов для ясности, так как есть несколько вещей, о которых вам нужно подумать при производстве:
- здравомыслие, проверяющее конфигурацию
- убедиться, что файлы действительно являются изображениями
- лучшая обработка ошибок, генерирование изображения ошибки при определенных обстоятельствах
- отслеживать любые изменения в конфигурации производного изображения и восстанавливать изображения соответствующим образом
Вывод изображений
Я намеренно включил медиазапросы в конфигурацию на стороне сервера; поскольку для работы Picturefill требуется достаточно много HTML, его генерация, вероятно, является хорошим кандидатом для помощника вида. Это выходит за рамки этой статьи, но дайте мне знать в комментариях или отправьте запрос на Github, если вы найдете что-то подходящее.
Резюме
В этой статье я описал возможное решение проблемы адаптивных изображений, комбинируя на стороне сервера создание производных изображений по требованию, предназначенных для использования с библиотекой Picturefill Javascript. srcset
временем сторонняя библиотека Javascript может стать избыточной по мере развития таких стандартов, как srcset
, но, скорее всего, потребность в серверных решениях останется.
Конечно, нет необходимости просто использовать этот код для использования Picturefill или аналогичного — его также можно использовать, скажем, в системе управления контентом, чтобы генерировать разные версии изображения для разных дисплеев. Вы можете думать о чем-нибудь еще?