Фронтенд-розробники подекуди використовують на сайтах елементи з вирізами, ви мусили натрапляти на них хоча б раз. Для створення такої фігури за допомогою CSS або SVG існує кілька способів. Кожен з них має свої переваги та недоліки, про які ми з вами сьогодні й побалакаємо.
Спочатку погляньмо, про які саме вирізи йдеться. Мова про вирізані ділянки фігури. Ось, наприклад:
Тобто ми вирізаємо отвір, відокремивши коло від прямокутника. У графічних редакторах це зробити просто. Однак виконати такий ефект у вебі дещо складніше:
-
Можливо, нам знадобиться скористатися JavaScript.
-
Фігура може містити зображення або текст.
-
Додавання рамок та тіней теж може завдати клопоту.
У наступних кількох розділах ми розберемо на прикладах, як можна реалізувати ефект вирізу за допомогою CSS або SVG.
Аватар
Ось справжній приклад, взятий з месенджера Facebook. Аватар користувача може мати зелену позначку, яка вказує на те, що користувач зараз в мережі. Поглянемо:
Ми можемо додати до зеленої позначки білу рамку. Але у темному режимі це матиме не найкращий вигляд.
Крім того, це може зіпсувати вигляд, якщо змінюється колір тла (наприклад під час наведення миші).
Знову ж, можна зробити колір рамки і тла однаковими, але це не найкращий варіант. Що ж нам робити?
Варіант 1 — Clip Path
Тут ми скористаємось поєднанням SVG та CSS. По-перше, нам потрібно створити шлях і експортувати його як SVG. Це можна зробити у вашому графічному редакторі, у нашому випадку це Figma.
Після цього скопіюємо значення 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>
\t
Значення objectBoundingBox
для атрибута clipPathUnits
означає, що значення всередині шляху пов'язане з граничною рамкою елемента, до якого застосовується clip-path.
.item {
clip-path:url("#circle");
}
Чудово. Але що буде, якщо ми захочемо включити внутрішню рамку для зображення? Наприклад, як запасний варіант для ситуацій, коли користувач завантажить яскраве зображення.
На жаль, неможливо додати внутрішню тінь для <img>
. Щоб обійти це, ми можемо або використати додатковий елемент HTML (наприклад, span), або псевдоелемент.
Ми додамо псевдоелемент.
.item:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid;
opacity: 0.2;
}
Йой, щось не так. Рамка з'явилася, де її не чекали. Ми можемо знову застосувати clip-path
і все працюватиме як слід.
.item:after {
/* other styles */
clip-path:url("#my-clip-path");
}
Останнє, що ми дослідимо, — це можливість додати тінь. Це можна зробити за допомогою фільтра CSS drop-shadow.
Чудово те, що він огинатиме вирізану форму аватара.
Переваги
-
Універсальність. Працює у всіх основних версіях браузерів Chrome, Edge, Firefox та Safari.
-
Чудовий варіант для простих випадків. Його можна ускладнити рамками або тінями.
Недоліки
-
Щоб прибрати ефект вирізу, нам потрібно змінити обрис. Це може бути важко зробити у компоненті з різними станами.
-
Потрібен досвід роботи зі злиттям форм у графічних редакторах.
Варіант 2 — Маска CSS
Можна створити ефект вирізу, якщо поєднати маски CSS та градієнти. З'ясуємо, як це зробити.
За допомогою radial-gradient
намалюємо коло, а потім заповнимо решту простору іншим кольором. Подивимось на цю фігуру:
.item {
background-image: radial-gradient(circle 20px at calc(100% - 30px) calc(100% - 30px), yellow 30px, purple 0);
}
Далі нам потрібно зробити коло прозорим і додати border-radius
до елемента.
.item {
background-image: radial-gradient(circle 20px at calc(100% - 30px) calc(100% - 30px), transparent 30px, purple 0);
border-radius: 50%;
}
Результат ми можемо використовувати як маску CSS, ось так:
.item {
-webkit-mask-image: radial-gradient(circle 20px at calc(100% - 30px) calc(100% - 30px), transparent 30px, purple 0);
border-radius: 50%;
}
Також можна додати зовнішній border
, оскільки він буде замаскований формою. Однак додати внутрішню рамку (внутрішню тінь) не вдасться, поки ми не використаємо для цього інший елемент, так само як і у попередньому варіанті.
Переваги
- Кросбраузерність, але потрібен вендорний префікс для всіх браузерів, окрім Firefox.
Недоліки
- Для інших випадків може мати обмеження або бути складним
Варіант 3 — Маска SVG
Спочатку ознайомимося з тим, як працює маска SVG. Нам потрібно створити маску, а потім застосувати її десь у самому SVG. Розглянемо приклад.
Це просто зображення, яке маскується по колу. У SVG маска відрізняється (за синтаксисом) від маски CSS. Проаналізуємо попередній код:
-
По-перше, ми маємо елемент
<mask>
, який містить коло. -
Маска накладається на елемент
<image>
. Наприклад, у SVG це може бути щось на зразок групи<g>
.
Спробуймо додати ще одне маленьке коло до маски.
Неперевершено. Питання в тому, як створити ефект вирізу? Для цього існує маленька хитрість.
У масках об'єкт, заповнений білим кольором, — це ділянка, яку ми хочемо показати. Водночас об'єкт чорного кольору — це те, що ми хочемо сховати.
Заповнимо маленьке коло чорною барвою.
Це і є та маленька хитрість. Дуже корисна і може надати багато можливостей для розробників. Якщо ви дизайнер, ось вам наочне пояснення.
Якщо обидва елементи маски білі, результат буде схожим на злиття двох фігур (об'єднання). Якщо один з них білий, а інший чорний, одна форма відніматиметься від іншої.
Тепер додамо внутрішню рамку аватара. З SVG це набагато простіше. Нам потрібно використати <circle>
з порожнім заповненням та напівпрозорою рамкою, застосувавши rgba()
.
<svg role="none">
\t<mask id="circle">
\t\t<circle fill="white" cx="100" cy="100" r="100"></circle>
\t\t<circle fill="black" cx="86%" cy="86%" r="18"></circle>
\t</mask>
\t<g mask="url(#circle)">
\t\t<image x="0" y="0" height="100%" width="100%" xlink:href="shadeed.jpg"
\t\t></image>
\t\t<circle fill="none" cx="100" cy="100" r="100" stroke="rgba(0,0,0,0.1)" stroke-width="2"></circle>
\t</g>
</svg>
Зауважте, що зображення та рамка розташовані всередині групи, а ця група має атрибут маски.
Переваги
-
Просте рішення.
-
Відмінна підтримка браузерами.
-
Можливість супроводу.
Недоліки
Явних недоліків немає, хіба потрібно навчитися працювати з SVG.
Це дуже вдалий метод, який, до речі, використовує Facebook. Чому він хороший? Бо без проблем працює у всіх браузерах і пропонує способи вимкнення маски, коли вона не потрібна.
Аватари, які свідчать про перегляд повідомлення
У нас є ефект вирізання, який відрізняється від попереднього прикладу. Аватари людей, котрі переглянули повідомлення у Facebook Messenger у груповій бесіді.
Для цього ефекту нам потрібно мати два кола, що перекриваються, а потім відняти одне від іншого.
Перейдемо до можливих варіантів.
Варіант 1
Спробуймо застосувати clip-path. Експортуємо шлях як SVG і перетворимо його значення на взаємопов'язані значення (як у нашому першому прикладі), і ось що отримуємо.
Експортований обрис буде дивним, якщо зображення має border-radius: 50%
. На жаль, clip-path
для цього прикладу не спрацює.
Варіант 2
Гаразд, спробуймо поєднати градієнти та маски CSS. Подібно до попереднього прикладу, нам потрібно намалювати еліпс, щоб створити ефект вирізу.
.item {
-webkit-mask-image: radial-gradient(ellipse 54px 135px at 11px center, #0000 30px, #000 0);
}
Успіх! Хоча є одна невелика проблема. Якщо придивитися, ви помітите, що краї еліпса зазубрені.
Цей ефект пов'язаний з тим, що кінцеве значення першого кольору є початковим наступного. Іншими словами, перший колір закінчується на 30px
, а другий починається від 30px
і закінчується на 100%. Щоб уникнути цього це, ми можемо змінити друге значення кольору на 30.5px
.
.item {
-webkit-mask-image: radial-gradient(ellipse 54px 135px at 11px center, #0000 30px, #000 30.5px);
}
Існує ще один спосіб з масками CSS: для нього використаємо зображення еліпса.
.item {
-webkit-mask-image: url(oval.svg);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: -26px 54%;
-webkit-mask-size: 80px 140px;
}
Як бачите, це не той результат, який нам потрібен. Ми хочемо протилежного — виключити еліпс і показати решту. Як це зробити? Виявляється, mask-composite
дає нам змогу додавати кілька масок і поєднувати їх як завгодно.
Додамо ще одну маску — суцільну заливку того ж кольору, яким закінчується linear-gradient
. Тоді використаємо mask-composite
і встановимо йому значення exclude
. Готово!
.item {
-webkit-mask-image: url(oval.svg), linear-gradient(#000, #000);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: -26px 54%, 100% 100%;;
-webkit-mask-size: 80px 140px, 100% 100%;
mask-composite: exclude;
-webkit-mask-composite: destination-out;
}
Примітка: mask-composite
працює у Firefox, а також через -webkit-mask-composite
для Chrome та Safari. Значення exclude рівнозначне destination-out
.
Переваги
- Кросбраузерність, однак потрібен вендорний префікс для всіх браузерів, окрім Firefox.
Недоліки
- Для інших випадків може мати обмеження або бути складним
Варіант 3
Пам'ятаєте, коли ми використовували два елементи <circle>
для маски, один білий, а інший чорний? Зробимо те саме й зараз.
<svg role="none" class="avatar-wrapper">
<mask id="cut">
<circle cx="50" cy="50" r="50" fill="white"></circle>
<circle fill="black" cx="-30" cy="50" r="50"></circle>
</mask>
<g mask="url(#cut)">
<image x="0" y="0" height="100%" width="100%" xlink:href="shadeed.jpg"></image>
<circle fill="none" stroke="rgba(0,0,0,0.1)" stroke-width="2"></circle>
</g>
</svg>
Змінилося лише те, що ми застосували від'ємне значення для атрибута cx
чорного <circle>
.
У реальному проєкті нам може знадобитися кілька варіантів цього компонента. Найчастіше для зміни розміру.
Тому краще застосовувати змінні CSS для обробки значень cx
, cy
та r
елемента <circle>
. Ось CSS для обробки розміру аватарів та маски:
.avatar {
--size: 100px; /* [1] */
width: var(--size);
height: var(--size);
}
/* [2] */
.avatar-circle {
cx: calc(var(--size) / 4 * -1);
cy: calc(var(--size) / 2);
r: calc(var(--size) / 2);
}
/* [3] */
.avatar-item {
margin-left: calc(var(--size) / 5.5 * -1);
}
Розберемо цей код CSS.
-
Визначення розміру аватара. Це буде використовуватися для властивостей ширини й висоти.
-
Використання розміру для пошуку розташування
cx
іcy
. -
Щоб визначити від'ємне поле між двома аватарами, нам потрібно розділити розмір на 5.5 та помножити на -1.
Погляньмо, як обчислюється значення cx.
cx: calc(var(--size) / 4 * -1);
Значення cx
та cy
починаються від центру кола. Це означає, що застосувавши половину значення
ми повністю сховаємо зображення. Розглянемо фігуру:
Для кращого унаочнення біле коло забарвлюємо фіолетовим кольором (це те, що ми хочемо показати), а чорне коло робимо прозорим, окресливши лінією (це ми хочемо сховати).
Коли значення чорного кола cx
дорівнює 0
, половину зображення буде сховано. Ми можемо прилаштуватися й замість цього використати від'ємне значення. Це значення можна визначити, спираючись на розмір ділянки вирізу.
Щоб отримати від'ємне поле між аватарами, його треба зробити майже таким самим, як і обчислене значення cx
, але трохи більшим. Аби все було правильно, потрібно поекспериментувати.
Переваги
-
Відмінна підтримка браузерами. Усе однаково працює у всіх основних браузерах.
-
Використовуючи змінні CSS, усім можна керувати за допомогою однієї змінної.
Недоліки
- Потрібні навички роботи з SVG
Хедер вебсайту
У нас є хедер із логотипом посередині. Для нього хочемо тут вирізати потрібну ділянку.
Перше, про що ви можете подумати, — потрібно додати білу рамку, чи не так? Це може частково розв'язати проблему. Та під час прокручування біла рамка на логотипі виглядатиме трохи дивно.
Тож як нам бути?
Варіант 1 — Радіальний градієнт CSS
Як і в попередньому прикладі, ми можемо використати радіальний градієнт, щоб створити вирізану ділянку в центрі хедера.
.site-header {
background: radial-gradient(circle at 50% 70%, rgba(0, 0, 0, 0) 58px, #95a57f 58px, #95a57f 100%);
}
І логотип потрібно розташувати над вирізаною ділянкою. Для цього скористаймося position: relative
зі значенням top
.
.logo {
position: relative;
top: 10px;
}
Це працює, хоч і не ідеально. А якщо нам потрібно зробити логотип та розмір вирізаної ділянки динамічними? Це означає, що його розмір повинен зменшуватися або збільшуватися залежно від розміру вікна перегляду. Перше, про що можна подумати, — це скористатися функцією CSS clamp()
.
:root {
--radius: clamp(48px, 4vw, 60px);
--logo-size: calc(calc(var(--radius) * 2) - 8px);
}
--radius
, як ви вже здогадалися, — це радіус кола. Тоді розмір логотипу має бути подвійним радіусом з невеликим зміщенням для прозорої ділянки.
Усе чудово, окрім того, що top: 10px
не працює, оскільки він має бути пропорційним розміру маски та логотипу.
Доведеться використати динамічне значення для властивості логотипу top. Що ми маємо:
-
Висота хедера
100px
. -
Центр ділянки вирізу розташований на 70% осі
y
. -
Радіус кола ми можемо отримати від змінної
--radius
.
Розглянемо пояснення.
Щоб обчислити динамічний інтервал, скористаймося такою формулою.
Distance = (Header Height * 70%) - Radius
Ось як цю формулу можна передати у CSS через функцію calc()
.
:root {
--header-height: 100px;
--radius: clamp(48px, 4vw, 60px);
--logo-size: calc(calc(var(--radius) * 2) - 8px);
}
.logo {
display: block;
position: relative;
top: calc(var(--header-height) * 0.7 - var(--radius) + 2px);
width: var(--logo-size);
margin-left: auto;
margin-right: auto;
}
.site-header {
background: radial-gradient(
circle at 50% 70%,
rgba(0, 0, 0, 0) var(--radius),
#95a57f var(--radius),
#95a57f 100%
);
}
Переваги
- Чудова підтримка браузерами
Недоліки
- Недоліки! Агов! Де ви? Мовчать, значить немає.
Варіант 2 — Маска SVG
Застосовуємо ту ж техніку, що й раніше. Маємо білий прямокутник і чорне коло. Це створить ефект вирізу.
<header class="site-header">
<img src="assets/logo.svg" alt="" />
<svg role="none" height="80">
<defs>
<mask id="hole">
<rect width="100%" height="100%" fill="white" />
<circle cx="50%" cy="80%" r="58" fill="black"></circle>
</mask>
</defs>
<rect width="100%" height="100%" mask="url(#hole)" />
</svg>
</header>
Майте на увазі, що для SVG потрібне абсолютне позиціювання, щоб охопити всю область хедера.
.site-header {
position: relative;
}
.site-header svg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
Висновок
Неймовірно, що веброзробники мають так багато способів для одного завдання. Часом різноманіття може й ускладнити роботу, але це нормально.
Сподіваємося, цей матеріал вам сподобався і був корисним. Дякуємо, що ви нами!
Довідково
- Mask Composite на MDN.
Ще немає коментарів