Skip to main content

Заметка о полезных возможностях современного CSS

· 10 min read

Привет, друзья!

В данной заметке я расскажу вам о некоторых полезных возможностях, предоставляемых современным CSS. Также мы немного поговорим о полезных "фичах", которые ждут нас в ближайшие 2 года.

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

Существующие возможности

Начнем с существующих возможностей, поддерживаемых большинством браузеров.

:not()

Функция-псевдокласс :not() позволяет стилизовать элементы, которые не совпадают ни с одним селектором из указанного списка. Список может состоять как из одного, так и из нескольких селекторов, разделенных запятыми. Селекторы могут быть как простыми, так и сложными.

Синтаксис

:not(selector1, selector2, ...selectorN) {}

Поддержка - 93.04%.

Пример

Предположим, что у нас имеется такой инпут:

<input type="text" placeholder="Enter some text..." required />

Мы хотим, чтобы в невалидном состоянии границы этого поля были красного цвета. "Невалидность" поля можно стилизовать с помощью псевдокласса :invalid. При этом, стилизация невалидности не должна влиять на стилизацию наведения и фокусировки. Это можно реализовать следующим образом:

input:invalid:not(:hover, :focus) {
border-color: red;
}

/* отключаем подсветку */
:invalid {
box-shadow: none;
}

:-moz-submit-invalid {
box-shadow: none;
}

:-moz-ui-invalid {
box-shadow: none;
}

Лирическое отступление: стилизация placeholder

Раньше заменители текста приходилось стилизовать так:

input.placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input:-moz-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input::-moz-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input:-ms-input-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input::-webkit-input-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}

Сейчас это можно делать с помощью псевдокласса ::placeholder (спасибо @dynamicult):

input::placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}

Поддержка - 96.26%.

:empty

Псевдокласс :empty позволяет стилизовать элементы, которые не имеют потомков. К потомкам относятся элементы, текст и (sic!) пробелы.

Синтаксис

:empty {}

Поддержка - 97.65%.

Пример

Предположим, что у нас имеется такой список ссылок:

<ul>
<li><a href="#">Link 1</a></li>
<li>
<a href="#" target="_blank" rel="noopener noreferrer">Link 2</a>
</li>
<li></li>
</ul>

С такими стилями:

ul {
display: inline-flex;
flex-direction: column;
/* отступы между элементами списка */
gap: 0.5rem;
list-style: none;
}

Допустим, что список формируется динамически и последний элемент по какой-то причине оказался пустым, например, в объекте не было url для ссылки. Тогда после второго элемента получим лишний отступ:


Скрываем пустые элементы списка с помощью :empty:

li:empty {
display: none;
}

Лишнего отступа больше нет:


Лирическое отступление: невидимый контент

display: none; полностью скрывает элемент. Сделать элемент невидимым, но доступным для устройств чтения с экрана, можно следующим образом:

.sr-only {
background: none;
border: none;
color: none;
cursor: none;
height: 0;
margin: 0;
opacity: 0;
outline: none;
overflow: hidden;
padding: 0;
pointer-events: none;
position: absolute;
user-select: none;
visibility: hidden;
white-space: nowrap;
width: 0;
z-index: -1;
}

:is() и :where()

Функции-псевдоклассы :is() и :where() позволяют стилизовать элементы, совпадающие с любым селектором из указанного списка. Список может состоять как из одного, так и из нескольких селекторов, разделенных запятыми. Селекторы могут быть как простыми, так и сложными.

Разница между :is() и :where() заключается в том, что :is() принимает специфичность самого конкретного селектора из списка, а специфичность :where() всегда равняется 0.

Синтаксис

:is(selector1, selector2, ...selectorN) {}
:where(selector1, selector2, ...selectorN) {}

Поддержка :is() - 94.9%. Поддержка :where() - 91.61%.

Пример

Предположим, что мы хотим стилизовать ссылки, находящиеся только в шапке или подвале страницы:

:is(header, footer) a:hover {
color: green;
}

Для понимания того, насколько :is() и :where() могут уменьшить количество шаблонного кода, рекомендую взглянуть на этот пример.

:focus-within

Псевдокласс :focus-within позволяет стилизовать элементы, которые либо сами находятся в фокусе (в этом случае :focus-within аналогичен псевдоклассу :focus), либо имеют потомков, находящихся в фокусе.

Синтаксис

:focus-within {}

Поддержка - 95.32%.

Пример

Предположим, что у нас имеется такой инпут с подписью и иконкой:

