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

NOTES 10.08.22 10.08.22 142
Бесплатные курсына главную сниппетов

В заметке речь идет о некоторых полезных возможностях, предоставляемых современным 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: 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 можно почитать в этой замечательной статье.

 

на главную сниппетов
Курсы