Пару лет назад в CSS произошла тихая революция, вызвавшая тектонический сдвиг в разработке интерфейсов. Вкратце — нам разрешили «сверлить» настоящие дырки в блоках.
Создание блоков с вырезами всегда было трудоёмким, даже для вырезов простейшей формы. Фронтендеры годами тренировались мысленно рассекать блоки на части: прямоугольничек для контента, прямоугольничек для выреза и ещё парочка рядом с ним. И вдруг парадигма поменялась...
Простые вырезы теперь делаются в десять раз быстрее. Одной строчкой кода. Да, надо менять мышление и забывать про нарезку блоков. И как же это приятно!
В статье мы сверстаем карточку с круглым вырезом двумя способами: традиционно‑дедовским и современным. Затем сравним объём кода, простоту и гибкость получившихся реализаций. И порадуемся, что будущее уже наступило!
Типовые требования к поведению компонента
Мы будем верстать эту карточку:

При реализации блоков с такими вырезами обычно учитывают два требования:
Под карточкой находится неоднородный фон: фотография или градиент, который должен по‑настоящему «просвечивать» в вырезе.
Карточка может изменять размеры. Если карточка резиновая, то она может изменять ширину, а при изменении контента может изменяться высота.
Парадигма приклеивания к блокам
У опытного верстальщика при взгляде на такой вырез возникают флэшбеки, а затем одна и та же схема:

Мы ищем прямоугольную область, в которой будет «жить» контент (выделена жёлтым). А затем «приклеиваем» к ней дополнительные элементы, которые формируют вырез.
Обычно вырез раскладывается на три части:
центральную — с фиксированными размерами (выделена зелёным);
верхнюю и нижнюю — которые растягиваются по высоте карточки (выделены красным).
Абсолютно типичный и рабочий подход. Давайте его реализуем.
Классическая реализация
Шаг 0. Базовая разметка
Начнём с самой простой разметки: одна карточка и контент внутри.
<div class="card"> <p class="tag">Устьянский район</p> <h1 class="headline">Осенние берега Устьи</h1> <p class="lead">Здесь природа говорит шёпотом —<br> ощути северную тишину.</p> <button class="button">Купить билет</button> </div>
Пока это просто набор текстовых элементов.

