Головна / Статті

Безпечна робота з сучасним CSS: якорі, смуги сітки, область застосування та light-dark()

Дізнайтеся, як позиціонування анкорів, модалі, мозаїчна структура з сіткою, переходи між вікнами, функції light-dark() та @scope замінюють бібліотеки JavaScript, а також як використовувати кожен з цих інструментів разом із альтернативами.

Видавець: REACTAPP.TOP7 хв читання1605 слів

Джерела: CSS Can Already Do This. Developers Are Still Scared to Ship It.

Багато пакетів фронт-енду все ще містять бібліотеки, єдиною функцією яких є визначення місця розташування підказки, такі як Floating UI чи її попередник Popper, а також інші бібліотеки, які використовуються для формування сітки у стилі Pinterest. Тепер браузери можуть виконувати обидві ці функції нативно, а також кілька інших завдань, які раніше вимагали JavaScript, проте лише деякі команди припинили використовувати ці залежності. У цьому посібнику розглядаються п’ять функцій CSS, які можуть замінити поширений код бібліотек, пояснюється, як працює кожна з них, та показується, як їх впровадити, не ризикуючи через нерівномірну підтримку браузерами.

Одночасно потрібні та уникнені

Опитування розробників добре відображають цю напругу. У результатах дослідження State of CSS за 2026 рік позиціонування анкорів було обране найбільш очікуваною новою функцією серед розробників, витіснивши :has(), який тепер настільки поширений, що вже не здається чимось новим. У тому ж опитуванні також запитували, які функції люди навмисно уникають через брак довіри до підтримки в браузерах, і позиціонування анкорів також опинилося на чолі цього списку. Для подолання цієї прогалини переважно необхідно впроваджувати функції за допомогою функцій-запитів. Цифри з опитувань та статус підтримки швидко змінюються, тому сприймайте їх як моментальний знімок та перевіряйте актуальні дані про сумісність перед тим, як покладатися на будь-яку функцію.

Позиціонування анкорів замість бібліотеки підказок

Підхід з використанням JavaScript додає плаваючий елемент до свого тригеру шляхом вимірювання обох елементів та перерахунку їхнього положення під час кожного прокручування та зміни розмірів. Позиціонування за допомогою анкора переміщує цей механізм у сферу CSS.

У наведеному нижче прикладі тригер визначає ім’я, а меню позиціонується відносно нього. Останнє оголошення замінює функцію виявлення зіткнень у бібліотеці:

.trigger {
  anchor-name: --menu-trigger;
}
.menu {
  position: fixed;
  position-anchor: --menu-trigger;
  top: anchor(bottom);
  left: anchor(center);
  position-try-fallbacks: flip-block, flip-inline;
}

