CSS-селектор :has() та міжрядкові інтервали у довгих текстах.

Якщо ви працювали з сайтами, що містять багато довгих текстів, особливо з сайтами на CMS, де користувачі працюють у WYSIWYG-редакторі, то ви напевно писали CSS для управління міжрядковими інтервалами між різними елементами типографіки – заголовками, параграфами, списками і т.д.

Писати такі стилі напрочуд непросто. Саме тому з’явилися інструменти, подібні до плагіну Tailwind Typography і Prose від Stack Overflow, хоча вони працюють далеко не тільки з міжрядковими інтервалами.

На момент написання цієї статті Firefox підтримує :has()при включенні флага layout.css.has-selector.enabled до about:config.

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

Здавалося б, достатньо вказати, що кожен елемент — ph2ulі т.д. — має бути певна величина верхнього та/або нижнього зовнішнього відступу (margin), так? На жаль, не все так просто. Припустимо, що потрібно виконати такі вимоги:

  • Зверху першого та знизу останнього елемента у блоці з довгим текстом не повинно бути ніякого додаткового простору, щоб нетипографські елементи навколо тексту розташовувалися правильно.
  • Між секціями у довгому тексті має бути великий інтервал. “Секція” тут – це заголовок і весь текст, який до нього відноситься: великий інтервал потрібен перед заголовком, але не тоді , коли перед заголовком знаходиться ще один!

:has()

Після h3 знаходиться параграф, а після h2 ще один h3.

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

Подивимося, де це знадобиться. Пара скріншотів з інтервалами з іншої статті:

:has()

h2 прямо над h3.

:has()

h3 відразу після параграфа та міжрядковий інтервал.

Традиційне рішення

Як правило, це завдання вирішують, обертаючи довгий вміст div(або в семантичний тег при необхідності). Зазвичай я називаю обгортку .rich-text– це спадщина старих версій Wagtail CMS, які додавали цей клас автоматично при рендерингу WYSIWYG-контенту. Tailwind Typography використовує класс .prose(і ще деякі класи-модифікатори).

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

Традиційне рішення має логічний вигляд… але в чому проблема? Мені здається, що проблем тут є кілька.

Жорстка структура

Необхідність додавати клас-обертку начебто .rich-text впровадження особливої ​​структури в HTML-код. У цьому випадку цього не потрібно. Легко забути додати такий клас у потрібне місце, особливо при використанні суміші CMS та контенту, в якому зміни не передбачені.

Структура HTML втрачає гнучкість, якщо слід прибрати верхній і нижній зовнішні відступи у першого і останнього елементів, оскільки вони мають бути прямими нащадками елемента-обгортки, наприклад, важливий селектор .rich-text > *:first-child>адже з його допомогою ми випадково вибираємо перший елемент у кожному ulабо ol.

Використання різних сторін зовнішніх відступів

До появи :has() був способу вибрати елемент залежно від наступного елемента. Отже, традиційний підхід до створення міжрядкових інтервалів друкарських елементів – використовувати і margin-top, і margin-bottom:

  1. Спочатку з margin-bottomвизначаємо розмір міжрядкового інтервалу за умовчанням.
  2. Потім, за допомогою суміжного селектора (наприклад, h2 + h3), створюємо міжрядковий інтервал для секцій через margin-top– наприклад, великий інтервал перед кожним заголовком.
  3. Перезаписуємо ці великі інтервали у разі, коли за заголовком відразу слідує ще один.

Не знаю, як вам, а мені завжди здавалося, що при визначенні міжрядкових інтервалів краще використовувати тільки одну сторону відступу, як правило, margin-bottom(припускаючи, що в даному випадку CSS-свойство gapне застосовується. Чи варто так працювати, я залишаю на ваш розсуд. Але для установки міжрядкових інтервалів довгого контенту я віддаю перевагу margin-bottom.

Схлопування зовнішніх відступів

Через схлопування зовнішніх відступів одночасне застосування margin-bottom і margin-topсаме собою не проблема. З двох розташованих один над одним зовнішніх відступів видно буде лише більший, а чи не сума значень відступів. Але мені не подобаються зовнішні відступи, що схлопуються. Їх також варто взяти до уваги.

Схлопування відступів може заплутати розробників-початківців, які не знають про цю особливість CSS. Міжрядкові інтервали зміняться (наприклад, припинять схлопуватися), якщо надати обгортці, скажімо, якість flex з flex-direction: column. Цього не станеться, якщо використовувати лише одну сторону відступу під час завдання вертикальних зовнішніх відступів.

Я більш-менш розумію, як працює схлопування зовнішніх відступів, і усвідомлюю, що так зроблено спеціально. Іноді воно допомагає, але іноді ні. Мені здається, що схлопування – дивна штука, і, як правило, я намагаюся уникати його.

Рішення через:has()

Нагадаю про те, чого я хочу досягти:

  • Позбутися класу-обгортки.
  • Використовувати лише одну сторону зовнішнього відступу .
  • Уникнути сплескування зовнішніх відступів (ви можете вважати це поліпшенням чи ні).
  • Позбутися установки стилів та їх негайного перезаписування.

Ось як я спробував вирішити ці проблеми через :has().

Нотатки та пояснення для вирішення за допомогою:has()

  • Завжди перевіряйте, чи браузер підтримує:has() . На момент написання цієї статті Firefox підтримує :has()лише після встановлення експериментального прапора .
  • Моє рішення не враховує всіх існуючих елементів типографіки . Наприклад, у моїй демоверсії немає підтримки <blockquote>. Але перелік селекторів просто розширити.
  • Моє рішення не працює з нетипографськими елементами , які можуть бути присутніми у певних блоках з довгим текстом, наприклад <img>. Справа в тому, що я працюю з сайтами, де WYSIWYG-редактори максимально обмежені основними текстовими елементами, такими як заголовки, параграфи та списки. Інші елементи (цитати, картинки, таблиці тощо) перебувають у окремому компонентному блоці CMS. Ці блоки під час рендерингу на сторінці розділені інтервалами. Але, повторюю, список селекторів легко доповнюється.
  • Я ввімкнув h1 тільки для повноти картини . Зазвичай я не дозволяю використання h1у WYSIWYG-редакторі: так заголовок сторінки виявляється десь у макеті, а змінювати його потрібно за допомогою CMS-редактора сторінки.
  • Я не передбачив ситуацію, коли за одним заголовком відразу слідує інший той самий рівень ( h2 + h2) . Це означало б, що перший заголовок не має контенту, що схоже на неправильне застосування заголовків (і виправте мене, якщо я помиляюся, але це може порушувати принципи посібника з доступності веб-вмісту WCAG 1.3.1 Info and Relationships) . Я також не передбачив пропуску рівнів заголовків.
  • Я жодним чином не зменшую переваги раніше згаданих мною підходів . Коли я створюю сайт за допомогою Tailwind, то, звичайно, використовую чудовий плагін Typography.
  • Я не дизайнер . Я поставив міжрядкові інтервали на око. Вам слід використовувати більш відповідні значення.

Висновок

У вас на руках ультрасучасне рішення дуже занудного завдання! Цей новий підхід я не назвав би «простим». Як я вже говорив на початку статті, але це завдання складніше, ніж може здатися на перший погляд. Але, крім наявності кількох нескладних селекторів, мені здається, що такий підхід набагато логічніше, а менш жорстка структура HTML дуже приваблива.

Переклад статті “Solved With :has(): Vertical Spacing in Long-Form Text