<div class="form-field">
<label>
<span>Some text:</span>
<input type="text" placeholder="Enter some text..." required />
</label>
<svg viewBox="0 0 60 60">
<path
d="M48.014,42.889l-9.553-4.776C37.56,37.662,37,36.756,37,35.748v-3.381c0.229-0.28,0.47-0.599,0.719-0.951
c1.239-1.75,2.232-3.698,2.954-5.799C42.084,24.97,43,23.575,43,22v-4c0-0.963-0.36-1.896-1-2.625v-5.319
c0.056-0.55,0.276-3.824-2.092-6.525C37.854,1.188,34.521,0,30,0s-7.854,1.188-9.908,3.53C17.724,6.231,17.944,9.506,18,10.056
v5.319c-0.64,0.729-1,1.662-1,2.625v4c0,1.217,0.553,2.352,1.497,3.109c0.916,3.627,2.833,6.36,3.503,7.237v3.309
c0,0.968-0.528,1.856-1.377,2.32l-8.921,4.866C8.801,44.424,7,47.458,7,50.762V54c0,4.746,15.045,6,23,6s23-1.254,23-6v-3.043
C53,47.519,51.089,44.427,48.014,42.889z"
fill="currentColor"
/>
</svg>
</div>

Обратите внимание на значение атрибута fill элемента path.

И такими стилями:

.form-field {
color: darkslategray;
position: relative;
width: max-content;
}

.form-field label {
align-items: center;
display: flex;
}

.form-field input {
border: 2px solid darkslategray;
margin-left: 0.5rem;
outline: none;
padding: 0.5rem;
/* отступ для иконки - 20px + 5px + 5px */
padding-right: 30px;
}

.form-field svg {
height: 20px;
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
width: 20px;
}

Допустим, что при наведении и фокусировке мы хотим менять цвет подписи, границы поля и заливки иконки.

С инпутом все просто:

.form-field input:hover {
border-color: deepskyblue;
}

.form-field input:focus {
border-color: mediumseagreen;
}

С наведением тоже:

.form-field:hover {
color: deepskyblue;
}

Но следующее работать не будет, поскольку элемент div не является фокусируемым (focusable):

.form-field:focus {
color: mediumseagreen;
}

Здесь на помощь приходит :focus-within:

.form-field:focus-within {
color: mediumseagreen;
}

Scroll Snap

Scroll Snap позволяет реализовывать прокручиваемые слайдеры (scrollable sliders). Основными свойствами данной модели являются:

  • scroll-snap-type - определяет строгость привязки контейнера к контрольным точкам;
  • scroll-snap-align - определяет контрольные точки для прокрутки.

Синтаксис (основные значения)

scroll-snap-type: x | y | both [mandatory | proximity];
scroll-snap-align: start | center | end;

Поддержка scroll-snap-type - 94.98%. Поддержка scroll-snap-align - 94.75%.

Пример

Прокручиваемый слайдер с тремя изображениями котиков:

<div class="slider">
<img
src="https://images.unsplash.com/photo-1529257414772-1960b7bea4eb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80"
alt=""
/>
<img
src="https://images.unsplash.com/photo-1598188306155-25e400eb5078?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1548&q=80"
alt=""
/>
<img
src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1768&q=80"
alt=""
/>
</div>
* {
margin: 0;
}

body {
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
overflow: hidden;
}

.slider {
display: flex;
overflow-x: scroll;
position: relative;
/* ! */
scroll-snap-type: x mandatory;
/* стилизация скроллбара для Firefox */
scrollbar-color: hotpink whitesmoke;
scrollbar-width: thin;
width: 480px;
}

.slider > img {
object-fit: cover;
/* ! */
scroll-snap-align: start;
width: 100%;
}

/* стилизация скроллбара для Webkit */
.slider::-webkit-scrollbar {
height: 6px;
}

.slider::-webkit-scrollbar-track {
background-color: whitesmoke;
}

.slider::-webkit-scrollbar-thumb {
background-color: hotpink;
}

scroll-behavior

Свойство scroll-behavior, как следует из названия, определяет поведение прокрутки.

Синтаксис (основные значения)

scroll-behavior: auto | smooth;

Поддержка - 91.01%.

Пример

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

<!-- якорь -->
<a id="top"></a>

<div class="page-content">
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
</div>

<a href="#top" class="top-link">
<img src="https://cdn-icons-png.flaticon.com/512/892/892692.png" alt="" />
</a>
* {
margin: 0;
}

html,
body {
/* ! */
scroll-behavior: smooth;
}

.page-content {
display: flex;
flex-direction: column;
}

p {
display: grid;
font-size: 4rem;
height: 100vh;
place-content: center;
}

.top-link {
bottom: 1rem;
left: 50%;
position: fixed;
transform: translateX(-50%);
}

.top-link img {
width: 40px;
}

accent-color

Свойство accent-color позволяет менять цвет таких элементов, как <input type="checkbox" />, <input type="radio" />, <input type="range" /> и <progress />.

Синтаксис

accent-color: auto | <color>;

Поддержка - 87.04%.

Пример

<input type="checkbox" checked />
<input type="radio" checked />
<input type="range" min="0" max="10" value="5" />
<progress max="100" value="50"></progress>
body {
/* ! */
accent-color: deepskyblue;
align-items: start;
display: flex;
flex-direction: column;
gap: 1rem;
}

