Магия CSS на практике: советы по вёрстке от гика

—

от автора

Хабр, привет! Я частенько пишу про работу CSS, его неизвестные возможности и влияние на доступность. Кажется, этих направлений мало для меня. Теперь я хочу показать техники вёрстки, используемые мной постоянно.

Цель — поделиться опытом с вами. Я использую не только трюки известных экспертов, есть лично мои придумки. Но, пожалуйста, относитесь к этому контенту, как просто альтернативному мнению. Мои техники не являются единственными правильными решениями.

Сегодня я расскажу:

  • как избавиться от соседнего родственного комбинатора + при реализации нестандартных чекбоксов и радиокнопок;
  • про свойство inset, сокращающее код на целых три строки;
  • мой сниппет для расширения интерактивной области у кнопок и ссылок;
  • стиль написания медиа-запросов, позволяющий сократить количество правил;
  • альтернативный способ центрирования элемента без свойства transform.

▍ Новый способ создания нестандартных чекбоксов и радиокнопок

Всю мою карьеру единственный способ создания нестандартных радиокнопок заключался в использовании селектора на основе соседнего родственного комбинатора +. Я уверен, что вы его знаете. А современные возможности CSS позволяют сделать по-другому.

В моей демонстрации будет использоваться следующая разметка:

<body>   <div class="cutom-radio-button">     <input id="rb-1" class="cutom-radio-button__input sr-only" type="radio" name="radio" checked>     <label for="rb-1" class="cutom-radio-button__label">Вариант №1</label>   </div>   <div class="cutom-radio-button">     <input id="rb-2" class="cutom-radio-button__input sr-only" type="radio" name="radio">     <label for="rb-2" class="cutom-radio-button__label">Вариант №2</label>   </div> </body> 

Сама стилизация радиокнопок происходит так.

.sr-only {   width: 1px;   height: 1px;   clip-path: inset(50%);   overflow: hidden;   position: absolute;   white-space: nowrap; }  .cutom-radio-button {   --custom-radio-button-size: 1rem;   --custom-radio-button-gap: 1rem;   --custom-radio-button-dot-size: 0.5rem;      display: inline-flex;   align-items: center;   position: relative;   isolation: isolate; }  .cutom-radio-button::before {   content: "";   box-sizing: border-box;   width: var(--custom-radio-button-size);   height: var(--custom-radio-button-size);    border: 1px solid hsl(0, 0%, 14%);   border-radius: 100%;   position: absolute;   z-index: -1; }  .cutom-radio-button__label {   display: grid;   padding-left: calc(var(--custom-radio-button-dot-size) + var(--custom-radio-button-gap)); }  .cutom-radio-button__label::before, .cutom-radio-button__label::after {   content: "";   width: var(--custom-radio-button-dot-size);   height: var(--custom-radio-button-dot-size);   border-radius: 100%;   opacity: 0;    position: absolute;   align-self: center;   left: var(--custom-radio-button-dot-size);     transform: translateX(-50%);   scale: 0;   transform-origin: left center; }  .cutom-radio-button__label::before {   background-color: hsl(0, 0%, 14%);   transition: 0.3s; }  .cutom-radio-button__label::after {   background-color: hsl(250, 100%, 44%);   transition: 0.6s; } 

Осталось сделать последний шаг. Дописать код для состояний, когда на радиокнопке сфокусировались и отметили её. Как я говорил ранее, он будет основан на соседнем родственном комбинаторе +.

.cutom-radio-button__input:checked + .cutom-radio-button__label::before {   opacity: 1;   scale: 1; }  .cutom-radio-button__input:focus + .cutom-radio-button__label::after {   scale: 3.6;   opacity: 0.2; } 

В чём проблема? Если в разметке элемент с классом .cutom-radio-button__input случайно перестанет быть перед элементом .cutom-radio-button__label, стилизация полетит к чёрту. В этом заключается проблема использования соседнего родственного комбинатора +.

В новой технике этого недостатка нет, потому что она основана на псевдоклассах :has() и :focus-within.

.cutom-radio-button:has(:checked) .cutom-radio-button__label::before {   opacity: 1;   scale: 1; }  .cutom-radio-button:focus-within .cutom-radio-button__label::after {   scale: 3.6;   opacity: 0.2; } 

