В заметке речь идет о некоторых полезных возможностях, предоставляемых современным CSS
и немного о полезных "фичах", которые ждут нас в ближайшие 2 года.
"Полезный" означает, что автор либо часто использует фичу в своих проектах, либо с нетерпением ждет такой возможности.
Начнем с существующих возможностей, поддерживаемых большинством браузеров.
Функция-псевдокласс :not() позволяет стилизовать элементы, которые не совпадают ни с одним селектором из указанного списка. Список может состоять как из одного, так и из нескольких селекторов, разделенных запятыми. Селекторы могут быть как простыми, так и сложными.
Синтаксис
:not(selector1, selector2, ...selectorN) {}
Пример
Предположим, что у нас имеется такой инпут:
<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;
}
Псевдокласс :empty позволяет стилизовать элементы, которые не имеют потомков. К потомкам относятся элементы, текст и (sic!) пробелы.
Синтаксис
:empty {}
Пример
Предположим, что у нас имеется такой список ссылок:
<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()
всегда равняется 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
), либо имеют потомков, находящихся в фокусе.
Синтаксис
:focus-within {}
Пример
Предположим, что у нас имеется такой инпут с подписью и иконкой:
<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 позволяет реализовывать прокручиваемые слайдеры (scrollable sliders). Основными свойствами данной модели являются:
Синтаксис (основные значения)
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: auto | smooth;
Пример
Рассматриваемое свойство позволяет легко реализовать прокрутку к определенной позиции на странице без помощи 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 позволяет менять цвет таких элементов, как <input type="checkbox" />
, <input type="radio" />
, <input type="range" />
и <progress />
.
Синтаксис
accent-color: auto | <color>;
Пример
<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(selector1, selector2, ...selectorN) {}
Примеры
/* стилизуем ссылки, которые содержат изображения в качестве непосредственных потомков */
a:has(> img) {}
/* стилизуем `img`, которые находятся в `figure`, которые имеют `figcaption` */
figure:has(figcaption) img {}
Функция 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 <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 — это своего рода стилизуемый вариант элемента 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().
Данная функция является альтернативой позиционированию элементов с помощью position: relative
и position: absolute
. Она позволяет привязывать одни элементы к другим независимо от их места в иерархии DOM
.
anchor()
также будет предоставлять возможность определять границы позиционирования и другие интересные фичи. Перейдите по ссылке, если хотите получить более подробную информацию о рассматриваемом интерфейсе (да, это целый интерфейс, а не одна функция).
Подробнее о новых и ожидаемых возможностях CSS
можно почитать в этой замечательной статье.
на главную сниппетов