Шаг 1. Планируем "нарезку" компонента
Классическую реализацию мы сделаем с помощью флексбокса, чтобы она была понятна всем. Да, можно использовать и гриды, и позиционирование, но сути это не изменит. Поэтому используем флексбокс и планируем дополнительные обёртки с учётом этого.
Чтобы получить карточку с вырезом, нам понадобится две колонки. В одной колонке будет «жить» вырез, в другой — контент. Внутри колонки с вырезом будет три блока: верхний, центральный и нижний.
Центральный блок будет иметь фиксированную высоту, а крайние — растягиваться, подстраиваясь под высоту карточки.
Шаг 2. Добавляем обёртки в разметку
Добавим дополнительные обёртки.
<div class="card"> <div class="cutout"> <div class="cutout-top"></div> <div class="cutout-center"></div> <div class="cutout-bottom"></div> </div> <div class="card-content"> <p class="tag">Устьянский район</p> <h1 class="headline">Осенние берега Устьи</h1> <p class="lead">Здесь природа говорит шёпотом —<br> ощути северную тишину.</p> <button class="button">Купить билет</button> </div> </div>
Уже сейчас видно, как DOM начал разрастаться.
Шаг 3. Базовая геометрия карточки
Делаем карточку флекс‑контейнером и задаём ширины колонок. Нам приходится подбирать ширину блоков и внутренние отступы, чтобы получить нужное расстояние от контента до выреза.
.card { display: flex; width: 620px; } .card-content { width: 420px; padding: 50px; padding-left: 75px; color: #6b5742; background-color: #fdf6f0; } .cutout { width: 75px; }
Теперь у нас есть две колонки: контент и будущий вырез.

Шаг 4. Стилизуем блоки вокруг выреза
Превращаем блок .cutout во флекс‑контейнер и направляем его главную ось вниз. Верхний и нижний блоки растягиваем по высоте с помощью flex-grow: 1; и задаём им фон. Центральному блоку, внутри которого будет изображение для выреза, задаём фиксированную высоту.
.cutout { display: flex; flex-direction: column; } .cutout-top, .cutout-bottom { flex-grow: 1; background-color: #fdf6f0; } .cutout-center { height: 150px; }
При изменении высоты карточки крайние элементы растягиваются, центральный остаётся фиксированным.

Шаг 5. Рисуем сам вырез
Форму выреза можно реализовать по‑разному: использовать PNG с полупрозрачностью, или SVG, или нарисовать с помощью CSS‑градиента. Мы используем радиальный градиент, чтобы не подключать лишнюю картинку.
.cutout-center { height: 150px; background-image: radial-gradient( circle at 0 50%, transparent 74.5px, #fdf6f0 75px ); }
Вырез появился. Карточка выглядит как задумано.

Проверяем на переполнение
Добавим больше текста. Высота карточки увеличилась — вырез остался на месте. Всё работает.

Реализация готова. Можно подвести итог. Нам понадобилось 5 дополнительных обёрток и около 20 строчек кода. Это не смертельно. Карточка ведёт себя хорошо. За исключением одного неприятного ограничения.
Ограничение — неоднородный фон у карточки
Попробуем добавить карточке градиентный фон. Добавляем его блоку с контентом.
.card-content { background-image: linear-gradient(135deg, #fff6e9, #fdd9b5, #f4a261); }
Нам нужно, чтобы фон заполнял всю карточку, поэтому добавим его и блокам .cutout-top и .cutout-bottom:
.cutout-top, .cutout-bottom { background-image: linear-gradient(135deg, #fff6e9, #fdd9b5, #f4a261); }
И тут становится больно. Карточка состоит из отдельных блоков и фон ломается на стыках.

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

Для этого нам понадобятся CSS‑маски.
Шаг 1. Возвращаемся к простой карточке
Убираем все дополнительные обёртки и оставляем один элемент .card. Единственное изменение — размер отступа слева. Увеличиваем его с учётом размера выреза.
.card { width: 420px; padding: 50px; padding-left: 150px; color: #6b5742; background-color: #fdf6f0; }
Результат такой:

Шаг 2. Добавляем одно свойство — mask-image
.card { mask-image: radial-gradient( circle at 0 50%, transparent 74.5px, black 75px ); }
Всё. Карточка с вырезом готова.

Лирическое отступление: как работает эта маска
Не так много разработчиков знакомы с CSS‑масками, да и радиальные градиенты никогда не отличались популярностью. Поэтому давайте подробнее разберём, как сделан этот вырез.
Семейство свойств mask-* работает почти так же как семейство свойств background-*. Если вы работали с фоновыми изображениями, то и с масками разберётесь быстро.
Чтобы понять механику, заменим mask-image на background-image. Это позволит увидеть форму маски. Вначале создадим простейший радиальный градиент, круглой формы с двумя цветами (прозрачным и чёрным):
background-image: radial-gradient( circle, transparent, black );
Результат:

Теперь добавим резкий цветовой переход между цветами, задав очень близкие позиции цвета в колорстопах. Разница в полпикселя нужна, чтобы сгладить края перехода и убрать эффект зубцов.
background-image: radial-gradient( circle, transparent 74.5px, black 75px );
Результат:

Сместим центр градиента на середину левой стороны блока с помощью at 0 50%. Форма маски готова.
background-image: radial-gradient( circle at 0 50%, transparent 74.5px, black 75px );
Результат:

Прозрачная часть — это то, что будет вырезано. Чёрная — то, что останется. Чтобы получить вырез, остаётся только заменить background-image на mask-image.
Переполнение и неоднородный фон
Благодаря использованию маски карточка остаётся цельной. Поэтому:
она корректно реагирует на изменение высоты и ширины;
карточке можно задавать неоднородный фон (градиент, изображение);
можно комбинировать несколько фонов.
.card { background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 50%, transparent 51%), linear-gradient(135deg, #fff6e9, #fdd9b5, #f4a261); background-repeat: repeat-y, no-repeat; background-size: 1.5px 20px, auto auto; background-position: 115px 4px, 0 0; }
В результате мы получили карточку с неоднородным фоном и «линией отреза»:

Выводы по современной реализации
Реализация выреза на CSS‑масках на порядок лучше старого подхода:
дополнительные обёртки не нужны;
используется всего одно CSS‑свойство;
позволяет задавать неоднородный фон самой карточке.
Поддержка CSS-масок — всё отлично!
Маски доступны во всех современных браузерах уже несколько лет, а радиальные градиенты — и того дольше.
Смена парадигмы
Старая парадигма «приклеивания к блокам», в которой мы собирали вырезы из нескольких частей, умирает. На замену ей приходит парадигма «вырезания из блоков», которая на порядок упрощает создание вырезов и даёт дополнительную гибкость при использовании фонов.
Будущее уже здесь!
А где взять ещё больше сложного CSS и код всех примеров?
Подписывайтесь на мой телеграм‑канал «CSS Боль». Там собраны все материалы, видеоролики, ссылки на интерактивные пошаговые демки
