Єдине рішення проблеми адаптивних зображень

1

Від автора: Адаптивні зображення були однією з найскладніших проблем адаптивного веб-дизайну, і залишаються нею донині. Поки у браузерних вендорів є «рідне» рішення, нам доводиться думати «на ходу» і знаходити власні рішення. Зображення «Retina» особливо проблемні, тому що якщо ви масштабували свою розмітку em’ами або у відсотках (як і має бути!), то не можете бути впевнені в точних піксельних розмірах кожного показується зображення.

У цій статті ми розглянемо одне з рішень проблеми, реалізоване нами своєму сайті-портфоліо на Etch, де можна бачити ранню працюючу версію у всій її красі.

Вимоги

Для Etch ми застосували підхід content-first. Ми знали, що потрібно використовувати безліч зображень, щоб швидко передати атмосферу компанії. Вони будуть супроводжуватися маленькими фрагментами, або «фразами», тексту.

Таке рішення було прийнято стосовно розміру зображень і їх формату. Щоб максимально контролювати дизайн, потрібен був ідеальний контроль над зображеннями. Ми вирішили використовувати Instagram в якості бази для своїх зображень з наступних причин:

Фіксоване співвідношення розмірів.

Велика частина службовців тут його вже використовують.

Такі прекрасні фільтри.

Instagram дозволяє максимальний розмір зображення 600 пікселів, тому в нас для роботи вже був перший набір тематичних обмежень: зображення зі співвідношенням розмірів 1:1 і максимальним розміром зображень 600 × 600. Жорсткі вимоги щодо вмісту полегшили процес дизайну, тому що обмежили наші можливості, роблячи, таким чином, всі рішення вимушеними.

Покінчивши з контентом, ми почали розглядати дизайн. І знову для збереження максимального контролю зійшлися на стилі адаптивного дизайну з фіксованими розмірами колонок. Ми застосували блокові елементи сітки, що відповідають максимальним розміром наших зображень. Кожен блок сітки або 600 × 600, або 300 × 300, що теж зручно підходило до чорновому начерку мінімальної ширини вікна перегляду веб 320 пікселів.

Під час тестування дизайнерського процесу було вирішено, що нам потрібні ще два розміру зображень: контрольні 100 × 100 і основні образи, які простягаються у всю ширину вмісту (300, 600, 900, 1200, 1600, 1800). Крім того, всі зображення доведеться підготувати до сумісності з Retina — або, по-іншому, більш щільним екранів. Так ми дісталися до останнього набору вимог до адаптивним зображень цього вебсайту:

Потенційна ширина зображення в пікселях): 100, 300, 600, 900, 1200, 1600, 1800

Підготовленість до екранів Retina

Повинні бути чіткими при мінімальній зміні розмірів (деякі помічають відхилення як навіть зменшених зображень)

Необхідність міняти розміри такої кількості зображень вручну, навіть з допомогою скрипта Photoshop, здавалася занадто величезною працею. Всі подібні речі слід автоматизувати для того, щоб замість них можна було зосередитися на захоплююче цікаве кодуванні. Крім того, автоматизація видаляє ризик здійснення людської помилки, типу «забув це зробити». Ідеальне для нас рішення – це один раз додати файл зображення і забути про нього.

Звичайні рішення

Перед переходом до нашого власним рішенням давайте розглянемо деякі з використовуваних в даний час. Щоб встигнути за популярними сьогодні методами і роботою, проделываемой веб-співтовариством для пошуку рішення проблеми адаптивних зображень, перейдемо до групи адаптивних зображень спільноти W3C.

ЕЛЕМЕНТ PICTURE

Перший за рахунком – елемент picture. Хоча в даний час у нього відсутня «рідна» підтримка, а браузерні вендори ще роздумують над застосуванням picture проти srcset і проти чого б ще не обговорювалося, його можна використовувати з полілфілом.

Елемент picture відмінно підходить, якщо крім простого зміни розміру вам потрібно передавати зображення різної форми, фокальної точки або інших властивостей. Однак доведеться попередньо масштабувати зображення до того, як перейти безпосередньо до HTML. Це рішення, крім того, з’єднує HTML з медиазапросами, а ми знаємо, що об’єднання CSS і HTML погано відбивається на підтримці. Також воно не стосується дисплеїв високої чіткості.