Оба псевдокласса срабатывают на родителе, когда их потомок получает какое-либо состояние. В случае псевдокласса :has() у потомка применяются стили, когда срабатывает состояние «отмечено». А когда на потомке сфокусировались, то в дело включается псевдокласс :focus-within.

По сути можно обойтись одним псевдоклассом :has(), заменив псевдокласс :focus-within.

.cutom-radio-button:has(:checked) .cutom-radio-button__label::before {   opacity: 1;   scale: 1; }  .cutom-radio-button:has(:focus) .cutom-radio-button__label::after {   scale: 3.6;   opacity: 0.2; } 

Мне этот способ не нравится. Я любитель правил. Раз дали псевдокласс :focus-within, то надо его использовать. Но я не могу запрещать вам. Поэтому, если что, имейте в виду.

▍ Свойство inset — новый способ растянуть элемент

Иногда элементы со свойством position и значением absolute используются для растягивания элемента по всему доступному пространству. Для этого чаще всего используется следующий код:

.parent {   position: relative; }  .parent::before {   content: "";   width: 100%;   height: 100%;      position: absolute;   top: 0;   left: 0; } 

Это устаревший сниппет. Я так думаю, потому что его можно сократить. Поможет в этой задаче свойство inset. Оно задаёт координаты для элемента сразу с четырёх сторон. Другими словами, оно устанавливает значение для свойств top, right, bottom и left.

Возвращаясь к моему примеру, нужно сделать пару действий. Первое — удалить свойства width и height. Поскольку для элемента .parent применено свойство position со значением absolute, его размеры могут быть рассчитаны в зависимости от заданных координат отступа.

В нашем примере для него будем использовать значение 0, которое установлено для свойства inset.

.parent {   position: relative; }  .parent::before {   content: "";   position: absolute;   inset: 0; } 

Где полезен этот сниппет? У меня как раз есть полезный пример, который позволит вам значительно улучшить интерфейс вашего продукта. Его рассмотрим далее.

▍ Сниппет расширения кликабельной области

В интерфейсах очень важна область, по которой пользователь может нажать или кликнуть. Чем она больше, тем лучше. Так нужно, потому что у людей есть различные особенности здоровья. Лично у меня периодически трясутся и ломит пальцы. Было у меня футбольное юношество в воротах.

К сожалению, далеко не всегда дизайн с достаточными размерами интерактивных элементов примет менеджмент. По этой причине мы встречаем маленькие кнопки и ссылки. Что делать? Я придумал лайфхак.

В его основе лежит псевдоэлемент, который растягивается за пределы своего интерактивного родителя, увеличивая его область. Делаю это я с помощью отрицательного значения для свойства inset.

.ha-clickable-area {   position: var(--ha-clickable-area-position, relative);   isolation: isolate; }  .ha-clickable-area::before {   content: "";   position: absolute;   inset: calc(-1 * var(--ha-clickable-area-expandable-ratio));   z-index: -1; } 

Вся фишка заключается в коэффициенте расширения области --ha-clickable-area-expandable-ratio. Он отвечает за то, на сколько пикселей псевдоэлемент выйдет за пределы родителя, т. е. на сколько пикселей кликабельная область расширится.

Как применять сниппет, я покажу на моём недавнем проекте, в котором я реализовывал требования стандарта Web Content Accessibility Guidelines (WCAG). В нём был элемент размером 32 на 32 пикселей. Я установил его с помощью свойств width и height в единицах измерения rem.

.page__back-to-home {   width: 2rem; /* 32px */   height: 2rem; /* 32px */ } 

Для минимального соответствия требованиям стандарта WCAG всё хорошо. Но этого мне было не достаточно. По моему мнению, чем больше интерактивная область, тем лучше. По этой причине я захотел сделать её 56 на 56 пикселей с помощью сниппета.

В моём проекте для его вычисления мне нужно было из 56 пикселей вычесть 32 пикселя. Полученное значение (24 пикселей) поделить на 2. В итоге с каждой стороны родителя псевдоэлемент выйдет за его границы на 12 пикселей.