Кожен елемент виконує певну функцію:

  • anchor-name надає елементу ідентифікатор у вигляді дефісів, за яким інші елементи можуть позиціонуватися.
  • position-anchor вказує елементу, який знаходиться у певній позиції, який анкор слід використовувати для позиціонування.
  • anchor() отримує координати, такі як нижній край або горизонтальний центр анкора, безпосередньо від цього елемента.
  • position-try-fallbacks перелічує альтернативи, які браузер пробує використати, коли елемент може вийти за межі видимої області; flip-block та flip-inline відображають його вертикально або горизонтально.
  • Поєднання з атрибутом popover

    Атрибут popover, який також є частиною стандарту, але здебільшого ігнорується, регулює поведінку випадаючого меню: кнопка з popovertarget активує елемент із відповідним id, для цього не потрібен скрипт:

    <button popovertarget="menu">Options</button>
    <div id="menu" popover anchor-name-target="--menu-trigger">...</div>
    

    Будьте обережні з цим фрагментом коду: anchor-name-target не є стандартним атрибутом HTML, тому не копіюйте його таким, як є. Під’єднайте popover до його точки кріплення за допомогою CSS за допомогою anchor-name та position-anchor, як показано вище. Деякі браузери також розглядають кнопку, яка запускає popover через popovertarget, як неявну точку кріплення; перевірте поточну документацію, перш ніж покладатися на це.

    Те, що popover надає за замовчуванням, — це відображення на верхньому рівні, тож меню з’являється над усім без конфліктів з параметром z-index у всьому додатку, легке закриття при кліку користувачем поза ним або натисканні клавіші Escape, а також належне керування фокусом під час його відкриття та закриття. Він не утримує фокус, як це робить модальне dialog, тому для справді модального контенту використовуйте dialog замість нього.

    Щоб дізнатися більше про API браузерів, які роблять пакети npm зайвими, перегляньте наш огляд нативних API браузерів, які замінюють популярні пакети.

    Нативний мозаїчний формат із смугами ґріду

    З моменту появи CSS Grid у браузерах у 2017 році для створення мозаїчних макетів все ще була необхідна JavaScript, тому бібліотека Masonry та подібні пакети були встановлені мільйони разів. Це змінюється. Наведене нижче правило є повним форматом макету:

    .gallery {
      display: grid-lanes;
      gap: 10px;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    }
    

    Кожна картка розміщується у тому стовпці, який наразі має найбільше простору, тож елементи щільно упаковуються без нерівних проміжків. Не існує ResizeObserver, а також жодних механізмів перерозподілу стовпців у скрипті після завершення завантаження зображення. Синтаксис цього елемента був предметом багаторічних дискусій між виробниками браузерів, причому Apple та Google підтримували різні підходи, перш ніж display: grid-lanes став компромісним рішенням.

    Згідно з інформацією про підтримку на момент написання цього тексту, Safari першим впровадив цей елемент, тоді як Chrome та Firefox залишили його у режимі пробної версії. Це пояснює, чому так мало сайтів його використовують, та чому зараз варто про це писати. Розмістіть grid-lanes у блоку @supports (display: grid-lanes), а як заміну використовуйте звичайну сітку.

    Переходи між вікнами з особливою обережністю

    Тут обережність є доцільною. Підтримка API View Transition існує, але є нерівномірною, через що вона, разом із позиціонуванням анкорів, належить до найбільш уникаючихся функцій, і така вагання є цілком розумною.

    Основний механізм простий: огорніть оновлення DOM функцією document.startViewTransition(), і браузер зробить знімки старого та нового станів, а потім плавно перейде від одного до іншого без використання бібліотек анімацій та без необхідності вручну вести облік станів до та після змін. Наведені нижче псевдоелементи керують цим стандартним переходом, тут скорочуючи його до 0,3 секунди:

    ::view-transition-old(root),
    ::view-transition-new(root) {
      animation-duration: 0.3s;
    }
    

    Сьогодні його можна використовувати як засіб прогресивного покращення. Браузер, у якого відсутній відповідний API, просто переходить у новий стан, тож нічого не ламається. Це робить його безпечним для некритичних ситуацій, таких як заміна сторінок чи переупорядкування списку. У скриптах необхідно перевірити, чи існує функція document.startViewTransition, перш ніж її викликати, а якщо ні — застосувати оновлення безпосередньо.

    Одна палітра для світлого та темного режимів за допомогою light-dark()

    У типовому темному режимі використовуються дві повні системи кольорів: одна — у медіа-запиті prefers-color-scheme: dark, а інша — поза ним. Функція light-dark() об’єднує кожну пару кольорів у одне оголошення.

    У наведеному нижче фрагменті спочатку встановлюється документ у обидва режими за допомогою параметра color-scheme, який необхідний для роботи light-dark(), а потім кожному властивості присвоюються значення для світлого та темного режимів поруч одне з іншим:

    :root {
      color-scheme: light dark;
    }
    
    body {
      background: light-dark(#ffffff, #1a1a1a);
      color: light-dark(#111111, #eeeeee);
    }
    

    Як тільки встановлюється color-scheme, кожен виклик light-dark() знаходить правильне значення відповідно до налаштувань системи користувача, і більше не потрібно дублювати запити до медіа-запитів у стилевому файлі. Щодо інших способів реалізації темування, дивіться нашу порівняльну статтю про техніки темного режиму.

    Обмеження стилів за допомогою @scope

    Однією з основних причин використання CSS-in-JS чи суворої номенклатури BEM є проблема витоку специфічності: правило .card, призначене для одного компонента, також форматує непов’язаний елемент .card, розташований глибше в маркапі іншого компонента. @scope природним чином обмежує стилі лише певною піддеревом.

    У наведеному нижче правилі стилі параграфу застосовуються всередині .card, але припиняють дію на будь-який елемент .card-nested:

    @scope (.card) to (.card-nested) {
      p {
        color: var(--card-text);
      }
    }
    

    Перший селектор визначає корень області видимості. Клозула to встановлює нижню межу, залишаючи простір, де вкладений компонент може керувати власними стилями замість того, щоб успадковувати стилі батьківського компонента. Для цього не потрібні кроки збірки, генеровані імена класів чи JavaScript у час виконання. Це не замінить усе, що робить CSS-in-JS, наприклад обчислення стилів на основі параметрів компонента, але воно усуває одну з основних причин, чому команди його використовують.

    Браузер — це екосистема

    Усі п’ять функцій вже доступні принаймні в одному справжньому браузері сьогодні, і жодна з них не залежить від React, плагіну для бандлера чи нової версії фреймворку. Розбіжність між тим, що може робити CSS, та тим, що використовують команди, переважно зумовлена звичкою: протягом десятиліття вирішення проблем інтерфейсу означало встановлення пакета.

    Виберіть одне місце у вашому проекті, де використовується обхідний захід на JavaScript для вирішення однієї з цих проблем, замініть його на код під @supports та подивіться, що перестане працювати. Зазвичай це буде менше, ніж ви очікуєте.

    Поширені запитання

    Чи готове позиціонування елементів до використання у продакшені?

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

    Чи все ще потрібна бібліотека для мозаїчного форматування?

    Для нового проекту, ймовірно, ні, за умови, що display: grid-lanes розташований у блоку @supports зі звичайною сіткою як альтернативою. Для існуючого дизайну у продакшені з суворою матрицею підтримки браузерів краще ще деякий час залишити цю бібліотеку.

    Яку функцію слід впровадити спочатку?

    light-dark(). Це зміна одним рядком для кожного значення кольору; на момент написання він мав найширшу підтримку серед п’яти варіантів, а також дозволяє усунути блок дубльованого CSS для темного режиму без ризиків для макету.

    Основні висновки

    • Розглядайте кожну функцію як прогресивне покращення: користуйтеся нативною поведінкою, якщо вона підтримується, або існуючим рішенням в іншому місці.
    • Позиціонування через anchor разом із popover задовольняє більшість потреб у підказках та випадаючих меню, але прив’язуйте anchor у CSS та використовуйте dialog, коли потрібен справжній модальний вікно.
    • grid-lanes та переходи між виглядами є найменш усталеними; обмежуйте їх використання за допомогою @supports або перевірки під час виконання.
    • light-dark() та @scope негайно усувають код та несуть найменший ризик.