К написанию этого материала меня побудила статья и назревшая необходимость адаптации нескольких своих сайтов к поддержке Retina-дисплеев. Под адаптацией я понимаю подготовку изображений высокого разрешения на страницах сайта.
Пожалуй лучшим способом адаптации на сегодняшний день является способ c background-image в CSS. Но он сложно применим к обычным изображениям в теге . Поэтому я решил определить для себя список необходимых мер по достижению результата в комплексе и продолжить поиски решения. Ниже описано два способа, каждый из которых применим к своим задачам. Показанные решения не претендуют на открытие, скорее это агрегация существующих способов, описанных ранее моими коллегами по цеху веб-разработок и небольшие собственные дополнения.
Полумера или простое решение
Решение для Apache
Решение для nginx
Если вы не хотите утруждать себя обработкой контентных изображений или у вас их нет в принципе, используйте способ показанный в коде ниже и не читайте статью дальше. Например, на одном из сайтов мне не принципиально производить обработку изображений в , но элементы интерфейса (иконки) слишком бросаются в глаза. Поэтому можно использовать простой, но действенный способ:
Разумеется, класс elem приведен для примера и нужно адаптировать этот css под ваши нужды. Ну и не забываем про подготовку дополнительного комплекта нарезки изображений дизайна сайта. Разумеется, тут придется поработать, особенно если дизайн сайта насыщен изображениями, но я сторонник разового выполнения таких работ, особенно если их сравнивать с разнообразными «костылями».
Для себя определил следующие критерии для решения задачи комплексного перевода сайта на поддержку Retina:
Перед описанием решения отмечу, что во всех случаях мы предполагаем наличие на сервере копий всех изображений, которые необходимо подменять для Retina-дисплеев.
Помимо дизайна сайта для меня была актуальной задача по формированию второго комплекта контентных изображений на сервере. В моём случае изображения на сайт загружают пользователи, поэтому было необходимо посредством серверных скриптов подготовить основное изображение и его копию высокого разрешения.
Механизм загрузки и обработки графических файлов описывать не буду, поскольку он учитывает индивидуальные особенности проекта, но суть сводится к тому, что из загруженной пользователем картинки необходимо сформировать рабочее изображение для сайта и его копию, оптимизированную для Retina, в том случае, если качество загруженного изображения это позволяет. Например, в рассматриваемом проекте максимальная ширина контентных изображений составляет 800px. Разумеется, если пользователь загружает изображение с шириной 400px, сформируется только его копия шириной 400px. А вот если пользователь загрузит изображение с шириной хотя бы 1600px, что случается довольно часто, мы сможем порадовать посетителей сайта с Retina.
При этом нужно учесть, что загруженные ранее изображения сохранить в высоком разрешении уже не получится, а уменьшать значения height и width без ведома пользователя как-то неправильно. Поэтому способ должен предусматривать работу как с существующими @2x картинками, так и без оных.
Заранее обозначу, что решение далеко от идеального, оно слишком затратно по ресурсом сервера и не рекомендуется к применению. Но, к сожалению, в случаях отсутствующего nginx или доступа к его конфигу, необходима альтернатива и она описана ниже.
Первым шагом в шаблон страниц (ну или в один из ваших подключаемых js файлов) добавляем простой js-скрипт (важно чтобы этот код выполнился до начала загрузки контента страницы и любого CSS, в котором указаны изображения, которые необходимо подменить):
JS код сформирует куку, по которой серверный скрипт может понять с каким дисплеем пришел на сайт пользователь. Следующим шагом во многих описанных способах формируется редирект посредством js и скрипты CMS, анализируя куку resolution, выдают правильную версию графики. Но мы не ищем легких путей и редирект не применяем. Посредством .htaccess или конфига apache устанавливаем обработчик куки вне скриптов CMS сайта:
Первая строка RewriteCond подсказывает скрипту в какой папке лежат обрабатываемые файлы изображений.
Вторая (закомментированная) строка позволяет добавить папку-исключение в обработчик.
В третьей строке RewriteCond мы избавляемся от того самомго редиректа, определяя куку, которая была выставлена при загрузке страницы.
Четвертое условие исключает из обработки уже отданные изображения высокого разрешения (с фрагментом " @2x" в конце имени файла).
В пятой строке мы определяем к изображениям какого формата необходимо применить подмену.
Преимущества описанного способа:
Недостатки:
Для себя я внес в рассмотренный способ коррективу для того, чтобы избежать проблем с несуществующими изображениями.
В .htaccess строка
заменена на
Простой php скрипт определяет наличие запрашиваемого изображения на сервере и выдает его. Если файл изображения не найден, скрипт отдает обычную (не hi-res) картинку. Разумеется с проверкой запрашиваемых данных:
Разумеется, это изменение увеличило нагрузку на сервер, но другого решения я не видел.
Благодаря подсказке docomo и progit появилась гораздо более изящная и верная, на мой взгляд, версия решения задачи. Если на вашем сервере есть nginx и есть доступ к его конфигу, можно избавиться от неоправданно высокой нагрузки которую даст php скрипт, а за одно и сам Apache, внеся не сложную корректировку.
На момент описания этого способа я немного изменил и js для шаблонов сайта.
Итак, новый js:
Таким образом cookie hires выставляется только в том случае, если нам нужно формировать комплект изображений «высокого разрешения».
Изящество описанного выше способа сохраняется (на любом сайте без переделки кода скриптов подмена может быть осуществлена и для background css и для тега ). А вот недостаток способа с php скриптом обходится настройкой конфига nginx:
Таким образом, nginx берет на себя всю работу со статикой.
И да, спасибо progit за немаловажное уточнение: При таком подходе для всех картинок на сайте обязательно указывать width и height. Для через аттрибуты или через css-свойства, а для фоновых картинок нужно указывать background-size: width height.
За основу изложенного способа решения задачи взят метод Automatically serve retina artwork by Sam Sehnert.
UP: Всем Хабраюзерам спасибо, внес правки по комментариям.
Пожалуй лучшим способом адаптации на сегодняшний день является способ c background-image в CSS. Но он сложно применим к обычным изображениям в теге . Поэтому я решил определить для себя список необходимых мер по достижению результата в комплексе и продолжить поиски решения. Ниже описано два способа, каждый из которых применим к своим задачам. Показанные решения не претендуют на открытие, скорее это агрегация существующих способов, описанных ранее моими коллегами по цеху веб-разработок и небольшие собственные дополнения.
Полумера или простое решение
Решение для Apache
Решение для nginx
Полумера: обработчик background-image
Если вы не хотите утруждать себя обработкой контентных изображений или у вас их нет в принципе, используйте способ показанный в коде ниже и не читайте статью дальше. Например, на одном из сайтов мне не принципиально производить обработку изображений в , но элементы интерфейса (иконки) слишком бросаются в глаза. Поэтому можно использовать простой, но действенный способ:
.elem {
background-image: url(elem.png);
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
only screen and (min-resolution: 144dpi) {
.icon {
background-image: url(elem@2x.png);
}
}
Разумеется, класс elem приведен для примера и нужно адаптировать этот css под ваши нужды. Ну и не забываем про подготовку дополнительного комплекта нарезки изображений дизайна сайта. Разумеется, тут придется поработать, особенно если дизайн сайта насыщен изображениями, но я сторонник разового выполнения таких работ, особенно если их сравнивать с разнообразными «костылями».
Постановка задачи комплексной поддержки Retina
Для себя определил следующие критерии для решения задачи комплексного перевода сайта на поддержку Retina:
- способ должен быть универсальным и должен включать обработку всех без исключений изображений на сайте;
- способ не должен приводить к обновлению страниц в результате какого-либо редиректа;
- способ должен быть максимально лояльным к пользователю, он не должен закачивать в браузер пользователя исходную картинку и подготовленную для Retina.
Перед описанием решения отмечу, что во всех случаях мы предполагаем наличие на сервере копий всех изображений, которые необходимо подменять для Retina-дисплеев.
Помимо дизайна сайта для меня была актуальной задача по формированию второго комплекта контентных изображений на сервере. В моём случае изображения на сайт загружают пользователи, поэтому было необходимо посредством серверных скриптов подготовить основное изображение и его копию высокого разрешения.
Механизм загрузки и обработки графических файлов описывать не буду, поскольку он учитывает индивидуальные особенности проекта, но суть сводится к тому, что из загруженной пользователем картинки необходимо сформировать рабочее изображение для сайта и его копию, оптимизированную для Retina, в том случае, если качество загруженного изображения это позволяет. Например, в рассматриваемом проекте максимальная ширина контентных изображений составляет 800px. Разумеется, если пользователь загружает изображение с шириной 400px, сформируется только его копия шириной 400px. А вот если пользователь загрузит изображение с шириной хотя бы 1600px, что случается довольно часто, мы сможем порадовать посетителей сайта с Retina.
При этом нужно учесть, что загруженные ранее изображения сохранить в высоком разрешении уже не получится, а уменьшать значения height и width без ведома пользователя как-то неправильно. Поэтому способ должен предусматривать работу как с существующими @2x картинками, так и без оных.
Решение для Apache
Заранее обозначу, что решение далеко от идеального, оно слишком затратно по ресурсом сервера и не рекомендуется к применению. Но, к сожалению, в случаях отсутствующего nginx или доступа к его конфигу, необходима альтернатива и она описана ниже.
Первым шагом в шаблон страниц (ну или в один из ваших подключаемых js файлов) добавляем простой js-скрипт (важно чтобы этот код выполнился до начала загрузки контента страницы и любого CSS, в котором указаны изображения, которые необходимо подменить):
<script>document.cookie='resolution='+Math.max(screen.width,screen.height)+("devicePixelRatio" in window ? ","+devicePixelRatio : ",1")+'; path=/';</script>
JS код сформирует куку, по которой серверный скрипт может понять с каким дисплеем пришел на сайт пользователь. Следующим шагом во многих описанных способах формируется редирект посредством js и скрипты CMS, анализируя куку resolution, выдают правильную версию графики. Но мы не ищем легких путей и редирект не применяем. Посредством .htaccess или конфига apache устанавливаем обработчик куки вне скриптов CMS сайта:
RewriteCond %{REQUEST_URI} ^/images
#RewriteCond %{REQUEST_URI} !^/images/excluded
RewriteCond %{HTTP_COOKIE} resolution=([^;,]+),2
RewriteCond %{REQUEST_URI} !@2x\.(jpe?g|gif|png)$
RewriteRule ([^.]+)\.(jpe?g|gif|png)$ $1@2x.$2 [L]
Первая строка RewriteCond подсказывает скрипту в какой папке лежат обрабатываемые файлы изображений.
Вторая (закомментированная) строка позволяет добавить папку-исключение в обработчик.
В третьей строке RewriteCond мы избавляемся от того самомго редиректа, определяя куку, которая была выставлена при загрузке страницы.
Четвертое условие исключает из обработки уже отданные изображения высокого разрешения (с фрагментом " @2x" в конце имени файла).
В пятой строке мы определяем к изображениям какого формата необходимо применить подмену.
Преимущества описанного способа:
- решение работает для всех изображений сайта;
- нет редиректа страницы;
- при отсутствии куки «resolution» пользователь увидит дефолтный контент страницы.
Недостатки:
- (по мнению автора этого способа) не учтен пользовательский способ подключению к интернету и возможность выкачивания им изображений в высоком разрешении;
- существенный недостаток, с моей точки зрения, в необходимости использования редиректа на hi-res изображение посредством Apache;
- также, с моей точки зрения, существенная проблема состоит в отсутствие проверки наличия @2x изображения на сервере.
Для себя я внес в рассмотренный способ коррективу для того, чтобы избежать проблем с несуществующими изображениями.
В .htaccess строка
RewriteRule ([^.]+)\.(jpe?g|gif|png)$ $1@2x.$2 [L]
заменена на
RewriteRule ([^.]+)\.(jpe?g|gif|png)$ retina.php?img=$1@2x.$2
Простой php скрипт определяет наличие запрашиваемого изображения на сервере и выдает его. Если файл изображения не найден, скрипт отдает обычную (не hi-res) картинку. Разумеется с проверкой запрашиваемых данных:
<?php
if (!empty($_GET['img']) && mb_substr_count($_GET['img'], '@2x'))
{
function isImg ($dress)
{
if (function_exists('exif_imagetype'))
$type_file = exif_imagetype ($dress);
else
{
$type_file = getimagesize ($dress);
$type_file = $type_file[2];
}
// проверка типа файла изображения
// 1 = GIF, 2 = JPG, 3 = PNG
if (in_array($type_file, array(1,2,3)))
{
if (type_file==1)
header("Content-Type: image/gif");
elseif ($type_file==2)
header("Content-Type: image/jpeg");
else
header("Content-Type: image/x-png");
header(sprintf('Content-Length: %d', filesize($dress)));
return true;
}
unset ($type_file);
return false;
}
$_GET['img'] = $_SERVER['DOCUMENT_ROOT'].'/'.$_GET['img'];
if (file_exists($_GET['img']) && isImg ($_GET['img']))
die (file_get_contents($_GET['img']));
else
{
$_GET['img'] = str_replace("@2x", "", $_GET['img']);
if (file_exists($_GET['img']) && isImg ($_GET['img']))
die (file_get_contents($_GET['img'])); // я сознательно не использовал readfile(), т.к. хостеры часто ее блокируют. Правильно отдаем файл: http://habrahabr.ru/post/151795/
}
header("HTTP/1.1 404 Not Found"); // если нет картинок
}
else
header("HTTP/1.1 403 Forbidden");
?>
Разумеется, это изменение увеличило нагрузку на сервер, но другого решения я не видел.
Решение для nginx
Благодаря подсказке docomo и progit появилась гораздо более изящная и верная, на мой взгляд, версия решения задачи. Если на вашем сервере есть nginx и есть доступ к его конфигу, можно избавиться от неоправданно высокой нагрузки которую даст php скрипт, а за одно и сам Apache, внеся не сложную корректировку.
На момент описания этого способа я немного изменил и js для шаблонов сайта.
Итак, новый js:
<script>if ("devicePixelRatio" in window && window.devicePixelRatio > 1) document.cookie='hires=1; path=/';</script>
Таким образом cookie hires выставляется только в том случае, если нам нужно формировать комплект изображений «высокого разрешения».
Изящество описанного выше способа сохраняется (на любом сайте без переделки кода скриптов подмена может быть осуществлена и для background css и для тега ). А вот недостаток способа с php скриптом обходится настройкой конфига nginx:
if ($http_cookie ~ "hires" ) {
set $hires 1;
}
location ~* ^(.+)@2x.(jpg|jpeg|gif|png)$ {
try_files $uri $1.$2 =404;
}
location ~* ^.+.(jpg|jpeg|gif|png)$ {
if ($hires = 1) {
rewrite ^(.+).(jpg|jpeg|gif|png)$ $1@2x.$2;
}
}
Таким образом, nginx берет на себя всю работу со статикой.
И да, спасибо progit за немаловажное уточнение: При таком подходе для всех картинок на сайте обязательно указывать width и height. Для через аттрибуты или через css-свойства, а для фоновых картинок нужно указывать background-size: width height.
За основу изложенного способа решения задачи взят метод Automatically serve retina artwork by Sam Sehnert.
UP: Всем Хабраюзерам спасибо, внес правки по комментариям.