Для цього проекту елемент picture вимагав занадто багато конфігурування і ручного створення і зберігання різних розмірів зображень і їх файлових шляхів.

SRCSET

Ще одне популярне рішення — srcset, останнім часом стало «рідним» в деяких браузерах на базі WebKit. Під час створення нашого плагіна воно ще не було доступним і, схоже, доведеться трохи почекати, поки кросбраузерна сумісність не стане досить хорошою для того, щоб можна було користуватися ним без альтернативного JavaScript’а. На момент написання цього тексту srcset придатний до вживання тільки в «нічних» збірках Chrome та Safari.

Єдине рішення проблеми адаптивних зображень

Наведений вище фрагмент коду демонструє вживання srcset. І знову видно, що воно сильно додає кількість вбудованих в HTML медиазапросов, і дуже мене дістає. Крім того, нам довелося б створювати різні розміри зображень до часу виконання, що означає або встановлення скрипта, або стомлюючу роботу вручну.

АНАЛІЗ НА СТОРОНІ СЕРВЕРА

Якщо ви схильні не користуватися JavaScript’ом для того, щоб вирішити, яке зображення слід передати, то можете спробувати проаналізувати агента користувача на стороні сервера і автоматично послати зображення потрібного розміру. Як правило, ми майже завжди говоримо: не покладайтеся на аналіз з серверної сторони. Він дуже ненадійний, а багато браузери містять неточні рядка користувальницького агента. Крім того, божевільна кількість випускаються кожен місяць нових пристроїв і розмірів екранів влаштують вам адову прірву роботи по підтримці проекту.

ІНШІ РІШЕННЯ

Ми вирішили створити власний плагін тому, що включення коду розмітки HTML здавалося нам небажаним, а необхідність заздалегідь створювати різні розміри зображень абсолютно не приваблювала.
Якщо хочете проаналізувати інші традиційні рішення з метою вирішити, яке з них краще підійде для вашого проекту, в Мережі є кілька відмінних статей і прикладів, включаючи одну на цьому самому сайті.

Вибираємо рішення проблеми адаптивних зображень Шеррі Александер (Sherri Alexander) на Smashing Magazine. Александер розглядає високі вимоги до адаптивним зображень, а потім критично розбирає безліч доступних на даний момент рішень.

Яке рішення проблеми адаптивних зображень слід застосовувати Кріса Койера (Chris Coyier) на CSS-Tricks. Койер пояснює вимоги до зображень, одночасно пропонуючи відповідні рішення.

Адаптивні зображення. Рішення, дуже схоже за своєю реалізації з Etch. У ньому використовується скрипт PHP для зміни розмірів та подання відповідних зображень. На жаль, коли ми кодували веб-сайт, його ще не було.

Picturefill. Це – заміна розмітки в стилях елемента picture JavaScript’ом.

Застосування куки для адаптивних зображень Кейта Кларка (Keith Clark). Кларк користується куки для зберігання розміру екрану, а потім зображення викликаються через скрипт PHP. І знову це рішення схоже на наше, але в той час воно було ще недоступно.

Переходимо до власним рішенням.

Наше рішення

Так як HTML-синтаксисы picture і srcset здавалися занадто значним і непотрібним зусиллям, ми стали шукати більш просте рішення. Нам потрібно додати шлях окремого зображення і дати можливість CSS, JavaScript і PHP самим впоратися з подачею правильного зображення — замість HTML, яким потрібно лише мати в наявності вірну інформацію.

На момент створення вебсайту нашим вимогам не відповідала ні одне загальновідоме рішення. Більшість їх були зосереджені на емуляції picture або srcset, які ми вже визначили як невідповідні. Вебсайт Etch сильно перевантажений графікою, що зробило б зміна їх розмірів вручну дуже тривалим, схильним до скоєння помилок процесом. Навіть автоматичного виконання скрипта Photoshop було визнано вимагає занадто великої підтримки.

Наше рішення з допомогою JavaScript’а при завантаженні сторінки повинно було визначити визуализируемую ширину зображення, а потім передати src і width PHP скрипту, який змінить розмір зображень і кешує їх «на ходу» до їх назад в приміщення DOM.