.page__back-to-home {   --ha-clickable-area-expandable-ratio: 0.75rem; /* 12px */    width: 2rem;   height: 2rem;    /* на скриншоте значение свойств задаётся через --uia-control-icon-size */ } 

Скриншот интерфейса. Есть ссылка назад в виде круга со стрелкой назад. У нее размер 24 на 24 пикселя. Кликабельная область 56 на 56 пикселя

Так лучше. Главное контролировать, чтобы области не накладывались друг на друга. Пользуйтесь на здоровье!

▍ Пользовательские свойства позволяют не раздувать код в медиа-запросах

При вёрстке любого проекта приходится писать кучу правил внутри медиа-запросов. От таких размеров кода легко потеряться в нём. В качестве примера проблемы, о которой идёт речь, я напишу стили для двух элементов с классами .intro__heading и .intro__description.

.intro__heading {    font-size: 2rem;  }  .intro__description {    font-size: 0.75rem;  }  @media (min-width: 641px) {   .intro__heading {      font-size: 3rem;    }    .intro__description {      font-size: 1.25rem;    } }  @media (min-width: 1025px) {   .intro__heading {      font-size: 3.5rem;    }    .intro__description {      font-size: 1.5rem;    } } 

Мы вынуждены создавать правила на каждое изменение свойства. По этой причине для изменения значения свойства font-size в каждом медиа-запросе по два правила с селектором .intro__heading и .intro__description. Этот стиль нельзя было изменить до появления пользовательских свойств. С их приходом у нас появился новый способ.

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

Применим данную технику на примере.

.intro__heading {    font-size: var(--heading-font-size, 2rem);  }  .intro__description {    font-size: var(--hint-font-size, 0.75rem);  }  @media (min-width: 641px) {   .intro {      --heading-font-size: 3rem;      --hint-font-size: 1.25rem;   } }  @media (min-width: 1025px) {   .intro {      --heading-font-size: 3.5rem;      --hint-font-size: 1.5rem;   }   } 

Код сократился на два правила. Кажется, мелочь. Но в реальных проектах поддержка кода в таком стиле значительно проще, чем при старом стиле. Не надо скроллить кучу кода, чтобы найти нужный фрагмент. Попробуйте сами. Я уверен, что вам понравится.

▍ Свойство transform для центрирования элемента — это устаревший способ

Свойство transform долгое время было наиболее адекватным способом отцентрировать элемент, у которого установлено свойство position и значение absolute. Вся суть сводилась к двум шагам.

Первый — сдвинуть элемент на 50% от краёв элемента с помощью свойств top и left. Второй — с помощью значения translate(-50%, -50%) вернуть элемент обратно на половину своей ширины и высоты.

.parent {   width: 20rem;   height: 20rem;   position: relative; }  .parent::before {   width: 2rem;   height: 2rem;    position: absolute;   top: 50%;   left: 50%;   transform: translate(-50%, -50%); } 

Сегодня у нас есть другой способ. Всё проще. Используем свойство place-items.

.parent {   width: 20rem;   height: 20rem;    display: grid;   place-items: center; }  .parent::before {   width: 2rem;   height: 2rem;   position: absolute; } 

Что тут происходит. Я убрал свойства top, left и transform. На их место добавил свойство place-items. Оно располагает элемент по центру. А ещё свойство position со значением relative больше тоже не нужно.

Вот так кода стало меньше. А его понятность возросла, поскольку нет скрытых штук.

▍ Заключение

Давайте подведём итог. В этой статье мы рассмотрели:

  • способ вёрстки нестандартных чекбоксов и радиокнопок с помощью псевдоклассов :has() и :focus-within;
  • свойство inset как альтернативу свойствам top, right, bottom и left;
  • мой сниппет для расширения интерактивной области;
  • использование пользовательских свойств для сокращения кода в медиа-запросах;
  • свойство place-items, позволяющее без магии центрировать элемент со свойством position и значением absolute.

Спасибо за чтение!

P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn’t magic. Присоединяйтесь. Ссылка в профиле.

Telegram-канал со скидками, розыгрышами призов и новостями IT ?


ссылка на оригинал статьи https://habr.com/ru/articles/822461/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *