Перейти к содержанию

Белый логотип на любой фотографии: чем полезны режимы смешивания CSS и где они подводят

Опубликовано
  • Админы
mix-blend-mode: смешивание слоёв в CSS
Заголовок обложки — тот самый difference поверх фотографии

Редакторская вёрстка устроена так, что фотографию в герой-блок выбирает не тот, кто рисовал макет. Дизайнер положил белый логотип на тёмное небо — красиво. Через неделю в ту же карточку заезжает снимок заснеженного поля, и логотипа на нём попросту нет. Обычно спасаются полупрозрачной чёрной плашкой поверх картинки: приём честный, но фотография после него превращается в серый прямоугольник, а выбирали её ради чего-то другого.

Альтернатива — перестать трогать фон и научить текст подстраиваться под то, что под ним оказалось. Ровно для этого в CSS есть свойство mix-blend-mode. Задачу, под которую обычно пишут скрипт с разбором яркости изображения, оно закрывает одной строкой.

Решение в две строки и откуда оно взялось

Формулировку, с которой чаще всего и начинают знакомство с темой, дал Cody Lindsay Gordon: логотипу назначается mix-blend-mode: exclusion, дальше он подстраивается под яркость снимка сам.

.blend-me {
  mix-blend-mode: exclusion;
}

К этому прилагается требование к разметке, о которое спотыкаются в первую очередь: слой, с которым идёт смешивание, обязан находиться ниже элемента и приходиться ему соседом, а не родителем.

<h1 class="blend-me">Hello world</h1>
<figure>
  <img src="/hero.jpg" alt="">
</figure>

Что при этом считает браузер

За смешиванием стоит поканальная функция двух аргументов. Первый — цвет подложки, в спецификации он обозначен как Cb (backdrop). Второй — цвет накладываемого слоя, Cs (source). Значения нормализованы к отрезку от 0 до 1, а сама функция применяется к красному, зелёному и синему по отдельности. По такой схеме работают двенадцать режимов из шестнадцати; их называют разделяемыми (separable) — каналы друг на друга не влияют. У normal считать нечего (побеждает верхний цвет), формула soft-light кусочная и в ячейку не влезает, а оставшиеся десять сведены в таблицу.

РежимФормула из спецификации
multiplyB = Cb × Cs
screenB = 1 − (1 − Cb) × (1 − Cs)
darkenB = min(Cb, Cs)
lightenB = max(Cb, Cs)
color-dodgeB = min(1, Cb / (1 − Cs))
color-burnB = 1 − min(1, (1 − Cb) / Cs)
hard-lightmultiply или screen в зависимости от того, темнее ли Cs половины
overlayто же, что hard-light, но слои поменяны местами
differenceB = abs(Cb − Cs)
exclusionB = Cb + Cs − 2 × Cb × Cs

Четвёрка hue, saturation, color и luminosity живёт по другим правилам — это неразделяемые режимы. Цвет в них раскладывается на тон, насыщенность и светлоту, после чего составляющие берутся из разных слоёв. У color, скажем, тон и насыщенность приходят сверху, а светлота снизу: на этом и держится раскраска чёрно-белых снимков.

Разговоры про формулы заменяет один снимок экрана. Белое слово #fff лежит поверх одной и той же картинки, отличается только значение mix-blend-mode:

Шестнадцать режимов mix-blend-mode на одной картинке
Шестнадцать плиток, снятых в Chromium 141: цвет надписи и разметка везде совпадают

ℹ️ Справка

Пропавший текст в плитках multiply, darken и color-burn — не сбой. Белый для всех трёх является нейтральным элементом, формула схлопывается до B = Cb, то есть подложка остаётся нетронутой. С hue, saturation и color история иная: тона и насыщенности у белого источника нет, так что на выходе получается подложка, у которой отняли цвет, — на светлых участках уловить разницу почти невозможно. Так что при жалобе «режим не работает» подозрение должно падать не на браузер, а на нейтральный цвет, поданный на вход.

Слабое место — середина шкалы

Дальше начинается неприятное. Возьмём формулу difference и подставим подложку ровно посреди диапазона: abs(0.5 − 1) = 0.5. Полученное значение совпадает с фоном, надпись растворилась. С exclusion ничего не меняется: 0.5 + 1 − 2 × 0.5 × 1 = 0.5. Контраст обнуляется у обоих режимов именно на средне-серых участках — самой распространённой яркости у настоящих фотографий.

Где difference и exclusion перестают работать
Шкала яркости 0—100 % под одной и той же надписью, снимок из Chromium 141

⛔ Так делать нельзя

Назначить основному тексту интерфейса mix-blend-mode: difference, поставив на этом точку в вопросе контраста. Норматив WCAG — 4.5:1 для обычного текста и 3:1 для крупного (от 24 px, либо от 18.66 px полужирным). Итог смешивания определяется картинкой, которую загрузит редактор, поэтому заранее выдержать эти значения нельзя в принципе. Взгляд на одну-единственную фотографию проверкой не считается.

Вывод получается практический: смешивание хорошо себя ведёт на крупной декоративной типографике, монограммах, цифрах, разделителях и состояниях наведения — там, где просевшая читаемость не рушит сценарий. Подписям, кнопкам и абзацам нужна подложка с предсказуемым поведением.

Контекст наложения: элемент смешивается не со всей страницей