Давайте розглянемо абстрактний приклад коду, написаного в HTML, JavaScript, PHP і LESS. Працює демо-приклад є на моєму сайті. Якщо хочете скористатися файлами демо-прикладу, їх можна знайти на GitHub.

Розмітка

Розмітку демо-приклад можна знайти у файлі index.html на GitHub. Ми обертаємо версію зображення в найвищій роздільній здатності в теги noscript для тих браузерів, де вимкнено JavaScript. Причина в тому, що якщо вважати продуктивність властивістю, а JavaScript – його поліпшенням, користувачі без JavaScript’а все одно отримають вміст, просто не оптимізований варіант. Потім ці елементи noscript обертаються в елемент div, де властивості зображення src і alt слугують атрибутами даних. Так забезпечується та інформація, яку JavaScript’у потрібно надіслати на сервер.

Єдине рішення проблеми адаптивних зображень

Фон пакувальника зображення встановлений як завантажуваний GIF з метою показати, що зображення завантажуються, а не несправне працюють. Альтернативне рішення (яке ми використовували в одному зі своїх побічних проектів, PhoSho) полягає у використанні самого низького дозволу зображення, яке ви будете показувати (якщо воно відомо), замість завантаження GIF’а. Воно вимагає дещо більшої ширини смуги пропускання, тому що завантажується більше одного зображення, але при завантаженні сторінки воно створює вигляд, як у одного з прогресивних JPEG’ів. Як зазвичай, дивіться, що диктують ваші вимоги.

Єдине рішення проблеми адаптивних зображень

JavaScript

JavaScript зв’язується за нас між HTML і сервером. Він доставляє масив зображень відповідної ширини з DOM і повертає з сервера файл кеша відповідного зображення.

Наш початковий скрипт посилав на сервер по одному запиту на кожне зображення, але це викликало безліч зайвих запитів. Об’єднавши зображення разом в один масив, ми скоротили кількість запитів і ощасливили цим свій сервер.

Плагін JavaScript можна відшукати в /js/resize.js сховища GitHub. По-перше, ми встановлюємо в плагіні масив контрольних точок, таких же, як контрольні точки в CSS, де змінюються розміри зображень. Для точок ми скористалися значеннями em, тому що вони базуються на основі розміру шрифту дисплея. Це вірний підхід, так як користувачі з поганим зором можуть змінювати розмір шрифта свого дисплея. Крім того, це полегшує зіставлення контрольних точок CSS з точками JavaScript. Якщо потрібно інакше, то плагін відмінно працює з контрольними точками на основі пікселів.

breakpoints: [
«32em»
«48em»
«62em»
«76em»
]

По мірі передачі кожної з цих точок потрібно перевіряти зображення, щоб переконатися, що вони правильного розміру. Під час завантаження ми спочатку встановлюємо поточну точку, відображувану для користувача за допомогою функції JavaScript matchMedia. Якщо потрібна підтримка старих браузерів (Internet Explorer 7, 8 і 9), то вам може знадобитися полифил matchMedia Підлоги Айриша (Paul Irish).

getCurrentBreakpoint: function() {
var bp, breakpoint, _fn, _i, _len, _ref,
_this = this;
bp = this.breakpoints[0];
_ref = this.breakpoints;
_fn = function(breakpoint) {
// Перевірити, чи передається контрольна точка
if (window.matchMedia && window.matchMedia(«all and (min-width:» + breakpoint + «)»).matches) {
return bp = breakpoint;
}
};
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
breakpoint = _ref[_i];
_fn(breakpoint);
}
return bp;
}

Після встановлення поточної точки ми збираємо зображення, що вимагають зміни розміру, з DOM шляхом їх перегляду та додавання в масив плагіна images.

gather: function() {
var el, els, _i, _len;
els = $(this.els);
this.images = [];
for (_i = 0, _len = els.length; _i < _len; _i++) {
el = els[_i];
this.add(el);
}
this.grabFromServer();
}

Скрипту PHP на сервері потрібно src зображення і поточна ширина для того, щоб правильно змінити його розмір, тому ми створили для відсилання на сервер якісь послідовні дані POST. Для швидкого перетворення зображення в придатну рядок запиту ми користуємося методом jQuery param.

buildQuery: function() {
var image = { image: this.images }
return $.param(image);
}

