Введение
CSS уже давно перестал быть просто языком для стилизации — сегодня он превращается в полноценный инструмент логики и динамики, способный решать задачи, которые раньше были доступны только через JavaScript. Особенно в последние год-два, когда CSS получил множество мощных обновлений: новые функции, улучшенные анимации, возможности для оптимизации производительности и даже математические вычисления.
Эта статья — путеводитель по некоторым самым важным и практичным фичам CSS, появившимся совсем недавно, но уже поддерживаемым большинством современных браузеров. Мы рассмотрим каждую из них подробно, с реальными примерами, пояснениями, контекстом и советами по применению в реальных проектах.
Вам больше не придётся:
- страдать с центровкой элементов,
- писать тонны JavaScript для валидации,
- изобретать велосипеды для адаптации под dark mode,
- бояться производительности при загрузке сложных страниц.
Новые возможности CSS помогут вам писать более чистый, лаконичный, быстрый и предсказуемый код, а также разгрузить JavaScript-логику.
1. Центрирование элементов одной строкой
Центрировать элемент по горизонтали и вертикали — одна из самых частых задач в вёрстке. Но многие разработчики годами не могут запомнить, как это делается. Раньше были десятки разных подходов: margin: auto, абсолютное позиционирование с transform: translate, использование flexbox, grid и т.д. Часто решение зависело от контекста, и не всегда работало одинаково во всех случаях.
Вот несколько популярных, но неудобных решений:
.parent {
display: flex;
justify-content: center;
align-items: center;
}
или
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
И вот наконец, появилось более простое и декларативное решение — одна строка!
С марта 2024 года большинство современных браузеров поддерживают свойство align-content: center не только в grid-контейнерах, но и в обычных блочных контекстах с display: block. Это означает, что можно центрировать дочерние элементы вертикально и горизонтально одной строкой CSS.
Пример 1: Центрирование блока с текстом по вертикали и горизонтали
<style>
.container {
height: 100vh;
text-align: center;
align-content: center;
}
</style>
<div class="container">
<p>Я по центру! </p>
</div>
Как работает этот код?
- height: 100vh задаёт высоту экрана.
- text-align: center центрирует текст по горизонтали.
- align-content: center центрирует весь контент по вертикали.
Работает только при наличии высоты у контейнера.
Пример 2: Центрирование нескольких карточек в контейнере
<style>
.grid {
display: grid;
height: 100vh;
align-content: center;
justify-content: center;
gap: 20px;
}
.card {
width: 200px;
height: 100px;
background: #f3f3f3;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<div class="grid">
<div class="card">Карточка 1</div>
<div class="card">Карточка 2</div>
</div>
Где использовать?
- Центрирование модальных окон, кнопок, логотипов.
- Главная страница с единственным call-to-action.
- Отображение пустого состояния по центру экрана.
Особенность
Все это работает только в сочетании с правильным display-контекстом. Убедитесь, что вы используете display: grid, или у вас есть блочный контейнер с заданной высотой. align-content работает на уровне контейнера, а не самого элемента.
2. Новые возможности CSS для анимации скрытых элементов
До недавнего времени анимировать скрытые элементы было настоящим испытанием. Самой большой проблемой было то, что когда элемент имеет display: none, он вообще не существует в потоке документа: у него нет размеров, его нельзя "поймать" для анимации, и CSS-транзишены просто игнорируют его. Единственным решением был JavaScript — например, изменять класс, чтобы сначала сделать display: block, а потом через setTimeout запускать анимацию, и это было неудобно и не всегда стабильно.
Теперь у нас есть два мощных инструмента:
- @starting-style — позволяет задать стартовые стили, из которых будет происходить анимация;
- transition-behavior: allow-discrete — позволяет анимировать свойства, которые раньше не поддавались анимации, например display, visibility, content-visibility.
Они работают вместе:
@starting-style говорит, откуда начинать, а transition-behavior: allow-discrete — позволяет совершить "рывок" между несовместимыми состояниями.
Пример 1: Диалоговое окно с анимацией появления
<style>
.dialog {
transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
transition-behavior: allow-discrete;
display: none;
opacity: 0;
transform: scale(0.9);
}
.dialog[open] {
display: block;
opacity: 1;
transform: scale(1);
}
@starting-style {
.dialog[open] {
opacity: 0;
transform: scale(0.9);
}
}
</style>
<dialog class="dialog" id="myDialog">Привет! Я всплываю с анимацией 🪄</dialog>
<button onclick="document.getElementById('myDialog').showModal()">Открыть</button>
Объяснение
Сначала display: none скрывает элемент. Когда dialog.showModal() вызывается — он получает display: block, и CSS применяет @starting-style для анимации появления.
Без @starting-style, анимация начиналась бы с текущих стилей, а так она начинается с opacity: 0 и scale(0.9) и плавно переходит к opacity: 1 и scale(1).
transition-behavior: allow-discrete разрешает анимировать такие вещи, как display, которые раньше были мгновенными.
Пример 2: Появление меню при событии "скролл"
<style>
nav {
position: fixed;
top: 0;
width: 100%;
background: white;
transform: translateY(-100%);
transition: transform 0.3s ease;
transition-behavior: allow-discrete;
}
nav.visible {
transform: translateY(0);
}
@starting-style {
nav.visible {
transform: translateY(-100%);
}
}
</style>
<nav id="menu">Я меню!</nav>
<script>
window.addEventListener('scroll', () => {
const menu = document.getElementById('menu');
if (window.scrollY > 200) {
menu.classList.add('visible');
} else {
menu.classList.remove('visible');
}
});
</script>
Когда использовать?
- Всплывающие окна, тултипы, модальные окна.
- Меню, появляющиеся по взаимодействию.
- Аккордеоны, FAQ-блоки, раскрывающиеся списки.
3. Управление стилями тёмной и светлой темы — проще, чем когда-либо
Тёмная тема стала стандартом. Пользователи ожидают, что сайты будут корректно переключаться между светлой и тёмной версией в зависимости от системных настроек. Раньше реализация этого процесса была довольно громоздкой, особенно если требовалось задать альтернативные цвета для множества элементов.
Что было раньше?
Чтобы изменить стили в зависимости от темы, приходилось использовать медиа-запросы:
body {
background: white;
color: black;
}
@media (prefers-color-scheme: dark) {
body {
background: black;
color: white;
}
}
Это громоздко, особенно если вы хотите использовать кастомные переменные, фоновые изображения и стили для множества компонентов.
Теперь в CSS появился новый light-dark() хелпер — это сокращение, которое позволяет задать сразу два значения: для светлой и тёмной темы. Он автоматически применит нужное значение, основываясь на системных настройках пользователя.
Пример 1: Автоматическое переключение фона
body {
background-color: light-dark(#ffffff, #1a1a1a);
color: light-dark(#000000, #ffffff);
}
Как работает код?
light-dark(#ffffff, #1a1a1a) означает: использовать #ffffff в светлой теме, и #1a1a1a — в тёмной.
Это заменяет необходимость писать отдельный @media-запрос.
Можно использовать как напрямую в свойствах, так и внутри calc() и переменных.
Пример 2: Использование с кастомными свойствами
:root {
--bg-color: light-dark(#fefefe, #111);
--text-color: light-dark(#222, #fefefe);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
Теперь вы можете централизованно управлять стилями темы через переменные.
Где использовать?
- Фоновый цвет страниц и секций.
- Цвета текста и ссылок.
- Цвета иконок, SVG, теней и бордеров.
- Внутри @property и calc(), если нужно.
Особенность
Пока light-dark() поддерживается не всеми браузерами (активно внедряется с 2024 года), но уже стабильно работает в Chrome, Edge, Safari и частично в Firefox.
4. Улучшенная стилизация валидных и невалидных полей формы
Формы — один из самых важных интерфейсных компонентов. Корректная валидация и визуальные подсказки пользователю (красная рамка, цвет фона, сообщения об ошибке) критичны для UX. Раньше приходилось использовать либо JavaScript, либо CSS-селекторы вроде :valid и :invalid.
Но! Эти селекторы начинали работать до того, как пользователь начал взаимодействовать с формой, что приводило к странным ошибкам: форма вся в красном уже с первого рендера.
Что было раньше?
input:invalid {
border: 1px solid red;
}
input:valid {
border: 1px solid green;
}
Это выглядело хорошо только после взаимодействия, но применялось сразу — ещё до ввода. Пользователь видит "ошибку", даже ничего не сделав. Это может запутать и раздражать.
Чтобы избежать этого, разработчики прибегали к JavaScript:
input.addEventListener('blur', () => {
if (!input.checkValidity()) {
input.classList.add('invalid');
}
});
Теперь CSS поддерживает псевдоселекторы :user-valid и :user-invalid. Они срабатывают только после того, как пользователь начал взаимодействовать с полем — то есть, ушёл с него (blur) или попытался отправить форму. Это позволяет избежать преждевременного показа ошибок.
Пример 1: Красная рамка только после фокуса
<style>
input:user-invalid {
border: 2px solid red;
background-color: #ffeaea;
}
input:user-valid {
border: 2px solid green;
background-color: #eaffea;
}
</style>
<form>
<input type="email" required placeholder="Введите email" />
</form>
Как работает код?
input:user-invalid — применяется, если поле невалидно, но только после взаимодействия.
input:user-valid — аналогично, только для валидного значения.
Пользователь больше не увидит красную рамку, пока не начнёт работать с полем.
Пример 2: Стилизация при попытке отправки формы
<form>
<input type="text" required placeholder="Ваше имя" />
<input type="email" required placeholder="Email" />
<button type="submit">Отправить</button>
</form>
<style>
input:user-invalid {
border: 2px dashed crimson;
box-shadow: 0 0 5px crimson;
}
input:user-valid {
border: 2px solid seagreen;
}
</style>
Теперь при нажатии на кнопку отправки формы стили сработают, и пользователь увидит, какие поля он не заполнил, но до этого всё будет чисто.
Где использовать?
- Во всех формах, где важна UX-валидация без лишнего шума.
- Формы обратной связи, регистрации, логина.
- Поля в checkout-страницах и верификациях.
- Любые формы с клиентской валидацией (required, pattern, type=email, и т.д.).
Совместимость
Селекторы :user-valid и :user-invalid уже работают в последних версиях Chrome, Edge, Safari. В Firefox пока частичная поддержка.
5. Управление размером запасного шрифта с помощью font-size-adjust
Когда вы используете на сайте красивый кастомный шрифт (например, загружаемый с Google Fonts), вы часто указываете запасной шрифт — на случай, если основной не загрузится. Проблема в том, что размеры у fallback-шрифта могут сильно отличаться по визуальному восприятию: один будет выглядеть крупнее, другой — мельче, даже если размер установлен один и тот же (16px например).
Это нарушает макет, вызывает сдвиги (Cumulative Layout Shift, или CLS) и ухудшает пользовательский опыт. И тут на сцену выходит свойство font-size-adjust.
Что было раньше?
Раньше всё, что можно было сделать — подобрать запасной шрифт, который на глаз примерно совпадает по высоте с основным. Часто это не срабатывало:
font-family: 'CustomFont', Arial, sans-serif;
Если CustomFont не загружен, Arial может выглядеть больше/меньше, и всё "поедет".
Что нового?
Свойство font-size-adjust позволяет визуально подогнать запасной шрифт под основной, сохранив единый масштаб текста. Оно работает на основе "аспектного соотношения строчных букв" (так называемая x-height — высота строчной буквы x).
Пример 1: Установка корректировки
body {
font-family: 'Merriweather', Georgia, serif;
font-size-adjust: 0.5;
}
Как работает код?
font-size-adjust: 0.5 — это отношение высоты строчной x к полной высоте шрифта.
Если основной шрифт не загрузился, то браузер увеличит/уменьшит размер fallback-шрифта так, чтобы его x-height приблизилась к 0.5 от общей высоты, как у Merriweather.
Это делает внешний вид текста более стабильным.
Пример 2: Адаптация интерфейса при потере основного шрифта
.custom-title {
font-family: 'Lora', Times New Roman, serif;
font-size: 2rem;
font-size-adjust: 0.52;
}
Если Lora не загрузится, то Times New Roman будет отрендерен в подходящем размере, чтобы не "вывалиться" из блока, не испортить отступы и не изменить визуальную иерархию заголовков.
Где использовать?
- На заголовках и подзаголовках — чтобы не нарушалась иерархия.
- В навигации — особенно если у вас кастомный шрифт.
- В текстовых блоках, если CLS критичен (например, на SPA/SSR сайтах).
- В дизайне, где важна точность — блочные кнопки, фиксированные размеры карточек.
Как подобрать значение?
Посетите MDN: font-size-adjust — там есть значения для популярных шрифтов.
Можно использовать инструмент вроде Modern Font Stacks — он подскажет сочетания, в которых fallback максимально приближен.
Или экспериментально: загружаете шрифт, отключаете его (через DevTools), смотрите, как выглядит fallback, и регулируете font-size-adjust до нужного визуального совпадения.
Поддержка
Поддерживается большинством современных браузеров, включая Chrome, Edge, Firefox и Safari. Лучше всего работает с шрифтами, у которых точно определён x-height.
6. Ускорение загрузки страниц с помощью content-visibility
Если на вашей странице много DOM-элементов, особенно вложенных и с тяжёлыми стилями, браузер тратит много ресурсов на их отрисовку, даже если пользователь пока не видит их (например, они находятся ниже "фолда", за пределами видимой области). Это снижает:
- Скорость первоначального рендеринга (First Contentful Paint)
- Общую производительность прокрутки
- Оценку Google PageSpeed
И раньше, чтобы решить эту проблему, нужно было писать JavaScript (например, с IntersectionObserver) или динамически добавлять/удалять элементы.
Что было раньше?
Раньше ленивую отрисовку приходилось реализовывать вручную:
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
});
observer.observe(document.querySelector('.lazy-block'));
Это долго, требует JS, и легко сломать.
Что нового?
Свойство content-visibility позволяет в одной CSS-строке указать, что элемент не должен рендериться, пока он не попадёт в область видимости. Это нативная, CSS-реализация "ленивого" рендеринга.
Основное значение: content-visibility: auto
Это значение означает, что:
- Элемент не будет отрисован до тех пор, пока пользователь не доскроллит до него.
- Но при этом браузер знает, что он есть — и займёт под него место на странице.
- Это улучшает скорость загрузки, особенно на длинных страницах.
Пример 1: Ленивый рендер секций
<section class="lazy-section">
<h2>Раздел 1</h2>
<p>Длинный контент, таблицы, списки, изображения и т.п.</p>
</section>
<style>
.lazy-section {
content-visibility: auto;
contain-intrinsic-size: 500px; /* Высота-заглушка */
margin-bottom: 2rem;
}
</style>
Как работает код?
content-visibility: auto — элемент не будет отрисован, пока не попадёт в область видимости.
contain-intrinsic-size: 500px — резервирует 500px вертикального пространства, чтобы страница не "прыгала", когда элемент появится.
Как только элемент войдёт в видимую область, он отрисуется полностью.
Где использовать?
- Длинные страницы (лендинги, маркетплейсы, каталоги).
- Много повторяющихся блоков (карточки товаров, статьи, посты).
- Списки, таблицы, массивные графики.
- SPA/SSR-приложения, где контент подгружается пачками.
- Вьюхи внутри вкладок, которые не отображаются сразу.
Пример 2: Ускорение ленты статей
<article class="article-preview" style="content-visibility: auto; contain-intrinsic-size: 400px;">
<h3>Заголовок статьи</h3>
<p>Превью текста, картинка, теги и т.д.</p>
</article>
Когда пользователь доскроллит до этого article, он появится без использования JavaScript. При этом пространство уже зарезервировано.
Подводные камни
Прокрутка может выглядеть "рывками", если не указать contain-intrinsic-size.
Не стоит использовать content-visibility на интерактивных элементах, которые важны для навигации (например, меню, вкладки), иначе они будут отрисовываться с задержкой.
Может не подойти для анимаций, завязанных на загрузку элемента.
Давайте поясним последнее утверждение.
Что делает content-visibility: auto?
Это свойство говорит браузеру:
«Не трать ресурсы на отрисовку и layout этого элемента, пока он не попадёт в viewport».
Это отлично:
- ускоряет первую отрисовку;
- снижает нагрузку на процессор;
- повышает производительность на сложных страницах.
Но...
Почему это может плохо работать с анимациями?
1. Элемент не загружается сразу
Если вы задаете анимацию появления, например через @keyframes, opacity, transform, transition, то:
- браузер не видит элемент в DOM до тех пор, пока он не попадёт в область видимости;
- анимация не начнётся, пока элемент не отрендерится;
- к тому моменту он уже «просто появился» — без плавного перехода.
Пример:
.card {
content-visibility: auto;
opacity: 0;
transform: translateY(50px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.card.in-view {
opacity: 1;
transform: translateY(0);
}
Допустим, вы желаете, чтобы .card плавно появилась при входе в зону видимости. Но с content-visibility: auto она физически не существует в рендеринге, пока не попадает в viewport.
А значит:
Анимация не отработает;
Браузер сразу применит конечные стили, и вы увидите резкое появление элемента без перехода.
2. Нет триггера для «начала»
Многие анимации или transition'ы начинаются по событию:
- наведение (hover),
- скролл (scroll into view),
- загрузка страницы.
Если content-visibility отложил загрузку элемента, то событие могло уже произойти, и элемент не сможет анимироваться правильно, потому что он буквально «опоздал на вечеринку».
Как это исправить?
- Не использовать content-visibility для элементов, которые анимируются при загрузке или скролле.
- Использовать альтернативу — Intersection Observer (в JavaScript) для контроля видимости, и запускать анимацию вручную.
- Или — задавать placeholder размер через contain-intrinsic-size, и снимать content-visibility после появления:
.card {
content-visibility: auto;
contain-intrinsic-size: 300px;
transition: opacity 0.5s ease, transform 0.5s ease;
opacity: 0;
transform: translateY(30px);
}
.card.reveal {
content-visibility: visible;
opacity: 1;
transform: translateY(0);
}
Здесь вы явно меняете content-visibility, чтобы включить отрисовку вручную, и браузер сможет применить анимацию.
Вывод
content-visibility — мощный инструмент для оптимизации, но он не волшебная палочка. Он не знает, что вы хотели запустить анимацию.
Поэтому для интерактивных, динамичных элементов — особенно с переходами или входными анимациями — лучше комбинировать content-visibility с дополнительной логикой или от него отказаться.
Рассмотрим два наглядных примера:
❌ первый — с content-visibility: auto без доработки, где анимация не сработает
✅ второй — с обходным путём, где всё будет работать как надо.
Пример, где анимация не работает с content-visibility: auto
<div class="card">Я карточка</div>
.card {
content-visibility: auto;
contain-intrinsic-size: 300px;
opacity: 0;
transform: translateY(50px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.card.in-view {
opacity: 1;
transform: translateY(0);
}
// Простая проверка, когда карточка входит в зону видимости
const card = document.querySelector('.card');
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
card.classList.add('in-view');
}
});
observer.observe(card);
Что произойдёт:
Когда .card попадает в viewport, она только тогда отрисовывается. Но браузер уже применяет итоговые стили (opacity: 1, transform: translateY(0)), и анимация пропускается.
Пример с обходом: управление content-visibility вручную
<div class="card">Я карточка</div>
.card {
content-visibility: auto;
contain-intrinsic-size: 300px;
opacity: 0;
transform: translateY(50px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.card.visible {
content-visibility: visible; /* отключаем ленивую отрисовку */
opacity: 1;
transform: translateY(0);
}
const card = document.querySelector('.card');
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
card.classList.add('visible');
}
});
observer.observe(card);
Как это работает:
content-visibility: auto — ленивый рендер, при попадании в зону видимости мы отключаем его через visible, после чего CSS-анимации применяются нормально.
Поддержка
Поддерживается в Chrome, Edge, Opera и Safari, начиная с 2024 года — на всех основных движках (Blink, WebKit). Firefox пока работает с ограничениями, но развивается.
7. Типизация и анимация пользовательских свойств в CSS
Пользовательские переменные CSS (--var) очень полезны: они позволяют делать темы, динамически изменять стили, повторно использовать значения. Но есть огромный минус: они никак не валидируются и не поддерживают анимации.
Например, если вы хотите анимировать --angle от 0deg до 90deg — ничего не произойдёт, потому что браузер не знает, что это вообще угол.
Раньше это можно было решить только через JavaScript, например обновляя переменную по таймеру.
Теперь есть правило @property, которое позволяет:
- Объявить переменную, указать её тип (<length>, <angle>, <number> и т.д.);
- Установить значение по умолчанию;
- Сделать переменную анимируемой
Пример 1: Анимация поворота с использованием пользовательского свойства
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.box {
width: 100px;
height: 100px;
background: coral;
transition: --angle 1s linear;
transform: rotate(var(--angle));
}
.box:hover {
--angle: 180deg;
}
Объяснение
@property описывает, что --angle — это значение угла (<angle>), не наследуется, и по умолчанию — 0deg.
Теперь переменную --angle можно анимировать.
На hover значение изменяется с 0deg до 180deg, а CSS плавно анимирует transform: rotate(var(--angle)).
Пример 2: Темная и светлая тема с плавной сменой цвета
@property --bg-color {
syntax: '<color>';
inherits: false;
initial-value: white;
}
body {
transition: --bg-color 0.5s ease;
background-color: var(--bg-color);
}
body.light {
--bg-color: white;
}
body.dark {
--bg-color: black;
}
<button onclick="document.body.classList.toggle('dark')">Сменить тему</button>
Как это работает:
Переменная --bg-color регистрируется как цвет (<color>).
CSS знает, как анимировать цвета, и применяет плавную анимацию при смене класса.
Сценарий работает без JavaScript-анимаций, только CSS.
Где можно использовать:
- Анимации углов (--angle), размеров (--width), цветов (--bg-color);
- Темы светлая/тёмная;
- Динамические состояния компонентов;
- Улучшенные UI/UX без JavaScript.
Важно знать
Поддержка: работает в Chrome и Edge (на Blink), Safari — частично, Firefox пока не поддерживает полностью.
Работает только с переменными, объявленными через @property. Простые --var по-прежнему нельзя анимировать.
Вы должны явно указать тип, иначе браузер проигнорирует попытку анимации.
8. Математические функции в CSS — round(), mod(), rem(), pow(), sqrt() и константы
CSS всегда был довольно ограничен в вычислениях. Нам приходилось использовать calc() для самых простых задач:
width: calc(100% - 40px);
А если нужны были более сложные вычисления (деление, остаток от деления, округление, степень), приходилось писать JS или использовать препроцессоры.
Теперь же можно:
- округлять значения, как в JavaScript;
- получать остаток от деления;
- вычислять степени и корни;
- использовать константы вроде pi, e, infinity;
- создавать по-настоящему динамические стили, без необходимости подключать скрипты.
Поддерживаемые функции
CSS теперь поддерживает следующие функции:
- round() - Округление значения
- mod() - Математический модуль
- rem() - Остаток от деления
- pow() - Возведение в степень
- sqrt() - Квадратный корень
- abs() - Абсолютное значение
- Константы: pi, e, infinity, NaN
Функция round(): округление
Функция round() округляет значение с нужной точностью.
font-size: round(12.3rem, 1rem); /* вернёт 12rem */
Формат:
round(<mode>, <value>, <precision>)
mode — необязательный параметр (по умолчанию nearest).
value — значение, которое нужно округлить.
precision — до какой величины округляем (по умолчанию 1).
Поддерживаемые режимы:
- up - Всегда вверх (как Math.ceil)
- down - Всегда вниз (Math.floor)
- nearest - До ближайшего (Math.round)
- to-zero - К ближайшему к нулю (уникальное поведение)
Пример 1: Округление размеров карточек
.card {
width: round(255.6px, 10px); /* 260px */
}
Вы можете динамически подогнать размер к кратному шагу сетки.
Пример 2: Округление поворота
.rotated {
rotate: round(up, -83deg, 45deg); /* -45deg */
}
Визуально приятнее — помогает подогнать к кратным значениям.
Функция rem() — остаток от деления по знаку делимого
Функция rem() в CSS возвращает остаток от деления, как % в JavaScript или других языках программирования. Главное отличие от mod() — она сохраняет знак первого параметра (делимого).
Формат:
rem(<делимое>, <делитель>)
Примеры:
rem(32px, 5px) /* 2px */
rem(-32px, 5px) /* -2px */
rem(32px, -5px) /* 2px */
rem(-32px, -5px) /* -2px */
В отличие от mod(), rem() просто берёт «остаток от деления», но сохраняет знак первого аргумента.
Почему это важно?
Допустим, у нас есть блоки, которые двигаются по циклу, и нам важно сохранять направление:
+rem — отступ вправо
-rem — отступ влево
Если бы мы использовали mod(), то получили бы всегда положительный результат при положительном делителе, а rem() оставит знак — и мы сможем применять это для симметричных расчётов.
Пример 1: Адаптивное выравнивание элементов по сетке
.grid-item {
margin-right: rem(20px, 6px);
}
Сценарий: Вы создаёте карточки в сетке и хотите динамически подгонять отступы так, чтобы они были кратны 6px (например, сетка дизайна в Figma так построена). Если значение 20px не делится, остаток 2px вы можете вычесть/компенсировать, чтобы выровнять.
Пример 2: Плавающее позиционирование с rem()
.circle {
left: calc(50% + rem(35px, 10px));
}
Что делает код:
- Центрирует элемент по горизонтали,
- Затем добавляет остаток от деления 35 на 10 — 5px — т.е. небольшое смещение.
- Можно использовать в динамических анимациях или при микронастройке отступов в рамках адаптивной сетки.
Когда использовать rem()?
- Когда нужно отразить остаток от деления, сохраняя направление;
- При создании движений влево/вправо, анимаций;
- Для плавающих смещений, основанных на остатке от условной ширины/высоты;
- Для отладки геометрии и дизайна, когда что-то съезжает из-за дробных значений.
Реальный пример: точное позиционирование по остаткам
.box {
position: absolute;
top: rem(75px, 12px); /* 3px */
left: rem(-60px, 20px); /* -0px */
}
Можно использовать в визуальных конструкциях (например, тень, отступ для декоративного элемента), где важно сохранить направление смещения при разных условиях.
Функция mod() — остаток от деления по знаку делителя
Если в rem() знак результата сохраняется как у делимого (первого параметра), то mod() ведёт себя как классическая математическая функция остатка от деления, где результат принимает знак делителя.
Функция mod() полезна, когда вы хотите поведение, более привычное из математики, особенно для расчётов, где важно учитывать кратность и цикличность — например, в каруселях, шагах сетки, круговых движениях.
Синтаксис
mod(<делимое>, <делитель>)
Примеры:
mod(32px, 5px) /* 2px */
mod(-32px, 5px) /* 3px */
mod(32px, -5px) /* -3px */
mod(-32px, -5px) /* -2px */
Пример 1:
margin-left: mod(32px, 5px);
→ 32 делим на 5 → 6 * 5 = 30 → остаток = 2.
margin-left: mod(-32px, 5px);
→ -32 делим на 5 → ближайшее меньшее кратное 5: -35 → остаток = 3
margin-left: mod(32px, -5px);
→ 32 делим на -5 → ближайшее большее кратное -5: -35 → остаток = -3
Пример 2 : Используем mod() для циклических сеток
Допустим, вы хотите, чтобы каждый 4-й элемент в сетке имел особый отступ:
.card {
margin-left: calc(mod(var(--index), 4) * 10px);
}
--index — это переменная с номером элемента.
Остаток от деления на 4 даст значение от 0 до 3.
Мы умножаем его на 10px, получая отступы: 0px, 10px, 20px, 30px.
Идея: Сдвиг элементов в ряду с цикличностью.
Пример 3: Обратное позиционирование для галереи
Вы создаёте двустороннюю галерею, где:
Чётные элементы идут слева направо,
Нечётные — справа налево.
.item {
float: calc(mod(var(--index), 2) == 0 ? left : right);
}
Пока mod() напрямую не поддерживает условные выражения, это может быть реализовано с помощью @property и кастомных значений, но как идея — применение mod() для цикличного определения направлений движения.
Пример 4: Углы вращения
При создании визуальных круговых конструкций (например, анимаций по кругу), вы можете использовать mod() для того, чтобы элемент не выходил за рамки 360°:
.rotate {
transform: rotate(mod(var(--angle), 360deg));
}
Если --angle превышает 360, mod() приведёт его в диапазон 0–359°, создавая бесконечную плавную анимацию без накопления лишнего вращения.
Когда использовать mod()?
- Для циклических расчётов (например, галерея, шаги, сетки);
- Для контроля пределов (например, вращение по кругу);
- Когда нужен остаток от деления, сохраняющий знак делителя (модель «от нуля»);
- Для управления интервалами между элементами в сетке;
- При создании шаблонных анимаций (волны, пульсации, повторяющиеся паттерны).
Комбинируем mod() с calc()
progress-bar {
width: calc(mod(var(--step), 10) * 10%);
}
Если у вас есть 10 шагов прогресса, и вы хотите показывать их циклично — mod() идеально впишется в расчёт.
Заключение по mod()
Функция mod() в CSS открывает двери в настоящую математику, позволяя нам программировать цикличные, повторяющиеся и ограниченные по диапазону стили, прямо в CSS, без JS. Отличное средство для сложных интерактивных интерфейсов, адаптивных сеток и точной геометрии.
Функция pow() — возведение в степень в чистом CSS
Раньше, чтобы вычислить значение, возведённое в степень, приходилось либо делать это вручную, либо использовать JavaScript. Например, для создания прогресс-баров, визуализаций роста, логарифмических шкал и прочих взаимодействий, требующих экспоненциального роста.
Теперь же, с функцией pow() вы можете выполнять экспоненциальные вычисления прямо в CSS, без помощи JavaScript — и это действительно мощно!
Синтаксис
pow(<base>, <exponent>)
<base> — основание степени.
<exponent> — показатель степени.
Важно: pow() работает только с числами без единиц (например, pow(2, 3)), но можно обернуть результат в calc(), если вы хотите использовать единицы измерения.
Что было раньше?
До pow() подобные вычисления приходилось выполнять заранее, в JavaScript.
CSS не позволял ничего, кроме calc() и простых математических операций (+, -, *, /).
Невозможно было сделать настоящую анимацию по экспоненте или создать динамический расчёт масштабов и размеров.
Примеры:
/* Просто число */
font-size: pow(2, 3); /* 8 */
/* 2 в степени 3 = 2 × 2 × 2 = 8 → шрифт будет 8 (без единицы — скорее всего браузер проигнорирует, если не обернуть в calc()).*/
/* Комбинируем с единицей измерения */
font-size: calc(1rem * pow(2, 3)); /* 8rem */
/* Здесь 2^3 = 8 → умножаем на 1rem = 8rem .*/
/* Более сложный пример: логарифмический масштаб */
width: calc(10px * pow(1.5, var(--step)));
Реальные примеры:
1. Прогресс-бары с экспонентой
.progress {
width: calc(10px * pow(2, var(--step)));
}
--step увеличивается от 0 до 5.
Результат: ширина будет: 10px, 20px, 40px, 80px, 160px, 320px.
Это имитирует экспоненциальный рост — полезно для визуализации динамики (например, роста доходов, количества пользователей, сложности).
2. Масштабирование элементов
.card {
transform: scale(calc(pow(1.2, var(--zoom-level))));
}
Увеличиваем --zoom-level, и элемент масштабируется экспоненциально.
Пример: 1.2^1 = 1.2, 1.2^2 = 1.44, 1.2^3 = 1.728 и т.д.
Применимо в интерфейсах, где пользователь увеличивает/уменьшает масштаб — например, в графиках или визуальных редакторах.
3. Анимация «роста» по экспоненте
@keyframes grow {
0% { width: calc(10px * pow(2, 0)); }
25% { width: calc(10px * pow(2, 1)); }
50% { width: calc(10px * pow(2, 2)); }
75% { width: calc(10px * pow(2, 3)); }
100% { width: calc(10px * pow(2, 4)); }
}
Это создаёт эффект ускоряющегося роста, когда блок "раздувается" всё быстрее.
Подходит для визуальных эффектов, прогрессивной загрузки, появления элементов.
4. Построение логарифмической сетки
.grid-line {
left: calc(10px * pow(2, var(--index)));
}
Подходит для графиков, музыкальных шкал (например, частот), логарифмических осей.
При использовании --index от 0 до 10 создаётся логарифмический масштаб, аналогичный графикам частот в эквалайзерах.
Что важно помнить?
- pow() не работает с единицами, но можно использовать calc() для совмещения.
- Результаты округляются до 6 знаков после запятой.
- Использование pow() совместимо с новыми браузерами, поддерживающими Baseline 2024.
Когда использовать pow()?
- При визуализации экспоненциального роста или уменьшения.
- В графиках, прогресс-барах, анимациях с ускорением.
- При построении логарифмических или геометрических рядов в интерфейсах.
- Для создания динамически масштабируемых интерфейсов.
Заключение по pow()
Функция pow() — это один из самых математически мощных инструментов в CSS. Она позволяет разработчикам интегрировать прогрессивные расчёты без использования JavaScript. Теперь мы можем делать масштабирование, анимации и визуализации, основанные на степенях, прямо в стилях — и это по-настоящему открывает новые горизонты в веб-разработке.
sqrt() — квадратный корень в CSS
Иногда при построении интерфейсов, особенно в графиках, анимациях или при вычислении пропорциональных размеров, необходимо использовать квадратный корень числа.
Например:
- создание пропорциональных блоков;
- расчет площади, диагонали, пропорций;
- реализация законов восприятия (например, рост объекта по корню от времени);
- ослабление анимации или замедление.
Раньше это было возможно только в JavaScript. Теперь — напрямую в CSS.
Синтаксис
sqrt(<number>)
<number> — любое положительное число без единиц измерения.
Чтобы использовать sqrt() с единицами (rem, px, % и т.д.), нужно обернуть его в calc().
Что было раньше?
Без sqrt() нужно было вычислять корень заранее (вручную или с JS).
Анимации по квадратному корню были невозможны без скриптов.
Например, нельзя было плавно уменьшать размер по законам физики без доп. логики.
Примеры:
/* Просто число */
width: sqrt(64); /* 8 */
/* 64 → √64 = 8. CSS рассчитает это значение и применит как width: 8, но без единицы браузер может проигнорировать результат. */
/* Используем вместе с единицей */
width: calc(1rem * sqrt(64)); /* 8rem */
/* 1rem × √64 = 1rem × 8 = 8rem → корректный синтаксис.*/
/* Пример с переменной */
width: calc(1vw * sqrt(var(--area)));
/*Если --area: 100, то sqrt(100) = 10 → 10vw. Это позволяет динамически изменять ширину, например, в зависимости от данных. */
Реальные применения
1. Расчёт диагонали блока
По теореме Пифагора:
√(a² + b²) = диагональ
:root {
--width: 300;
--height: 400;
}
.box {
width: calc(1px * var(--width));
height: calc(1px * var(--height));
--diag: sqrt(calc(pow(var(--width), 2) + pow(var(--height), 2)));
border: 1px dashed red;
}
Используем sqrt() для расчета диагонали. Можно применить, например, для поворота блока по диагонали или позиционирования всплывающего элемента.
2. Анимация роста по корню времени
@keyframes grow {
0% { width: calc(1rem * sqrt(0)); }
25% { width: calc(1rem * sqrt(1)); }
50% { width: calc(1rem * sqrt(4)); }
75% { width: calc(1rem * sqrt(9)); }
100% { width: calc(1rem * sqrt(16)); }
}
sqrt(0) = 0
sqrt(1) = 1
sqrt(4) = 2
sqrt(9) = 3
sqrt(16) = 4
Эффект: рост блока замедляется, потому что скорость увеличения уменьшается с каждым кадром.
3. Визуализация по площади
Пусть у нас есть переменная --area, и мы хотим визуально показать квадрат, у которого эта площадь. Для этого нам нужно рассчитать сторону квадрата — это и есть sqrt():
.square {
--area: 400;
width: calc(1px * sqrt(var(--area)));
height: calc(1px * sqrt(var(--area)));
background: lightblue;
}
√400 = 20, получаем квадрат 20x20 пикселей.
4. Адаптивные сетки
Иногда нужно динамически рассчитать ширину колонки, исходя из общего числа элементов:
:root {
--total: 16;
}
.grid-item {
width: calc(100% / sqrt(var(--total)));
}
√16 = 4 → 100% / 4 = 25%
Это позволяет равномерно распределить n² элементов по строкам.
Когда использовать sqrt()?
- При динамических вычислениях, завязанных на площади, диагонали и пропорциях.
- В анимациях, где нужно добиться замедления (например, физика движения).
- В визуализациях, например, диаграммах роста, где визуальная метрика должна соответствовать площади.
- При адаптивных сетках, чтобы рассчитать оптимальное число колонок или размер.
Важно
- sqrt() работает только с числами — не с единицами.
- Чтобы использовать его в реальных стилях — всегда оборачивайте в calc() с единицами (rem, px, vw, и т.д.).
- Поддерживается в современных браузерах с CSS Math Functions (Baseline 2024).
Заключение по sqrt()
CSS-функция sqrt() — это простой, но мощный инструмент, который позволяет делать больше без JS. Она открывает возможности для динамики, адаптации и математически обоснованных решений прямо в стилях.
Визуализация диагоналей, площади, роста или ослабления — теперь не требуют сложной логики. Просто используйте sqrt() — и будьте спокойны за точность и производительность.
Математические константы в CSS — pi, infinity, e, NaN
До недавнего времени, если вам нужно было использовать математические константы вроде числа π или бесконечности, приходилось писать их вручную в calc() или использовать JavaScript.
Например:
- Для поворота элемента по окружности.
- Для создания анимаций по синусоиде (например, π).
- Для задания экстремальных значений (например, z-index: 999999).
- Или обработки некорректных значений (NaN, бесконечность и т.д.).
Теперь в CSS появились встроенные константы, поддерживаемые как часть CSS Math Functions начиная с 2024 года.
Поддерживаемые константы:
- pi Число π (3.1415…), полезно для кругов, вращения и анимации
- e Число Эйлера (2.718…), применяется в логарифмических и экспоненциальных расчетах
- infinity Положительная бесконечность
- -infinity Отрицательная бесконечность
- NaN Not a Number — может использоваться как заглушка или для сброса
Синтаксис
property: calc(math-constant * value);
Константы можно использовать в любом выражении, совместимом с calc(), min(), max() и прочими функциями.
Примеры использования
1. Вращение на π радиан
Радианы — основная единица измерения углов в CSS-поворотах (rotate), и π радиан = 180°.
.box {
transform: rotate(calc(pi * 1rad)); /* 180deg */
}
Как работает:
pi * 1rad → 3.14159 * 1rad = 3.14159rad → 180°
Браузер автоматически интерпретирует результат в градусах.
Применение: поворот стрелки, смена направления, разворот элемента.
2. Круговая анимация по углу
@keyframes spin {
to {
transform: rotate(calc(2 * pi * 1rad)); /* 360deg */
}
}
.spinner {
animation: spin 2s linear infinite;
}
Это идеально для прогресс-индикаторов, круглых лоадеров или циферблатов.
3. z-index на бесконечность
Раньше приходилось писать z-index: 999999, чтобы поднять элемент поверх всех. Сейчас:
.modal {
z-index: calc(infinity);
}
Или если хотим отправить элемент в ад 👇
.hidden {
z-index: calc(-infinity);
}
Для модальных окон, алертов, всплывающих элементов.
Чтобы гарантировать, что элемент не окажется под другими слоями.
4. Управление шириной на грани
.container {
max-width: calc(infinity); /* по сути, неограниченно */
}
Это особенно удобно, если вы хотите снять ограничения или задать заведомо "максимальное" значение.
5. Использование e для расчетов
Например, логарифмическое или экспоненциальное масштабирование:
.scale {
transform: scale(calc(pow(e, 2))); /* e² = ~7.389 */
}
Подойдет для физики, замедления/ускорения, распределения значений.
6. Отладка через NaN
Иногда полезно сбросить значение или специально задать ошибочное поведение:
.test {
width: rem(3, 0); /* Деление на ноль → NaN */
}
Можно использовать NaN в качестве тестовой заглушки или для того, чтобы убедиться, что правило не применяется.
Как это все работает?
Браузер под капотом знает числовые значения этих констант:
- pi ≈ 3.14159265359
- e ≈ 2.71828182846
- infinity - бесконечность
- NaN - Не число
Все они не требуют объявления — просто используйте их в calc(), и они сработают.
Реальные применения
Построение графиков
Для анимации по синусоиде или кругу:
.graph-point {
left: calc(50% + cos(pi) * 100px);
top: calc(50% + sin(pi) * 100px);
}
Отключение/сброс свойств
Можно сбросить размер, позицию, z-индекс и прочее, если хотите «отключить» элемент:
.element {
top: calc(NaN);
}
Лимитирование значений
input[type="range"] {
max-width: calc(infinity);
}
Работает как гарантированный максимум, без ограничений.
Когда использовать математические константы?
- При работе с вращением, окружностями и углами (pi);
- В визуализациях и графиках;
- Для установки предельно больших или малых значений (infinity, -infinity);
- Для отладки или исключения значений (NaN);
- В физике движения или анимациях, где используется e.
Всё, что раньше требовало JS-обработки, теперь можно делать прямо в CSS. И это не просто удобно — это быстрее, безопаснее и проще поддерживать.
Собираем все вместе
Мы разложили новые CSS-возможности по полочкам — теперь давай соберем их в реальный пример, чтобы понять, как они могут работать в тандеме.
Сценарий: Умная карточка товара
Давайте представим, что мы делаем карточку товара в интернет-магазине.
В ней:
- Картинка, цена, кнопка «Добавить в корзину»;
- Поведение адаптивное, с центровкой;
- Поддерживается тёмная тема;
- Кнопка появляется с анимацией при наведении;
- Используются переменные с типами;
- Работа с округлением и остатком;
- Рендер оптимизирован для быстрого отображения.
Примерный итоговый код для нашего случая может выглядить так:
<div class="product-card" popover>
<img src="tune-it-course.jpg" alt="Онлайн-курс в tune-it" />
<p class="price">Цена: <span class="amount">25000 руб.</span></p>
<button class="add-to-cart">Добавить в корзину</button>
</div>
* 1. Центрируем карточку по горизонтали и вертикали */
body {
display: grid;
align-content: center;
justify-content: center;
min-height: 100vh;
}
/* 2. Поддержка тёмной темы */
:root {
--bg: light-dark(#fff, #1a1a1a);
--text: light-dark(#000, #fff);
--accent: light-dark(#007bff, #66ccff);
--price-color: light-dark(#333, #ccc);
}
body {
background: var(--bg);
color: var(--text);
}
/* 3. Карточка с ленивым рендером и резервной высотой */
.product-card {
content-visibility: auto;
contain-intrinsic-size: 300px;
width: 300px;
padding: 1rem;
border-radius: 8px;
background: var(--bg);
box-shadow: 0 0 10px rgba(0,0,0,0.1);
transition: box-shadow 0.3s ease;
}
/* 4. Анимация кнопки при появлении карточки */
.add-to-cart {
display: none;
transition: opacity 0.5s ease;
}
.product-card:hover .add-to-cart {
display: block;
opacity: 0;
@starting-style {
opacity: 0;
}
transition-behavior: allow-discrete;
opacity: 1;
}
/* 5. Стилизация валидации (не используется тут, но можно добавить к форме) */
/*
input:invalid {
border: 1px solid red;
}
input:user-invalid {
border: 1px solid red;
}
*/
/* 6. Типизированные переменные */
@property --card-scale {
syntax: "<number>";
inherits: false;
initial-value: 1;
}
.product-card {
--card-scale: 1;
transform: scale(var(--card-scale));
transition: transform 0.4s ease;
}
.product-card:hover {
--card-scale: 1.05;
}
/* 7. Математика */
.price .amount::after {
content: " (скидка " calc(round(120.49 * 0.2, 0.01)) " $)";
color: var(--price-color);
}
/* 8. z-index через infinity */
.product-card[popover]:open {
z-index: calc(infinity);
}
/* 9. Пример остатка: если цена делится на 10 — выделим */
.price .amount[data-price-mod]:after {
content: " [special offer]";
}
/* JS для установки атрибута, если mod(120, 10) === 0 */
Что делает этот код?
- Центрирует карточку по экрану (align-content: center);
- Поддерживает светлую и тёмную темы через light-dark() и переменные;
- Кнопка появляется мягко, даже если была скрыта;
- Используются типизированные переменные для анимации масштабирования;
- Выводится скидка с помощью round() прямо в контенте;
- z-index карточки устанавливается на бесконечность при открытии;
- Остаток (mod) можно использовать для условий (например, отмечать акции).
Почему это круто?
- Всё сделано без единой строки JavaScript (кроме, может, установки data-price-mod);
- Поддержка в современных браузерах;
- Чистый, читаемый CSS, который легко поддерживать;
- Производительность за счёт content-visibility;
- Математика прямо в стилях.
Заключение
CSS прошёл огромный путь — от набора скучных деклараций до настоящего языка с логикой, вычислениями и реактивностью. И всё это — без единой строчки JavaScript. Новые свойства и функции дают разработчикам невероятный контроль над поведением элементов, их стилями, адаптацией под пользовательские предпочтения, а также возможностями оптимизации и анимации.
Что особенно важно — всё это уже доступно: большинство новых фич поддерживается последними версиями Chrome, Firefox и Safari. А значит, вы можете внедрять их уже сейчас — не откладывая на потом, не боясь поломок и не полагаясь на костыли.