«У меня всё то же самое, только не работает» — самая частая жалоба, и причина у неё почти всегда одна. Смешивание захватывает не всё, что нарисовано ниже, а исключительно содержимое ближайшего контекста наложения (stacking context). Появилась между текстом и картинкой обёртка, создающая собственный контекст, — и смешиваться стало не с чем.

Список свойств, порождающих контекст наложения, заметно длиннее, чем принято помнить: сюда попадают position: fixed и contain: paint, любой filter, любой transform, объявленный will-change, opacity со значением меньше единицы, а ещё потомок flex- или grid-контейнера, которому задан z-index не в значении auto. Порождает контекст и само свойство mix-blend-mode, прямо на том узле, где его объявили.

Контекст наложения ломает mix-blend-mode
Три карточки с идентичной разметкой; расходится единственное свойство обёртки. Chromium 141

⚠️ Осторожно

Работает и обратная неприятность. Забыли завести контекст — и партнёром по смешиванию становится вообще всё, что нарисовано под элементом: соседние карточки, заливка секции, фон документа. Достаточно одной строки на общем предке:

.hero {
  isolation: isolate;
}

Значение isolate принудительно заводит контекст наложения и запирает смешивание внутри блока. Поддержка во всех браузерах — с января 2020 года.

Отдельного упоминания заслуживает случай, который вообще ни на что не похож. Фоновая заливка, объявленная у корневого элемента либо у body, продвигается браузером на канву окна — а канва в расчёте не участвует. Смешивать оказывается не с чем: партнёром выступает прозрачность, и на экране не меняется ровным счётом ничего. В обложке к этому материалу ошибки нет — снимок отдан внутреннему блоку-сцене, ему же прописан isolation: isolate, и уже поверх этой сцены лежит заголовок с difference.

Соседнее свойство background-blend-mode

Названия похожи, области действия — нет. Там, где mix-blend-mode смешивает элемент с нарисованным под ним, background-blend-mode работает внутри одного элемента: между собой смешиваются несколько background-image и лежащий под ними background-color. Дополнительные обёртки и псевдоэлементы ради затемнения становятся не нужны.

background-blend-mode: multiply, screen и дуотон
Четыре карточки, один и тот же файл в свойстве background-image. Снято в Chromium 141

Дуотон умещается в четыре строки:

.duotone {
  background-image: linear-gradient(135deg, #ff2d6f, #00d4ff), url("/hero.jpg");
  background-blend-mode: color;
  background-size: cover;
  background-position: center;
}

Тон и насыщенность отдаёт верхний слой, светлоту — нижний, и вся лента обложек приходит к единой стилистике. Слои в background-image перечисляются сверху вниз, поэтому градиент стоит первым, а фотография последней.

✅ Как правильно

Притемнить снимок под белую надпись, не заводя ни одного дополнительного узла:

.hero {
  background-image: url("/hero.jpg");
  background-color: #1b2440;
  background-blend-mode: multiply;
  background-size: cover;
}

Степень притемнения регулируется единственным значением background-color — без второго div, без ::after и без вмешательства в HTML.

Шпаргалка: режим под задачу

ЗадачаРежимЧто учесть
Крупный логотип или заголовок поверх произвольного фотоdifference, exclusionпровал контраста на средне-серых участках
Затемнить картинку под белый текстmultiply с тёмным цветомпредсказуемо, но гасит саму фотографию
Осветлить картинку под тёмный текстscreen со светлым цветомсъедает детали в тенях
Дуотон, единый стиль всех обложекcolor или luminosityсчитается один раз, можно закешировать в CSS
Наложить текстуру шума или бумагиmultiply, soft-lightдержите текстуру мелкой, её вес идёт в трафик
Свечение, вспышки, частицыscreen, plus-lighterplus-lighter есть в Chromium 141, plus-darker — нет
Перекрасить чёрно-белую иконкуcolorна цветном источнике результат непредсказуем
Подсветить область под курсоромdifference на элементе-курсоресвойство не анимируется, анимируйте позицию

❗ Главное

Анимации mix-blend-mode не поддаётся — формулировка MDN звучит как Animation type: Not animatable. Переключать значение внутри @keyframes смысла нет; анимируют соседей — то, что лежит под элементом и над ним: положение, размер, цвет.

Сборка для боевого проекта

Схема, которая выдерживает произвольный контент: предсказуемая подложка в основании, смешивание сверху как улучшение, проверка поддержки через @supports.

.hero {
  position: relative;
  isolation: isolate;
  background: url("/hero.jpg") center / cover;
}

/* база: работает всегда */
.hero__title {
  color: #fff;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.75);
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgb(0 0 0 / 0.55), transparent 60%);
}

/* улучшение: только для крупного декоративного заголовка */
@supports (mix-blend-mode: difference) {
  .hero__logo {
    mix-blend-mode: difference;
    color: #fff;
  }
}

Инверсия достаётся логотипу автоматически, а текст, обязанный читаться, держится на градиентной подложке и тени — их контраст считается заранее и проверяется любым чекером. Готовый герой-блок стоит прогнать минимум на четырёх изображениях: очень тёмном, очень светлом, средне-сером и пёстром. Больше всего провалов ловит именно средне-серое.


Источники

💬 Вопрос к сообществу

Доходило ли у вас до боевого проекта — или ограничивались градиентной подложкой? И попадались ли случаи, когда режим отрабатывал по-разному в Chromium и Safari: любопытно собрать список расхождений, пойманных руками.

Featured Replies

No posts to show

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Последние посетители 0

  • Ни одного зарегистрированного пользователя не просматривает данную страницу
Яндекс.Метрика

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.