Зображення, що ще не завантажилося, залишає дірку на сторінці. Звичайні рішення - сірий блок, розмитий thumbnail у HTML або взагалі нічого. Дешевша альтернатива, що покриває більшість потреб - заповнити простір середнім кольором фото. Так макет виглядає навмисним ще до того, як завантажиться перший піксель справжнього зображення.
Laravel 13.24 додав метод dominantColor() до вбудованого API для роботи із зображеннями, який повертає цей колір без окремого пакунка. У цій статті розглянемо, як отримати колір, де його зберігати та як ним користуватися.
Налаштування
Drайвери GD та Imagick компонента для роботі із зображеннями базуються на Intervention Image - це рекомендована, а не обов'язкова залежність:
composer require intervention/image:^4.0
Більше нічого не потрібно. Визначення домінантного кольору працює на обох драйверах.
Отримання домінантного кольору
Викличте dominantColor() на будь-якому екземплярі зображення - отримаєте hex-рядок:
use Illuminate\Support\Facades\Image;
$color = Image::fromPath(storage_path('app/photo.jpg'))->dominantColor();
// "#8a6f4c"
Реалізація зменшує копію зображення до одного пікселя та зчитує його значення. Це саме середнє значення, а не найчастіший колір - тому фото червоного амбара в зеленому полі дасть каламутний оливковий, а не червоний. Для плейсхолдера це саме те, що потрібно: колір має лежати за всім зображенням, а не представляти його об'єкт.
Префікс # завжди включений. Зображення з альфа-каналом повертають восьмизначну форму (#rrggbbaa), тому враховуйте це при визначенні розміру колонки.
Перетворення застосовуються перед вибіркою кольору - ви отримуєте колір зображення, яке збираєтесь зберегти, а не оригіналу:
$color = Image::fromStorage('uploads/photo.jpg')
->crop(800, 600, x: 200, y: 0)
->dominantColor();
Обріжте небо - і колір зміниться.
Збереження домінантного кольору при завантаженні
Колір корисний лише тоді, коли він доступний при рендерингу сторінки. Тому обчисліть його один раз при завантаженні та зберігайте в моделі. Почніть із колонки:
Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->string('path');
$table->string('dominant_color', 9)->nullable();
$table->timestamps();
});
Потім у контролері створіть зображення один раз і використовуйте екземпляр повторно:
use App\Models\Photo;
public function store(Request $request)
{
$request->validate([
'photo' => ['required', 'image', 'max:12288'],
]);
$photo = $request->image('photo')
->orient()
->scale(width: 1600)
->optimize();
return Photo::create([
'path' => $photo->store('photos'),
'dominant_color' => $photo->dominantColor(),
]);
}
Порядок тут важливий: store() виконує pipeline, кешує оброблені байти в екземплярі та очищає pipeline. Наступний виклик dominantColor() на тому ж екземплярі бере ці закешовані байти без додаткової роботи. Поміняйте рядки місцями - і колір обчислюватиметься для одноразового клону pipeline, а store() обробить зображення вдруге. Той самий результат, вдвічі більше роботи.
Значення також мемоізується для кожного екземпляра - повторний виклик dominantColor() нічого не коштує. Оскільки кожне перетворення повертає новий екземпляр, thumbnail, відгалужений від того самого джерела, обчислює власний колір замість успадкування:
$source = Image::fromStorage('uploads/photo.jpg')->orient();
$thumbnail = $source->cover(300, 300);
$display = $source->scale(width: 1600);
$thumbnail->dominantColor(); // вибірка з обрізаного квадрата
$display->dominantColor(); // вибірка з повного кадру
Використання домінантного кольору як плейсхолдера
Маючи колір у моделі, шаблон стає фоновим кольором блоку, що вже має правильне співвідношення сторін:
<div class="aspect-[3/2] overflow-hidden rounded-lg"
style="background-color: {{ $photo->dominant_color }}">
<img src="{{ Storage::url($photo->path) }}"
alt="{{ $photo->caption }}"
loading="lazy"
class="h-full w-full object-cover">
</div>
Кольоровий блок малюється з першим кадром, зображення з'являється поверх нього, коли завантажиться, і сторінка ніколи не перебудовується. Це добре поєднується з loading="lazy" у довгій галереї, де більшість зображень не завантажується, поки читач не прокрутить.
Читабельний текст поверх кольору
Підписи чи іконки, розміщені поверх плейсхолдера, мають залишатися розбірливими незалежно від того, це фото снігового поля чи нічного неба. Відносна яскравість дає розумний поріг у кілька рядків:
namespace App\Support;
class Color
{
public static function isLight(string $hex): bool
{
[$r, $g, $b] = sscanf(substr($hex, 0, 7), '#%02x%02x%02x');
// Rec. 709 luma, 0-255.
return (0.2126 * $r + 0.7152 * $g + 0.0722 * $b) > 140;
}
}
<figcaption class="{{ Color::isLight($photo->dominant_color) ? 'text-gray-900' : 'text-white' }}">
{{ $photo->caption }}
</figcaption>
Заповнення letterbox та обертання
Та сама вибірка доступна як фонове значення всередині перетворення. Передача dominant у contain() заповнює letterbox-смуги власним кольором зображення замість фіксованого, який ви вибрали заздалегідь:
Image::fromUpload($request->file('photo'))
->contain(1200, 800, background: 'dominant')
->store('photos');
Портретне фото, доповнене до альбомного формату, отримує смуги, що належать фото - це виглядає набагато краще в сітці, ніж білі чи чорні смуги на кожній третій плитці. rotate() приймає те саме значення для кутів, що відкриваються:
$image->rotate(8, background: 'dominant');
В обох випадках вибірка відбувається для зображення на цьому етапі pipeline - обрізка раніше в ланцюжку змінює заповнення.
Що це не є
dominantColor() повертає один усереднений колір. Це не екстрактор палітри, і він не знайде акцентний колір, який вибрав би дизайнер. Якщо вам потрібен набір репрезентативних зразків або перцептивна кластеризація, що ігнорує великий плоский фон - це не та функція, і спеціалізована бібліотека залишається правильним вибором.
Для плейсхолдерів, letterbox-заповнення та тонування картки за її власним зображенням один усереднений колір - це вся робота, і тепер він постачається з фреймворком.
Додаткове читання
- Практичний посібник з вбудованої обробки зображень Laravel охоплює решту API: зміна розміру, обрізка, конвертація форматів та зберігання
- Image Dominant Color та підтримка HEIC у Laravel 13.24 містить повні примітки до релізу
- Функцію додав @dr-codswallop у #60932