Современный CSS: nesting, :has(), @layer, subgrid, color-mix и View Transitions
↑ 1.3 CSS и SASS · ← Предыдущая
Зачем это на собесе
Всё больше из того, ради чего раньше подключали SASS и JavaScript, умеет сам CSS: вложенность, родительские селекторы, управление каскадом, смешивание цветов, анимации переходов между страницами. Вопрос «что нового в CSS?» проверяет, следите ли вы за платформой и можете ли заменить тяжёлый инструмент нативной возможностью.
Подтемы
- Вложенность (nesting)
- Селектор
:has(),:is(),:where() - Каскадные слои
@layer - Subgrid
-
color-mix()и современные цвета - Единицы
dvh,svh,lvhиtext-wrap - View Transitions
- Поддержка браузерами и fallback
Объяснение
Вложенность (nesting)
Раньше вложенные правила требовали SASS. Теперь их понимает браузер:
.card {
padding: 1rem;
& .title { font-weight: 700; } /* .card .title */
&:hover { color: red; } /* .card:hover */
@media (width >= 600px) { /* медиа-запрос внутри правила */
padding: 2rem;
}
}Символ & обозначает родительский селектор. Вложенные @media и @container остаются рядом с компонентом. Разница с SASS: nesting работает во время выполнения и не склеивает имена (нет &-suffix).
Селекторы :has(), :is(), :where()
:has()выбирает элемент по содержимому (родительский селектор):
.form:has(input:invalid) { outline: 2px solid red; } /* форма с невалидным полем */
.card:has(img) { display: grid; } /* карточка с картинкой */
label:has(+ input:required)::after { content: " *"; } /* метка перед обязательным полем */:is(a, b)сокращает список селекторов, специфичность равна наибольшей в списке.:where(a, b)то же, но специфичность нулевая: удобно для базовых стилей, которые легко переопределить (каскад и специфичность).
Каскадные слои @layer
Слои задают порядок приоритета групп стилей независимо от специфичности. Слой, объявленный позже, побеждает слой, объявленный раньше, даже если селектор в первом слое «сильнее».
@layer reset, base, components, utilities; /* порядок задан один раз */
@layer base { a { color: blue; } }
@layer components { .btn { color: white; } }
@layer utilities { .text-red { color: red; } }Стили вне слоёв побеждают стили в слоях. Слои решают проблему «войны специфичности» при подключении сторонних библиотек: положите их в нижний слой, а свои стили выше.
flowchart LR R["reset"] --> B["base"] --> C["components"] --> U["utilities"] --> N["стили вне слоёв<br/>(самый высокий приоритет)"]
Subgrid
Вложенная сетка может использовать линии родительской, а не строить свои. Так выравниваются элементы внутри независимых карточек:
.parent { display: grid; grid-template-columns: repeat(3, 1fr); }
.child { grid-column: span 3; display: grid; grid-template-columns: subgrid; }Типичный случай: в карточках заголовок, текст и кнопка выстраиваются по строкам одинаково, даже если тексты разной длины (Grid).
Цвета: color-mix() и современные пространства
:root { --brand: #3366ff; }
.btn:hover { background: color-mix(in srgb, var(--brand) 85%, black); } /* темнее на 15 % */
.badge { background: color-mix(in oklch, var(--brand) 20%, white); } /* светлый тон */Функция смешивает цвета прямо в CSS, поэтому палитру можно строить из одной переменной без SASS-функций. Пространство oklch даёт более равномерные по восприятию оттенки. Связано с CSS-переменными.
Единицы и типографика
| Возможность | Что делает |
|---|---|
dvh, svh, lvh | высота области просмотра с учётом динамической панели браузера на телефонах: dvh меняется, svh минимальная, lvh максимальная |
text-wrap: balance | выравнивает длину строк в коротком тексте (заголовки) |
text-wrap: pretty | избегает «висячих» одиночных слов в конце абзаца |
aspect-ratio | задаёт пропорции блока |
@container | адаптация по размеру контейнера, а не окна (адаптивность) |
100vh на мобильных часто перекрывается панелью адреса; 100dvh решает проблему.
View Transitions
API для плавных анимированных переходов между состояниями DOM и страницами. Браузер делает снимок «до» и «после» и анимирует разницу.
function navigate(update) {
if (!document.startViewTransition) return update(); // fallback: без анимации
document.startViewTransition(update); // update() меняет DOM
}::view-transition-old(root) { animation: fade-out .2s; }
::view-transition-new(root) { animation: fade-in .2s; }
.hero-image { view-transition-name: hero; } /* элемент «перелетает» между состояниями */Подробнее об анимациях: анимации и transitions. В SPA на Vue переходы запускают в роутере вокруг смены маршрута.
Поддержка браузерами
Большинство возможностей доступны в современных версиях Chrome, Edge, Safari и Firefox, но не все одновременно и не все давно. Проверяйте на caniuse.com и в таблицах MDN, а для новых свойств используйте прогрессивное улучшение:
.card { background: #eee; } /* базовое значение для всех */
@supports (background: color-mix(in srgb, red, blue)) {
.card { background: color-mix(in srgb, var(--brand) 10%, white); }
}Примеры
Компонент без SASS
@layer components {
.card {
--pad: 1rem;
display: grid;
padding: var(--pad);
background: color-mix(in srgb, var(--brand, #36f) 8%, white);
& h3 { text-wrap: balance; }
&:has(img) { grid-template-columns: 120px 1fr; }
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / .12); }
@container (width >= 480px) { --pad: 1.5rem; }
}
}Вложенные слои и сторонние стили
@layer vendor, app;
@import url("ui-kit.css") layer(vendor); /* библиотека в нижнем слое */
@layer app { .btn { border-radius: 8px; } } /* ваши стили побеждают без !important */Нюансы и подводные камни
- Не все возможности есть везде. Проверяйте поддержку, добавляйте
@supportsи запасные значения. :has()может быть дорогим на огромных деревьях: избегайте очень общих селекторов вроде:has(*)на корне.- Слои меняют привычную специфичность. Стили без слоя всегда побеждают слоёвые;
!importantв слоях работает в обратном порядке (важные правила ранних слоёв выигрывают). - Nesting не заменяет SASS полностью. Нет миксинов, функций и циклов; но переменные, вложенность и цвета нативно покрываются.
- View Transitions и доступность. Уважайте
prefers-reduced-motionи отключайте анимацию для тех, кто её не хочет. color-mixи контраст. Проверяйте контраст смешанных цветов (доступность).
Вопросы с ответами
Что делает селектор
:has()?Выбирает элемент по его содержимому или соседям, то есть это «родительский селектор». Например,
.form:has(input:invalid)стилизует форму с невалидным полем.
Чем
:is()отличается от:where()?Оба принимают список селекторов. Специфичность
:is()равна наибольшей в списке, у:where()всегда нулевая, поэтому такие правила легко переопределить.
Что такое
@layerи зачем он нужен?Каскадные слои задают порядок приоритета групп стилей независимо от специфичности: поздний слой побеждает ранний. Позволяют подключать чужие стили в нижний слой и не воевать со специфичностью.
Чем нативный CSS nesting отличается от SASS?
Он работает в браузере на лету, использует
&и допускает вложенные@mediaи@container. У SASS есть ещё миксины, функции, циклы и склейка имён.
Что такое subgrid?
Вложенная сетка, которая использует линии родительской сетки. Нужна, чтобы выравнивать внутренности независимых карточек по общим строкам или колонкам.
Почему
100vhпроблемен на телефонах и что использовать?Динамическая панель браузера меняет видимую высоту, и
100vhможет выходить за экран.100dvhучитывает текущую высоту,100svhминимальную.
Как работает View Transitions API?
document.startViewTransition(update)делает снимок до и после изменения DOM и анимирует разницу через псевдоэлементы::view-transition-*. Элементы с одинаковымview-transition-nameанимируются как один объект.
Связанные темы
- Каскад: Каскад, специфичность, наследование
- Grid: Grid
- Адаптивность и container queries: Адаптивность
- CSS-переменные: CSS-переменные
- SASS: SASS и SCSS