Потім зображення надсилаються через AJAX запит на сервер для зміни розміру. Зверніть увагу – єдиним запитом відомості для завантаження сервера до мінімуму.

grabFromServer: function() {
var data,
_this = this;
data = this.buildQuery();
$.get(«resize.php», data, function(data) {
var image, _i, _len;
for (_i = 0, _len = data.length; _i < _len; _i++) {
image = data[_i];
_this.loadImage(image);
}
}
);
}

Отримавши зображення з сервера, ми можемо додати їх в DOM або замінити зображення, яке вже знаходиться на своєму місці, якщо воно змінилося. Якщо це одне і те ж зображення, то нічого не відбувається, і його не доведеться завантажувати заново, тому що воно вже знаходиться в кеші браузера.

loadImage: function(image) {
var el, img,
_this = this;
el = $(«[data-src='» + image.og_src + «‘]»);
img = $(«»);
img.attr(«src», image.src).attr(«alt», el.attr(«data-alt»));
if (el.children(«img»).length) {
el.children(«img»).attr(«src», image.src);
} else {
img.load(function() {
el.append(img);
el.addClass(‘img-loaded’);
});
}
}

PHP

Якщо JavaScript просто запитує масив зображень різних розмірів, то PHP знаходиться в самій гущавині подій.

Ми користуємося двома сценаріями. Один з них – це клас resize (є в /php/lib/resize-class.php демо-приклад), який створює кешовані версії зображення потрібних нам розмірів. Другий скрипт знаходиться в кореневому каталозі Мережі, підраховує самий підходящий розмір відображення і діє як інтерфейс між JavaScript’ом і ресайзером. Запускаючи зміна розмірів і скрипт інтерфейсу, ми спочатку встановлюємо масив піксельних розмірів зображень, які потрібно відобразити, а також маршрут до папки кешованих зображень. Розміри зображень визначені в пікселях, так як сервер ще нічого не знає про поточному рівні збільшення тексту користувачем, а тільки про те, які направляються фізичні розміри зображень.

$sizes = array(
‘100’,
‘300’,
‘600’,
‘1200’,
‘1500’,
);
$cache = ‘img/cache/’;

Далі створюємо маленьку функцію, яка повертає розмір зображення, найбільш близький до поточним розміром дисплея.

function closest($search, $arr) {
$closest = null;
foreach($arr as $item) {
// відстань від ширини зображення -> поточного найближчого входу більше, ніж відстань від
if ($closest == null || abs($search — $closest) > abs($item — $search)) {
$closest = $item;
}
}
$closest = ($closest == null) ? $closest = $search : $closest;
return $closest;
}

Нарешті, можна пройтися по шляхах відправлених скрипту зображень і передати їх класу resize для отримання шляху режим файлу зображення (а при необхідності створити цей файл).

$crispy = new resize($image,$width,$cache);
$newSrc = $crispy->resizeImage();

Ми повертаємо вихідний шлях зображення для того, щоб знову відшукати його в DOM, і шлях до кэшированному файлу зображення правильно зміненим розміром. Всі маршрути зображень відсилаються назад у вигляді масиву, щоб можна було пройтися з ним і додати в HTML.

$images[] = array(‘og_src’ => $src, ‘src’ => ‘/’.$newSrc);

В клас resize для процесу зміни розмірів спочатку потрібно зібрати певну інформацію про зображення. Ми користуємося Exif для визначення виду зображення, тому що в файлу може виявитися неправильне розширення або взагалі не виявитися жодного розширення.

function __construct($fileName, $width, $cache) {
$this->src = $fileName;
$this->newWidth = $width;
$this->cache = $cache;
$this->path = $this->setPath($width);
$this->imageType = exif_imagetype($fileName);
switch($this->imageType)
{
case IMAGETYPE_JPEG:
$this->path .= ‘.jpg’;
break;
case IMAGETYPE_GIF:
$this->path .= ‘.gif’;
break;
case IMAGETYPE_PNG:
$this->path .= ‘.png’;
break;
default:
// *** Не пізнаний
break;
}
}

Вищенаведене властивість $this->path, що містить шлях режим зображення, встановлюється з допомогою поєднання ширини дисплея, хеша часу останньої трансформації файлу src, а також вихідного імені файлу.

Викликаючи метод resizeImage, ми перевіряємо, чи вже існує шлях, встановлений в $this->шлях, і якщо це так, то просто повертаємо шлях режим файлу. Якщо файл не існує, то за допомогою GD відкриваємо зображення для зміни розміру. Коли він готовий до використання, ми вважаємо співвідношення ширини до висоти вихідного зображення і застосовуємо його, щоб отримати висоту режим зображення після зміни розміру до необхідної ширини.

if ($this->image) {
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
}
$ratio = $this->height/$this->width;
$newHeight = $this->newWidth*$ratio;
Потім за допомогою GD міняємо початкове зображення до нових розмірів і повертаємо скрипту інтерфейсу шлях файлу режим зображення.
$this->imageResized = imagecreatetruecolor($this->newWidth, $newHeight);
imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $newHeight, $this->width, $this->height);
$this->saveImage($this->newWidth);
return $this->path;

Чого ми досягли?

Цей плагін дає можливість отримати одну партію зображень для web. Нам не доводиться думати про зміну їх розмірів, тому що цей процес автоматизовано. Це сильно полегшує підтримку та оновлення сайту і звільняє від безлічі зусиль, які краще присвятити більш важливим завданням. Увімкніть його одного разу і забудьте.

Давайте ще раз резюмуємо функціональність.

У своїй розмітці ми забезпечуємо пакувальник зображень, містить альтернативний варіант noscript. У цього пакувальника як еталон є атрибут даних нашого вихідного зображення у високому дозволі. Ми користуємося JavaScript’ом, щоб надіслати запит AJAX до файлу PHP на сервер, запитуючи версію цього зображення правильного розміру. Файл PHP або змінює розмір і доставляє шлях зображення потрібного розміру, або просто повертає шлях, якщо зображення вже створено. Як тільки AJAX запит виконується, ми прикріплюємо в DOM нове зображення або просто оновлюємо src, якщо воно вже додано. Якщо користувач змінює розмір свого браузера, то ми знову перевіряємо, чи потрібно використовувати зображення більш підходящого розміру.

«За» і «Проти»

У всіх рішень проблеми адаптивних зображень є аргументи «за» і «проти», і вам слід уважно дослідити кілька з них перед тим, як вибрати яке-небудь для свого проекту. Наше рішення відповідає набору спеціальних вимог, і не повинно бути рішенням за замовчуванням. Наскільки можна сказати, в даний час такого рішення не існує, тому ми рекомендуємо вам випробувати якомога більшу їх кількість.

Як оцінити дане рішення?

«ЗА»

Початкова швидке завантаження сторінки завдяки меншій вазі зображень

Одного разу встановивши, їм легко користуватися

Не вимагає багато підтримки

Швидко працює при створених кешованих файлів

Подає зображення правильного піксельного розміру (у межах допуску)

Постачає нове зображення при зміні розміру дисплея (у межах допуску)

«ПРОТИ»

Неможливо вибрати область фокусування зображення

Для повної функціональності потрібні PHP і JavaScript

Нездатне забезпечити всі можливі розміри, якщо використовуються «текучі» зображення

Може виявитися несумісним з деякими системами управління контентом

Зміна розміру зображень одним запитом означає, що при порожньому кеші вам доведеться чекати, поки не змінять розмір вони всі, а не лише одне зображення

Скрипт PHP прив’язаний до контрольних точок, тому його не можна вставити без відповідних налаштувань

Вирішення проблеми адаптивних зображень за останні місяці проробили величезний шлях, і якщо б нам довелося робити все заново, ми, ймовірно, розглянули б що-небудь на зразок цього рішення, тому що воно видаляє зі сторінки несемантический HTML шляхом модифікування .htaccess.

Резюме

Поки не з’явиться «рідне» рішення проблеми адаптивних зображень, не буде знайдено і «правильного» способу. Завжди дослідіть кілька можливостей перед тим, як зупинитися на одному з них для свого проекту. Наведений тут приклад добре працює для вебсайтів з декількома звичайними розмірами зображень по контрольним точкам, але ні в якому разі не є остаточним рішенням. А до тих пір чому б вам не спробувати створити власне рішення або спробувати обіграти дане рішення на GitHub?