Обрезка и маски: clip-path, mask, blend-mode
Диагональные секции, текст, растворяющийся к краю, и наложение цвета на фото — без графического редактора и без единой картинки.
Что ты научишься делать
- Обрежешь блок по произвольной форме и анимируешь эту форму
- Сделаешь плавное растворение градиентной маской
- Наложишь цвет на изображение режимом смешивания
#Три инструмента, которые часто путают
Все три меняют то, как элемент виден на экране, но делают это по-разному.
clip-path — ножницы. Вырезает форму: внутри видно, снаружи нет. Края резкие, промежуточных состояний не бывает.
mask — трафарет. Прозрачность элемента берётся из другой картинки или градиента. Можно сделать плавное растворение.
mix-blend-mode — смешивание. Определяет, как цвета элемента складываются с цветами того, что под ним. Ничего не вырезает.
#clip-path: вырезаем форму
Самый частый случай — polygon(): набор точек в процентах от размера элемента. Первая координата — по горизонтали, вторая — по вертикали.
Считать эти проценты руками мучительно, поэтому их подбирают мышью.
| Функция | Что делает |
|---|---|
polygon(x y, x y, …) | Многоугольник по точкам |
circle(40% at 50% 50%) | Круг заданного радиуса с центром в точке |
ellipse(40% 25% at 50% 50%) | Эллипс с разными радиусами |
inset(10px 20px round 12px) | Прямоугольник с отступами от краёв и скруглением |
path("M0 0 …") | Произвольный контур строкой из SVG |
#Анимация формы
clip-path анимируется, но с одним условием: у начальной и конечной формы должно быть одинаковое число точек. Иначе браузеру не между чем интерполировать, и переход будет резким.
Приём: если нужно «раскрыть» блок из точки, задай начальную форму с тем же количеством вершин, просто сведённых вместе.
.card { /* Четыре точки, но все схлопнуты к левому краю */ clip-path: polygon(0 0, 0 0, 0 100%, 0 100%); transition: clip-path .5s cubic-bezier(.22, 1, .36, 1);}.card.is-open { /* Те же четыре точки, разведённые по углам */ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);}#mask: плавные края
Маска берёт прозрачность из другого изображения. Белое (точнее, непрозрачное) — видно, чёрное или прозрачное — не видно, серое — полупрозрачно.
Чаще всего в качестве маски выступает обычный градиент — и это самый простой способ сделать растворение у края.
#Маска из SVG и из картинки
Градиент — частый, но не единственный источник маски. Подойдёт любое изображение: PNG с прозрачностью, SVG, даже вторая копия элемента.
Классический приём — одноцветная иконка из любого файла: вместо того чтобы перекрашивать картинку, её делают маской, а цвет берут из фона.
.icon-mask { inline-size: 24px; block-size: 24px; /* Цвет задаёт фон, а форму — маска */ background: currentColor; mask-image: url("icon.svg"); mask-size: contain; mask-repeat: no-repeat; mask-position: center;}#Режимы смешивания
mix-blend-mode смешивает элемент с тем, что под ним, а background-blend-mode — слои фона между собой. Разница важна: первое затрагивает соседей по странице, второе остаётся внутри одного элемента.
Режимов около шестнадцати, они пришли из графических редакторов. В вёрстке реально используются пять.
| Режим | Что делает | Типичная задача |
|---|---|---|
multiply | Затемняет: белое становится прозрачным | Наложить подпись или печать на фото |
screen | Осветляет: чёрное становится прозрачным | Блики, свечение, дым на тёмном |
overlay | Усиливает контраст | Тонирование изображения |
difference | Инвертирует по разнице цветов | Текст, читаемый на любом фоне |
luminosity | Берёт яркость сверху, цвет снизу | Одноцветное фото поверх заливки |
#Как это выглядит в реальном проекте
@layer components { /* Горизонтальная лента: края тают, значит список продолжается */ .gallery { display: flex; gap: 16px; overflow-x: auto; scroll-snap-type: x mandatory; mask-image: linear-gradient( to right, transparent, #000 24px, #000 calc(100% - 24px), transparent ); } .gallery__item { flex: none; scroll-snap-align: start; } /* Подпись поверх снимка: multiply убирает белый фон подписи */ .gallery__caption { mix-blend-mode: multiply; background: #fff; padding-inline: 8px; } /* Тень под фигурной обрезкой — только через drop-shadow */ .gallery__badge { clip-path: polygon(0 0, 100% 0, 100% 78%, 50% 100%, 0 78%); } .gallery__badge-wrap { filter: drop-shadow(0 6px 12px rgb(20 24 48 / .35)); }}#Когда НЕ надо
- Прятать содержимое обрезкой.
clip-pathубирает только изображение: текст остаётся в разметке, программа чтения с экрана его прочитает, поиск по странице найдёт. Скрывать надоhiddenилиdisplay: none. - Обрезать интерактивные элементы. Часть кнопки за пределами формы перестаёт быть видимой, но остаётся кликабельной. Человек будет попадать в пустоту.
- Смешивание на большой площади.
mix-blend-modeзаставляет браузер заново считать композицию слоёв. На полноэкранном блоке с прокруткой это заметно проседает. - Маска вместо простого скругления. Если нужно скруглить углы, есть
border-radius. Маска здесь дороже и хуже отлаживается: в инструментах разработчика её результат не показывается отдельным слоем, и понять, что именно обрезалось, можно только по картинке.
#Поддержка браузерами
| Возможность | Состояние |
|---|---|
clip-path с фигурами | Во всех современных браузерах |
clip-path: path() | Поддерживается, Safari подтянулся позже остальных |
mask-image с градиентом | Работает везде; в старых сборках WebKit нужен префикс -webkit-mask-image |
mix-blend-mode, background-blend-mode | Во всех современных браузерах |
Анимация clip-path | Работает при совпадающем числе точек |
/* База: обычный блок. Если clip-path не поддержан — просто без скоса */.hero { background: linear-gradient(135deg, #7c5cff, #b06cff); }@supports (clip-path: polygon(0 0, 100% 0, 100% 90%, 0 100%)) { .hero { clip-path: polygon(0 0, 100% 0, 100% 88%, 0 100%); }}Где спотыкаются почти все
Анимация clip-path между формами с разным числом точек
Почему Интерполировать не между чем — переход происходит скачком в середине.
Как надо Уравняй количество вершин: лишние точки схлопни в одну координату.
Тень пропала после обрезки
Почему clip-path режет элемент целиком, вместе с box-shadow.
Как надо Заверни в родителя и повесь на него filter: drop-shadow().
Маска не работает в Safari
Почему В части сборок WebKit нужно префиксное свойство.
Как надо Пиши обе строки: -webkit-mask-image и следом mask-image.
Обрезкой прячут текст
Почему Невидимый текст остаётся в разметке: читалка озвучит его, а поиск по странице найдёт.
Как надо Скрывать содержимое — задача hidden, а не обрезки.
- Обрезка clip-path
- Вырезание элемента по геометрической форме. Края резкие.
- Маска mask
- Прозрачность элемента, взятая из другого изображения или градиента.
- Режим смешивания blend mode
- Правило, по которому цвета элемента складываются с цветами подложки.
- Тень по форме drop-shadow
- Фильтр, обводящий непрозрачные пиксели, а не прямоугольник элемента.
Проверь себя
Обрезка — ножницы: видно или не видно. Маска берёт прозрачность из градиента или картинки, поэтому умеет промежуточные состояния.
Всё за пределами формы исчезает. Тень под фигурной формой делают у родителя через <code>filter: drop-shadow()</code>.
Браузер интерполирует точку в точку. Разное количество вершин — интерполировать не между чем.
Плашка совпадает только с известным однотонным фоном. На картинке или в тёмной теме она станет видна.
Обрезка меняет только картинку. Текст остаётся в разметке и будет прочитан вслух.
Задание
Диагональ и растворение
Сделай две вещи.
- Секции
.hero— скошенный низ черезclip-path: polygon(): правый нижний угол выше левого. - Блоку
.fade— растворение книзу черезmask-imageс линейным градиентом.
Разметку не трогай, картинки не добавляй.
Подсказка 1
Скос: clip-path: polygon(0 0, 100% 0, 100% 88%, 0 100%); — последние две точки задают наклон.
Подсказка 2
Растворение: mask-image: linear-gradient(to bottom, #000 55%, transparent);
Подсказка 3
Для Safari можно продублировать строкой -webkit-mask-image с тем же значением.
Показать решение
<section class="hero"> <h2>Кофейня «Полка»</h2> <p>Варим с восьми утра</p></section><div class="fade"> <p>Длинный текст, который должен растворяться книзу, а не обрываться резко.</p> <p>Вторая строка нужна, чтобы растворение было заметно.</p></div>.hero { padding: 34px 20px 50px; color: #fff; background: linear-gradient(135deg, #7c5cff, #b06cff); clip-path: polygon(0 0, 100% 0, 100% 88%, 0 100%);}.hero h2 { margin: 0 0 6px; }.hero p { margin: 0; opacity: .85; }.fade { margin-top: 16px; padding: 14px; background: #f4f4f8; border-radius: 10px; -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent); mask-image: linear-gradient(to bottom, #000 55%, transparent);}.fade p { margin: 0 0 8px; font-size: 14px; }