CSS clip и mask для всплывающих подсказок

Вырезание (clipping) и маскирование (masking) присутствуют в CSS уже продолжительное время и имеют довольно приличную поддержку браузерами. В этой статье будут рассматриваться техники вырезания и создание маски для всплывающих подсказок (tooltip), например, отображающих назначение ссылок в тексте.

Дизайн этих всплывающих подсказок отличается в зависимости от их содержимого:

Всплывающая подсказка (tooltip) с текстом
Один дизайн — это всплывающая подсказка, с простым текстом и сплошным фоном.
Всплывающая подсказка (tooltip) с картинкой
Другой дизайн заполняет всё пространство изображением.

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

Интереснее дело обстоит при использовании изображения во втором дизайне.

Рассмотрим несколько способов реализации всплывающих подсказок с разными заливками (сплошной ровный фон или изображение) и особенностями формирования хвостика-стрелки.

Способ 1. clip-path и polygon

CSS-свойство clip-path позволяет определить произвольный многоугольник с процентными значениями координат углов.

Часто этого бывает вполне достаточно, если форма вырезаемой фигуры не очень сложная. В примере ниже используется вычисление calc(), чтобы маленький треугольник-хвостик оставался одного размера независимо от того, насколько будет растянут родительский элемент.

.tooltip {
  clip-path: polygon(
    0% 0%, // точка вверху слева
    100% 0%, // точка вверху справа
    100% calc(100% - 10px), // точка внизу справа
    calc(50% + 10px) calc(100% - 10px), // правый угол хвостика
    50% 100%, // кончик хвостика
    calc(50% - 10px) calc(100% - 10px), // левый угол хвостика
    0% calc(100% - 10px) // точка внизу слева
  );
}

See this code Responsive complex clipping in CSS on x.xhtml.ru.

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

Способ 2. clip-path и SVG

Использование SVG-элемента path кажется хорошим решением. Сперва надо экспортировать вырез SVG, а затем использовать его в CSS со значением url(#clipPathId).

Посмотрите пример. Есть ли тут какие-нибудь проблемы с вырезанием?

See this code Tooltip with clip-path & svg on x.xhtml.ru.

Хвостик-стрелка растягивается в зависимости от соотношения сторон изображения. Поскольку небольшой хвостик является частью всей фигуры, он растягивается по размеру прямоугольника.

Способ 3. mask-image

С помощью CSS-свойства mask-image можно комбинировать слои маски. Это работает аналогично background-image, с помощью которого можно применить несколько градиентов или изображений к одному элементу.

Что будет, если объединить несколько слоёв маски, чтобы получилась маска нужной формы и размеров? Это именно то, что предстоит здесь сделать с двумя слоями:

clipping-css-layers
  1. Большой прямоугольник, покрывающий весь блок, кроме полосы внизу (зелёный).
  2. Изображение стрелки (розовый)

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

Приведенные в тексте примеры кода не содержат CSS-префиксов. На момент написания этой статьи для Edge, Chrome и Safari требуется использование префиксов.

Как и в случае со свойствами background, для определения двух слоев здесь будут использоваться три варианта CSS-свойства mask:

  • mask-image: это свойство позволяет нарисовать прямоугольник с градиентной заливкой и стрелку со встроенным SVG.
  • mask-position: прямоугольнику не требуется позиционирование (так как он начинается в верхнем левом углу), но хвостик надо расположить внизу и по центру.
  • mask-repeat: следует избегать повторения этих слоёв; в противном случае линейный градиент покроет весь элемент при повторении.
.tooltip {
  mask-image:
    linear-gradient(#fff, #fff), /* прямоугольник */
    url('data:image/svg+xml;utf8,'); /* положение маски хвостика: */
    0 0, /* прямоугольник */
    50% 100%; /* хвостик */
  mask-size:
    100% calc(100% - 18px), /* прямоугольник */
    38px 18px; /* хвостик */
  mask-repeat: no-repeat;
}

See this code Tooltip with mask-image on x.xhtml.ru.

Изменение размеров всплывающей подсказки или замена изображения не повлияют на размеры хвостика.

Более сложные формы

Теперь можно усложнить задачу и глубже погрузиться в эту технику. Например, попробовать с помощью такой техники маскирования слоями воспроизвести всплывающие подсказки, как в приложении iMessage для iOS.

clipping ios demo

Чтобы отобразить каждый закругленный угол, придётся использовать больше слоёв для маски:

Clip iOs
  • четыре круга, по одному на каждый угол (красные)
  • один горизонтальный прямоугольник (голубой)
  • один вертикальный прямоугольник (зелёный)
  • один SVG для хвостика (жёлтый)

CSS-код теперь получится немного длиннее, так как будет использовано больше слоёв для отрисовки фигуры, но логика останется прежней.

Для отрисовки углов используются четыре радиальных градиента. Чтобы заполнить основную фигуру, понадобятся два прямоугольника (вертикальный и горизонтальный), как на картинке выше. И, наконец, маленький хвостик, который использует встроенный SVG.

.tooltip {
  --radius: 25px;
  mask-image:
    radial-gradient(#fff (var(--radius) - 1), #fff0 var(--radius)), /* верхний левый угол */
    radial-gradient(#fff (var(--radius) - 1), #fff0 var(--radius)), /* верхний правый угол */
    radial-gradient(#fff (var(--radius) - 1), #fff0 var(--radius)), /* нижний левый угол */
    radial-gradient(#fff (var(--radius) - 1), #fff0 var(--radius)), /* нижний правый угол */
    linear-gradient(#fff, #fff), /* горизонтальный прямоугольник */
    linear-gradient(#fff, #fff), /* вертикальный прямоугольник */
    url('data:image/svg+xml;utf8,'); /* иконка справа внизу */
  mask-position: 
    0 0, /* верхний левый угол */
    100% 0, /* верхний правый угол */
    0 100%, /* нижний левый угол */
    100% 100%, /* нижний правый угол */
    0 var(--radius), /* горизонтальный прямоугольник */
    var(--radius) 0, /* вертикальный прямоугольник */
    100% 100%; /* иконка справа внизу */
  mask-size:
    (var(--radius) * 2) (var(--radius) * 2),  /* верхний левый угол */
    (var(--radius) * 2) (var(--radius) * 2),  /* верхний правый угол */
    (var(--radius) * 2) (var(--radius) * 2),  /* нижний левый угол */
    (var(--radius) * 2) (var(--radius) * 2),  /* нижний правый угол */
    100% calc(100% - #{var(--radius) * 2}), /* горизонтальный прямоугольник */
    calc(100% - #{var(--radius) * 2}) 100%, /* вертикальный прямоугольник */
    (39px / 2) (25px / 2); /* иконка справа внизу */
  mask-repeat: no-repeat;
}

See this code iMessage clipped speech bubbles on x.xhtml.ru.

Как видно из примера, можно легко поворачивать и перемещать хвостик-стрелку влево или вправо.

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