В некоторых дизайнах иногда попадаются моменты, когда к определённому элементу необходимо применить эффект вырезания. Есть несколько способов добиться такого эффекта, используя CSS или SVG, но у каждого из них есть свои плюсы и минусы. Попробуем рассмотреть их на конкретной ситуации.

Введение

Во-первых, давайте определимся, что имеется в виду под эффектом вырезания. Речь идет о вырезании части фигуры. Вот пример:

У нас есть элемент 1 - круг и элемент 2 - прямоугольник. Необходимо из элемента 2 вырезать 1. В дизайнерских графических редакторах это просто сделать. Однако когда дело доходит до реализации аналогичных эффектов на уровне вёрстки, это может быть немного сложней по разным причинам:

  • Может потребоваться JavaScript для переключений режимов вырезанного/не вырезанного элемента
  • Элемент может содержать изображение или текст
  • Добавление рамки или теней может усложнить задачу

Давайте рассмотрим конкретный пример и то, как мы можем реализовать в нем эффект вырезания с помощью CSS или SVG.

Вырезание аватарки

Представим, дизайнером отрисован такой элемент - аватарка со статусом. Статус - кружок в нижнем правом углу, который может быть разного цвета в зависимости от состояния: зелёный (в сети), красный (недоступен) и т.д.

Как мы можем застилить статус? Мы можем добавить белую рамку или тень к зеленому кружку и положить этому конец, верно? Однако, здесь дело обстоит не так. С тёмным фоном это будет выглядеть так:

Опять же, можно просто изменить обводку значка в соответствии с фоном, но это не лучшее решение. Давайте посмотрим, как это можно сделать элегантнее.

Способ 1 - Clip Path

В этом решении используется сочетание SVG и CSS. Во-первых, нам нужно создать path и экспортировать его как SVG. Вы можете сделать это в любом редакторе, работающем с вектором (Adobe Illustrator, Figma, ...), и экспортировать его как SVG. По сути, нам нужна вот такая SVG:

Когда мы сделали эту SVG, нам нужно скопировать значение path и преобразовать их в относительные единицы. По умолчанию точки пути в SVG являются абсолютными. Это означает, что они могут растягиваться при изменении ширины и высоты. Чтобы исправить это на раннем этапе, используем этот отличный инструмент.

Затем, инлайново на странице добавляем SVG с уже исправленным clipPath.

<svg class="svg">
  <clipPath id="circle" clipPathUnits="objectBoundingBox">
    <path d="M0.5,0 C0.776,0,1,0.224,1,0.5 C1,0.603,0.969,0.7,0.915,0.779 C0.897,0.767,0.876,0.76,0.853,0.76 C0.794,0.76,0.747,0.808,0.747,0.867 C0.747,0.888,0.753,0.908,0.764,0.925 C0.687,0.972,0.597,1,0.5,1 C0.224,1,0,0.776,0,0.5 C0,0.224,0.224,0,0.5,0">
    </path>
  </clipPath>
</svg>

Значение objectBoundingBox для атрибута clipPathUnits означает, что значения внутри path относятся к ограничивающей рамке элемента, к которому применяется clip-path.

Самой картинке собственно добавляем свойство clip-path:

img {
  clip-path: url("#circle");
}

Плюсы такого способа:

  • Кроссбраузерность, работает во всех основных версиях Chrome, Edge, Firefox и Safari;
  • Подходит для очень простых примеров. В сочетании с границами или тенями могут быть трудности.

Минусы:

  • Чтобы изменить вырез, нужно изменить path. Это может вызвать осложнения, если отрисованы статусы с разными вырезаниями;
  • Требуется некоторый опыт работы со слияниями фигур в графических редакторах.

Способ 2 - CSS-маска

Эффект вырезания можно создать, используя комбинацию CSS-маски и градиентов. Давайте посмотрим, как это сделать.

Используя радиальный градиент radial-gradient, мы можем нарисовать круг, а затем заполнить остальное пространство другим цветом. Рассмотрим следующий рисунок:

Чтоб его заверстать градиентом, мы можем прописать следующее:

.image {
  background-image: radial-gradient(circle 20px at calc(100% - 25px) calc(100% - 25px), yellow 30px, deepskyblue 0); 
  /* yellow - желтый цвет, deepskyblue - голубой */
}

А чтобы воссоздать подобие аватарки, изменим цвета и добавим радиус:

.image {
  background-image: radial-gradient(circle 20px at calc(100% - 25px) calc(100% - 25px), transparent 30px, deepskyblue 0);
  border-radius: 50%;
}

Основываясь на этих возможностях, мы можем применить CSS-маску к картинке:

img {
  -webkit-mask-image: radial-gradient(circle 20px at calc(100% - 25px) calc(100% - 25px), transparent 30px, deepskyblue 0);
  border-radius: 50%;
}

Плюсы такого способа:

  • Кроссбраузерность, но с вендорным префиксом для всех браузеров, кроме Firefox.

Минусы:

  • Может быть ограниченным или сложным в исполнении для других дизайнов.

Способ 3 - SVG-маска

Для начала, давайте рассмотрим, как работает SVG-маска. Нам нужно создать маску, а затем применить ее где-нибудь внутри самого SVG.

В качестве примера, попробуем обрезать нашу аватарку просто круглой маской. Для этого достаточно в код добавить такую SVG:

<svg class="img">
  <mask id="circle">
    <circle fill="white" cx="100" cy="100" r="100"></circle>
  </mask>
  <g mask="url(#circle)">
    <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg"></image>
  </g>
</svg>

Как видим, синтаксически SVG-маска отличается от CSS-маски. Проанализируем код:

  • Во-первых, у нас появился элемент <mask>, содержащий круг <circle>;
  • Маска применяется к элементу <image>. В SVG это может быть что угодно, например, тег <g>.

На выходе получим:

Давайте добавим второй кружочек к маске:

<svg class="img">
  <mask id="circle">
    <circle fill="white" cx="100" cy="100" r="100"></circle>
    <circle fill="white" cx="86%" cy="86%" r="18"></circle>
  </mask>
  <g mask="url(#circle)">
    <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg"></image>
  </g>
</svg>

Однако на выходе получим:

Как же нам добиться эффекта вырезания второго круга? Интересная вещь - в масках объект с белой заливкой представляет область, которую мы хотим показать, а объект, залитый черным, представляет собой область, которую мы хотим скрыть.

Давайте вместо этого изменим заливку маленького кружка на черный:

<svg class="img">
  <mask id="circle">
    <circle fill="white" cx="100" cy="100" r="100"></circle>
    <circle fill="black" cx="86%" cy="86%" r="18"></circle>
  </mask>
  <g mask="url(#circle)">
    <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg"></image>
  </g>
</svg>

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

Теперь результат будет таким:

Плюсы такого способа:

  • Простое решение;
  • Отличная кроссбраузерная поддержка;
  • Код легко поддерживать.

Минусы:

  • Субъективно, у этого решения нет никаких минусов, кроме того, что это может быть немного сложно для человека, который не знает SVG.

Способ 3 является лучшим решением. К сведению, им пользуется Facebook. Если это нам что-то говорит, так это то, что это решение работает во всех браузерах без проблем, а также позволяет отключить маску, когда она не нужна.