Теперь в простых случаях можно обходиться без создания кастомных чекбоксов, радио-кнопок и т.д.

Будущие возможности

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

Вложенность

Вложенность - одна из самых ожидаемых возможностей, давно реализованных в таких CSS-препроцессорах, как Sass и Less.

Синтаксис похож на Sass, за исключением того, что использование символа & в качестве родительского селектора является обязательным.

Пример

<article class="article">
<h2 class="title">Title</h2>
<div class="info">
<p class="author">Author</p>
<time datetime="2022-08-01" class="date">01.08.2022</time>
</div>
<p class="summary">Summary</p>
</article>
/* стилизуем информацию об авторе статьи */
.article {
& .info {
/* .article .info .author */
& .author {
/* ... */
}
}
}
/* в Sass можно опускать `&` */
.article {
.info {
.author {
/* ... */
}
}
}

Еще одно отличие от Sass состоит в том, что, судя по всему, в CSS нельзя будет "склеивать" селекторы. Например, если у нас имеется такая разметка:

<article class="article">
<h2 class="article__title">Title</h2>
</article>

В Sass стилизовать заголовок можно следующим образом:

.article {
&__title {
/* ... */
}
}

Если вас интересует более подробная информация о вложенности, рекомендую взглянуть на эту статью.

:has()

Функция-псевдокласс :has() предназначена для стилизации элементов, совпадающих хотя бы с одним селектором из списка, переданного в качестве аргумента. Прелесть данной функции состоит в том, что она позволяет стилизовать родительские элементы.

Синтаксис

:has(selector1, selector2, ...selectorN) {}

Примеры

/* стилизуем ссылки, которые содержат изображения в качестве непосредственных потомков */
a:has(> img) {}

/* стилизуем `img`, которые находятся в `figure`, которые имеют `figcaption` */
figure:has(figcaption) img {}

color-mix()

Функция color-mix принимает 2 цвета и возвращает результат их смешивания в указанном цветовом пространстве и на указанный процент. Дефолтным цветовым пространством является LCH.

Синтаксис:

color-mix(in <colorspace>?, <color1> <percentage>?, <color2> <percentage>?)

Пример

<div class="box brand"></div>
<div class="box darken"></div>
<div class="box lighter"></div>
:root {
--brand: deepskyblue;
/* ! */
--darken: color-mix(var(--brand) 25%, #333);
--lighter: color-mix(var(--brand) 25%, #eee);
}

.box {
width: 100px;
height: 100px;
}

.box.brand {
background-color: var(--brand);
}

.box.darken {
background-color: var(--darken);
}

.box.lighter {
background-color: var(--lighter);
}

@scope

Директива @scope предназначена для определения области видимости стилей.

Синтаксис

@scope <selector> {
<stylesheet>
}

Пример

Вернемся к примеру из раздела про вложенность:

.article {
& .info {
& .author {
/* ... */
}
}
}

Если после этих стилей будет определено что-то вроде:

.section {
& .info {
& .author {
/* ... */
}
}
}

То специфичность селекторов .article .info .author и .section .info .author будет одинаковой и стили, определенные в .section, перезапишут стили, определенные в .article. @scope позволяет решить данную проблему за счет инкапсуляции стилей в собственной области видимости:

@scope .article {
/* стили применяются только к элементам, находящимся в элементе с классом `article` */
& .info {
& .author {
/* ... */
}
}
}

@scope .section {
/* стили применяются только к элементам, находящимся в элементе с классом `section` */
& .info {
& .author {
/* ... */
}
}
}

Справедливости ради следует отметить, что на сегодняшний день существует еще одно средство для решения проблемы правильного порядка определения стилей - директива @layer, позволяющая определять так называемые каскадные слои стилей (cascade layers). Однако, на мой взгляд, эта директива больше подходит для использования в CSS-фреймворках, чем для локального применения. Например, @layer активно используется в таком фреймворке, как TailwindCSS.

selectmenu

Элемент selectmenu - это своего рода стилизуемый вариант элемента select.

Синтаксис

selectmenu::part(<part>) {}

Пример

<selectmenu class="select-menu">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
.select-menu::part(button) {
background-color: #f00;
border-radius: 5px;
color: white;
padding: 5px;
}

.select-menu::part(listbox) {
border-radius: 5px;
border: 1px solid red;
margin-top: 5px;
padding: 10px;
}

anchor()

Последняя возможность, о которой я хочу рассказать, это функция anchor().

Данная функция является альтернативой позиционированию элементов с помощью position: relative и position: absolute. Она позволяет привязывать одни элементы к другим независимо от их места в иерархии DOM.

anchor() также будет предоставлять возможность определять границы позиционирования и другие интересные фичи. Перейдите по ссылке, если хотите получить более подробную информацию о рассматриваемом интерфейсе (да, это целый интерфейс, а не одна функция).

Подробнее о новых и ожидаемых возможностях CSS можно почитать в этой замечательной статье.

Благодарю за внимание и happy coding!