Виріз в елементах: CSS чи SVG?

14 хв. читання

Фронтенд-розробники подекуди використовують на сайтах елементи з вирізами, ви мусили натрапляти на них хоча б раз. Для створення такої фігури за допомогою CSS або SVG існує кілька способів. Кожен з них має свої переваги та недоліки, про які ми з вами сьогодні й побалакаємо.

Спочатку погляньмо, про які саме вирізи йдеться. Мова про вирізані ділянки фігури. Ось, наприклад:

Виріз в елементах: CSS чи SVG?

Тобто ми вирізаємо отвір, відокремивши коло від прямокутника. У графічних редакторах це зробити просто. Однак виконати такий ефект у вебі дещо складніше:

  • Можливо, нам знадобиться скористатися JavaScript.

  • Фігура може містити зображення або текст.

  • Додавання рамок та тіней теж може завдати клопоту.

У наступних кількох розділах ми розберемо на прикладах, як можна реалізувати ефект вирізу за допомогою CSS або SVG.

Аватар

Ось справжній приклад, взятий з месенджера Facebook. Аватар користувача може мати зелену позначку, яка вказує на те, що користувач зараз в мережі. Поглянемо:

Виріз в елементах: CSS чи SVG?

Ми можемо додати до зеленої позначки білу рамку. Але у темному режимі це матиме не найкращий вигляд.

Виріз в елементах: CSS чи SVG?

Крім того, це може зіпсувати вигляд, якщо змінюється колір тла (наприклад під час наведення миші).

Виріз в елементах: CSS чи SVG?

Знову ж, можна зробити колір рамки і тла однаковими, але це не найкращий варіант. Що ж нам робити?

Варіант 1 — Clip Path

Тут ми скористаємось поєднанням SVG та CSS. По-перше, нам потрібно створити шлях і експортувати його як SVG. Це можна зробити у вашому графічному редакторі, у нашому випадку це Figma.

Виріз в елементах: CSS чи SVG?

Після цього скопіюємо значення path та перетворимо їх на відносні одиниці. Типово точки шляху SVG є абсолютними. Це означає, що вони можуть розтягуватися, якщо змінюються ширина та висота. Щоб виправити це на ранній стадії, ми можемо скористатися цим інструментом.

Виріз в елементах: CSS чи 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");
}
Виріз в елементах: CSS чи SVG?

Чудово. Але що буде, якщо ми захочемо включити внутрішню рамку для зображення? Наприклад, як запасний варіант для ситуацій, коли користувач завантажить яскраве зображення.

Виріз в елементах: CSS чи SVG?

На жаль, неможливо додати внутрішню тінь для <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;
}
Виріз в елементах: CSS чи SVG?

Йой, щось не так. Рамка з'явилася, де її не чекали. Ми можемо знову застосувати clip-path і все працюватиме як слід.

.item:after {
    /* other styles */
    clip-path:url("#my-clip-path");
}
Виріз в елементах: CSS чи SVG?

Останнє, що ми дослідимо, — це можливість додати тінь. Це можна зробити за допомогою фільтра CSS drop-shadow. Чудово те, що він огинатиме вирізану форму аватара.

Виріз в елементах: CSS чи SVG?

Переваги

  • Універсальність. Працює у всіх основних версіях браузерів Chrome, Edge, Firefox та Safari.

  • Чудовий варіант для простих випадків. Його можна ускладнити рамками або тінями.

Недоліки

  • Щоб прибрати ефект вирізу, нам потрібно змінити обрис. Це може бути важко зробити у компоненті з різними станами.

  • Потрібен досвід роботи зі злиттям форм у графічних редакторах.

Варіант 2 — Маска CSS

Можна створити ефект вирізу, якщо поєднати маски CSS та градієнти. З'ясуємо, як це зробити.

За допомогою radial-gradient намалюємо коло, а потім заповнимо решту простору іншим кольором. Подивимось на цю фігуру:

Виріз в елементах: CSS чи SVG?
.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 чи SVG?

Результат ми можемо використовувати як маску 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. Розглянемо приклад.

Виріз в елементах: CSS чи SVG?

Це просто зображення, яке маскується по колу. У SVG маска відрізняється (за синтаксисом) від маски CSS. Проаналізуємо попередній код:

  1. По-перше, ми маємо елемент <mask>, який містить коло.

  2. Маска накладається на елемент <image>. Наприклад, у SVG це може бути щось на зразок групи <g>.

Спробуймо додати ще одне маленьке коло до маски.

Виріз в елементах: CSS чи SVG?

Неперевершено. Питання в тому, як створити ефект вирізу? Для цього існує маленька хитрість.

У масках об'єкт, заповнений білим кольором, — це ділянка, яку ми хочемо показати. Водночас об'єкт чорного кольору — це те, що ми хочемо сховати.

Заповнимо маленьке коло чорною барвою.

Виріз в елементах: CSS чи SVG?

Це і є та маленька хитрість. Дуже корисна і може надати багато можливостей для розробників. Якщо ви дизайнер, ось вам наочне пояснення.

Виріз в елементах: CSS чи SVG?

Якщо обидва елементи маски білі, результат буде схожим на злиття двох фігур (об'єднання). Якщо один з них білий, а інший чорний, одна форма відніматиметься від іншої.

Тепер додамо внутрішню рамку аватара. З 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 у груповій бесіді.

Виріз в елементах: CSS чи SVG?

Для цього ефекту нам потрібно мати два кола, що перекриваються, а потім відняти одне від іншого.

Виріз в елементах: CSS чи SVG?

Перейдемо до можливих варіантів.

Варіант 1

Спробуймо застосувати clip-path. Експортуємо шлях як SVG і перетворимо його значення на взаємопов'язані значення (як у нашому першому прикладі), і ось що отримуємо.

Виріз в елементах: CSS чи SVG?

Експортований обрис буде дивним, якщо зображення має border-radius: 50%. На жаль, clip-path для цього прикладу не спрацює.

Варіант 2

Гаразд, спробуймо поєднати градієнти та маски CSS. Подібно до попереднього прикладу, нам потрібно намалювати еліпс, щоб створити ефект вирізу.

.item {
    -webkit-mask-image: radial-gradient(ellipse 54px 135px at 11px center, #0000 30px, #000 0);
}
Виріз в елементах: CSS чи SVG?

Успіх! Хоча є одна невелика проблема. Якщо придивитися, ви помітите, що краї еліпса зазубрені.

Виріз в елементах: CSS чи SVG?

Цей ефект пов'язаний з тим, що кінцеве значення першого кольору є початковим наступного. Іншими словами, перший колір закінчується на 30px, а другий починається від 30px і закінчується на 100%. Щоб уникнути цього це, ми можемо змінити друге значення кольору на 30.5px.

.item {
    -webkit-mask-image: radial-gradient(ellipse 54px 135px at 11px center, #0000 30px, #000 30.5px);
}
Виріз в елементах: CSS чи SVG?

Існує ще один спосіб з масками CSS: для нього використаємо зображення еліпса.

.item {
    -webkit-mask-image: url(oval.svg);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: -26px 54%;
    -webkit-mask-size: 80px 140px;
}
Виріз в елементах: CSS чи SVG?

Як бачите, це не той результат, який нам потрібен. Ми хочемо протилежного — виключити еліпс і показати решту. Як це зробити? Виявляється, 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.

Виріз в елементах: CSS чи SVG?

Переваги

  • Кросбраузерність, однак потрібен вендорний префікс для всіх браузерів, окрім 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 чи SVG?

У реальному проєкті нам може знадобитися кілька варіантів цього компонента. Найчастіше для зміни розміру.

Виріз в елементах: CSS чи SVG?

Тому краще застосовувати змінні 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.

  1. Визначення розміру аватара. Це буде використовуватися для властивостей ширини й висоти.

  2. Використання розміру для пошуку розташування cx і cy.

  3. Щоб визначити від'ємне поле між двома аватарами, нам потрібно розділити розмір на 5.5 та помножити на -1.

Погляньмо, як обчислюється значення cx.

cx: calc(var(--size) / 4 * -1);

Значення cx та cy починаються від центру кола. Це означає, що застосувавши половину значення ми повністю сховаємо зображення. Розглянемо фігуру:

Для кращого унаочнення біле коло забарвлюємо фіолетовим кольором (це те, що ми хочемо показати), а чорне коло робимо прозорим, окресливши лінією (це ми хочемо сховати).

Виріз в елементах: CSS чи SVG?

Коли значення чорного кола cx дорівнює 0, половину зображення буде сховано. Ми можемо прилаштуватися й замість цього використати від'ємне значення. Це значення можна визначити, спираючись на розмір ділянки вирізу.

Щоб отримати від'ємне поле між аватарами, його треба зробити майже таким самим, як і обчислене значення cx, але трохи більшим. Аби все було правильно, потрібно поекспериментувати.

Переваги

  • Відмінна підтримка браузерами. Усе однаково працює у всіх основних браузерах.

  • Використовуючи змінні CSS, усім можна керувати за допомогою однієї змінної.

Недоліки

  • Потрібні навички роботи з SVG

Хедер вебсайту

У нас є хедер із логотипом посередині. Для нього хочемо тут вирізати потрібну ділянку.

Виріз в елементах: CSS чи SVG?

Перше, про що ви можете подумати, — потрібно додати білу рамку, чи не так? Це може частково розв'язати проблему. Та під час прокручування біла рамка на логотипі виглядатиме трохи дивно.

Виріз в елементах: 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 не працює, оскільки він має бути пропорційним розміру маски та логотипу.

Виріз в елементах: CSS чи SVG?

Доведеться використати динамічне значення для властивості логотипу top. Що ми маємо:

  • Висота хедера 100px.

  • Центр ділянки вирізу розташований на 70% осі y.

  • Радіус кола ми можемо отримати від змінної --radius.

Розглянемо пояснення.

Виріз в елементах: CSS чи SVG?

Щоб обчислити динамічний інтервал, скористаймося такою формулою.

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>
Виріз в елементах: CSS чи SVG?

Майте на увазі, що для SVG потрібне абсолютне позиціювання, щоб охопити всю область хедера.

.site-header {
    position: relative;
}

.site-header svg {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
}

Висновок

Неймовірно, що веброзробники мають так багато способів для одного завдання. Часом різноманіття може й ускладнити роботу, але це нормально.

Сподіваємося, цей матеріал вам сподобався і був корисним. Дякуємо, що ви нами!

Довідково

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація