CSS переменные: полное руководство от основ до продвинутых техник
Оглавление
- Основы. Чем CSS переменные принципиально отличаются от переменных препроцессоров
- Уникальное поведение: каскад, наследование и циклы
- Практическое применение: темы, адаптивный дизайн и кастомизация компонентов
- Интеграция с JavaScript: динамика и интерактивность
- Продвинутые техники и «хаки»
- Ограничения и подводные камни
- Заключение. Почему это важно для вашего бизнеса
Мир фронтенда постоянно развивается, и одна из его самых значимых эволюций — появление нативных CSS переменных, официально известных как Custom Properties. Если вы до сих пор считаете их просто аналогом переменных из Sass или Less, эта статья перевернет ваше представление. CSS переменные — это не просто удобство для хранения цвета или размера шрифта. Это мощный инструмент, который меняет парадигму взаимодействия CSS, HTML и JavaScript, предлагая настоящую динамику, каскадирование и реактивность прямо в таблицах стилей.
Эта статья проведет вас от основ до продвинутых техник, покажет практические кейсы и убедит, что будущее стилизации веб-приложений уже здесь.
Основы. Чем CSS переменные принципиально отличаются от переменных препроцессоров
Давайте начнем с азов. В препроцессорах, как Sass, переменная — это абстракция, которая существует только на этапе разработки. Препроцессор подставляет её значение в конечный CSS-файл. После компиляции переменная исчезает.
Sass-переменная:
$primary-color: #3498db;
.button { background-color: $primary-color; }
После компиляции в CSS мы увидим: .button { background-color: #3498db; }. Значение зафиксировано.
CSS переменная — это полноценное CSS-свойство. Оно существует в браузере, участвует в каскаде, наследовании и может быть изменено на лету.
:root {
--primary-color: #3498db; /* Объявляем переменную */
}
.button {
background-color: var(--primary-color); /* Используем через функцию var() */
}
Ключевые отличия уже видны:
- Имена начинаются с двух дефисов:
--my-variable. - Область видимости: Они привязаны к элементу и его потомкам (наследуются), если не переопределены.
- Доступность в браузере: Их можно читать и изменять с помощью JavaScript.
Как объявлять и использовать
Объявление происходит как у обычного свойства. Лучшее место для глобальных переменных — селектор :root (корень документа, обычно <html>).
:root {
--main-color: #e74c3c;
--spacing-unit: 16px;
--header-height: 60px;
}
Использовать значение переменной можно с помощью функции var().
.header {
height: var(--header-height);
background-color: var(--main-color);
margin-bottom: var(--spacing-unit);
}
Запасное значение (Fallback)
Функция var() принимает второй, необязательный аргумент — запасное значение, которое будет использовано, если переменная не определена или невалидна.
.element {
/* Если --accent-color нет, будет использован синий цвет */
color: var(--accent-color, blue);
/* Можно использовать и другую переменную как fallback */
padding: var(--custom-padding, var(--spacing-unit));
}
Это одна из ключевых фич, которая открывает возможности для создания тем и безопасного экспериментального CSS.
Уникальное поведение: каскад, наследование и циклы
Вот где начинается настоящая магия. Поскольку CSS переменные — это CSS-свойства, они подчиняются тем же правилам, что и, например, color или font-size.
Каскад и переопределение
Переменные можно переопределять для конкретных элементов, медиа-запросов или состояний. Это основа для создания контекстных стилей и тем.
:root {
--theme-bg: white;
--theme-text: black;
}
body.dark-mode {
--theme-bg: #1a1a1a;
--theme-text: #f0f0f0;
}
body {
background-color: var(--theme-bg);
color: var(--theme-text);
transition: background-color 0.3s, color 0.3s;
}
Изменение класса у <body> на dark-mode мгновенно переопределит значения переменных для всего поддерева и обновит стили.
Наследование и его отмена
Как и color, CSS переменные наследуются потомками. Но иногда это не нужно. Наследование можно «оборвать», установив для переменной специальное значение initial.
.card {
--card-highlight: gold; /* Эта переменная будет видна внутри .card */
}
.card__footer {
--card-highlight: initial; /* Здесь мы "обрываем" наследование, значение сбросится */
/* Теперь var(--card-highlight) вернется к значению, объявленному выше по дереву, или к дефолтному */
}
Более радикальный способ — использовать универсальный селектор для сброса наследования, хотя на практике это требуется редко.
Опасность циклических зависимостей
CSS не является императивным языком программирования. Все объявления обрабатываются одновременно. Это создает ловушку циклических зависимостей.
:root {
--size: 10px;
}
.box {
width: var(--size);
--size: calc(var(--width) - 10px); /* ОШИБКА: width зависит от --size, а --size зависит от width */
}
Браузер обнаружит эту циклическую зависимость и объявит значение переменной недействительным на момент вычисления (invalid at computed-value time — IACVT). В таком случае свойство, использующее эту переменную, откатится к своему начальному значению (например, width: auto), что может привести к неожиданному «развалу» верстки.
Избегайте ситуаций, когда переменная A зависит от переменной B, которая, в свою очередь, зависит от A.
Практическое применение: темы, адаптивный дизайн и кастомизация компонентов
Создание переключаемых тем
Это классический и самый наглядный пример. Раньше для тем приходилось дублировать CSS-правила или генерировать новые классы. С переменными всё сводится к переопределению нескольких значений.
:root {
/* Светлая тема (по умолчанию) */
--color-bg: #fff;
--color-text: #222;
--color-primary: #4a6fa5;
--shadow: 0 2px 10px rgba(0,0,0,0.1);
}
[data-theme="dark"] {
/* Тёмная тема */
--color-bg: #222;
--color-text: #f0f0f0;
--color-primary: #66cc99;
--shadow: 0 2px 10px rgba(255,255,255,0.05);
}
body {
background: var(--color-bg);
color: var(--color-text);
font-family: sans-serif;
transition: background 0.3s ease, color 0.3s ease;
}
.button {
background: var(--color-primary);
box-shadow: var(--shadow);
}
<body>
<button class="button" onclick="document.body.dataset.theme='dark'">Включить тёмную тему</button>
<!-- Остальной контент -->
</body>
Упрощение адаптивного дизайна (Responsive Design)
Часто в медиа-запросах мы меняем множество свойств: отступы, размеры шрифтов, сетки. С переменными можно менять только набор базовых «токенов».
:root {
--gutter: 16px;
--columns: 4;
--font-scale: 1;
}
@media (min-width: 768px) {
:root {
--gutter: 24px;
--columns: 8;
--font-scale: 1.125;
}
}
@media (min-width: 1200px) {
:root {
--gutter: 32px;
--columns: 12;
--font-scale: 1.25;
}
}
.container {
padding: 0 var(--gutter);
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
gap: var(--gutter);
}
.title {
font-size: calc(2rem * var(--font-scale));
}
Код становится декларативным и легче поддерживается. Изменение отступа на всех разрешениях теперь требует правки в одном месте.
Стилизация компонентов с «публичным API»
Это мощнейший паттерн. Вы можете создать компонент (например, кнопку), стилизация которого контролируется не внутренними сложными селекторами, а набором четко определенных CSS-переменных. Это похоже на публичный API для стилей.
CSS компонента:
.button {
/* Публичные переменные API с безопасными значениями по умолчанию */
--button-bg: var(--color-primary, #4a6fa5); /* Использует глобальную или запасной цвет */
--button-text: white;
--button-padding: 0.75em 1.5em;
--button-radius: 4px;
/* Внутренние стили, использующие API */
background-color: var(--button-bg);
color: var(--button-text);
padding: var(--button-padding);
border-radius: var(--button-radius);
border: none;
cursor: pointer;
transition: background-color 0.2s;
}
.button:hover {
--button-bg: var(--color-primary-dark, #3a5a85); /* Переопределяем только нужную переменную для состояния */
}
Использование в HTML: Теперь мы можем кастомизировать каждую кнопку индивидуально, не создавая новых классов и не переопределяя правила с повышенной специфичностью.
<button class="button">Обычная</button>
<button class="button" style="--button-bg: #e74c3c; --button-radius: 20px;">Особая</button>
Это дает беспрецедентную гибкость и является отличной альтернативой генерации множества модификатор-классов в методологиях типа БЭМ.
Интеграция с JavaScript: динамика и интерактивность
Это, пожалуй, самый революционный аспект. Раньше для динамического изменения стиля из JavaScript приходилось либо менять классы, либо напрямую лезть в element.style. Теперь мы можем управлять дизайном через изменение данных (переменных).
Простой пример: отслеживание курсора мыши
Допустим, мы хотим, чтобы градиент или тень следовали за курсором.
JavaScript:
document.addEventListener('mousemove', (e) => {
// Вычисляем положение курсора в процентах
const x = (e.clientX / window.innerWidth) * 100;
const y = (e.clientY / window.innerHeight) * 100;
// Устанавливаем переменные в корень документа
document.documentElement.style.setProperty('--mouse-x', `${x}%`);
document.documentElement.style.setProperty('--mouse-y', `${y}%`);
});
CSS:
.follow-cursor {
width: 300px;
height: 300px;
/* Центр радиального градиента привязан к положению мыши */
background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), red, blue);
}
Перемещайте курсор мыши, чтобы увидеть как градиентное пятно следует за ним!
See the Pen mouse cursor tracking by inteltone (@inteltone) on CodePen.
Связь чистая и простая: JS занимается данными (—mouse-x), CSS — их визуальным представлением.
Создание кастомного ползунка (Range Input)
Значение ползунка можно напрямую «прокидывать» в CSS.
HTML:
<input type="range" min="0" max="100" value="50" class="custom-slider">
<div class="slider-preview"></div>
JS:
const slider = document.querySelector('.custom-slider');
const root = document.documentElement;
slider.addEventListener('input', (e) => {
root.style.setProperty('--slider-value', e.target.value + '%');
});
CSS:
.slider-preview {
width: 300px;
height: 30px;
/* Ширина фона зависит от значения ползунка */
background: linear-gradient(90deg, #3498db 0%, #3498db var(--slider-value, 50%), #eee var(--slider-value, 50%));
border: 1px solid #ccc;
}
Перемещайте ползунок слайдера, чтобы увидеть как на это реагирует связанный с ним элемент!
See the Pen Creating a Custom Slider (Range Input) by inteltone (@inteltone) on CodePen.
Индикатор прогресса прокрутки (Scroll Progress)
Аналогично можно создать индикатор, показывающий, какую часть страницы или контейнера пользователь уже проскроллил.
JavaScript:
const scrollableContainer = document.querySelector('.scrollable-content');
scrollableContainer.addEventListener('scroll', () => {
const scrollPercent = (scrollableContainer.scrollTop / (scrollableContainer.scrollHeight - scrollableContainer.clientHeight)) * 100;
scrollableContainer.style.setProperty('--scroll-progress', `${scrollPercent}%`);
});
CSS:
.scrollable-content {
--scroll-progress: 0%;
height: 300px;
overflow-y: auto;
}
.scrollable-content::before {
content: '';
position: absolute;
top: 0;
left: 0;
height: 4px;
width: var(--scroll-progress);
background: linear-gradient(90deg, red, orange);
z-index: 10;
}
Воспользуйтесь прокруткой страницы, чтобы увидеть Индикатор прокрутки страницы в действии!
See the Pen Scroll Progress Indicator by inteltone (@inteltone) on CodePen.
Продвинутые техники и «хаки»
Имитация свойств-миксинов (Mixins) и сокращений (Shorthands)
Вы можете создать «псевдосвойство», которое предзаполняет часть значений реального свойства.
/* Создаем свой "миксин" для тени с фиксированным цветом */
.element {
--purple-shadow: 0 10px 20px rebeccapurple; /* Предопределяем всю тень */
box-shadow: var(--purple-shadow, 0 10px 20px #000); /* Используем с fallback */
}
/* Теперь мы можем переопределять только часть, например, размытие */
.element:hover {
--purple-shadow: 0 10px 30px rebeccapurple; /* Меняем только размытие с 20px на 30px */
}
Работа с SVG
CSS-переменные прекрасно работают внутри SVG, позволяя динамически управлять атрибутами.
<svg width="200" height="200">
<circle cx="100" cy="100" r="50" class="morphing-circle"/>
</svg>
<button onclick="changeColor()">Сменить цвет</button>
.morphing-circle {
fill: var(--circle-fill, #3498db); /* Управляем заливкой через CSS */
transition: fill 0.3s;
}
function changeColor() {
document.querySelector('.morphing-circle').style.setProperty('--circle-fill', '#e74c3c');
}
Кликните на кнопке «Сменить цвет», чтобы увидеть изменение цвета круга.
See the Pen Working with SVG by inteltone (@inteltone) on CodePen.
Полифилы и поддержка браузеров
На сегодняшний день поддержка CSS переменных составляет более 95% глобально. Для старых браузеров (в основном IE11) существуют полифилы, например, css-vars-ponyfill. Однако чаще используется подход постепенной улучшаемости (progressive enhancement):
.button {
background: #4a6fa5; /* Фолбэк для старых браузеров */
background: var(--primary-color, #4a6fa5); /* Современные браузеры используют это */
}
Или с помощью @supports:
.button {
background: #4a6fa5;
}
@supports (color: var(--test)) {
.button {
background: var(--primary-color);
}
}
Ограничения и подводные камни
- Нельзя использовать в именах свойств, селекторах или медиа-запросах.
var(--size)работает только в значении свойства.margin-top: var(--size);— да.var(--prop): 10px;— нет. - Проблема с конкатенацией строк. Нельзя сделать так:
url('img/' var(--image-name)). Решение — хранить полный путь в переменной:url(var(--full-image-path)). - Чувствительность к регистру и пробелам.
--colorи--Color— разные переменные. Значение--value: ;(пробел) является валидным и будет передано в функциюvar().
Заключение. Почему это важно для вашего бизнеса
CSS переменные — это не очередной модный фреймворк, а фундаментальное улучшение языка стилей. Они позволяют создавать по-настоящему динамические, гибкие и легко поддерживаемые интерфейсы. Разделение ответственности становится четче: JavaScript управляет данными и состоянием, а CSS — их визуальным воплощением. Это приводит к более чистому коду, ускорению разработки и снижению стоимости поддержки проектов.
Готовы вывести ваш цифровой продукт на новый уровень?
Команда нашей веб-студии находится на передовой современных технологий, таких как CSS переменные. Мы не просто создаем статичные макеты — мы проектируем живые, адаптивные и интерактивные интерфейсы, которые работают на вас, повышая вовлеченность и конверсию.
Хотите сайт, который впечатляет не только дизайном, но и техническим совершенством? Давайте обсудим ваш проект. Мы поможем воплотить самые смелые идеи, используя лучшие практики современной фронтенд-разработки, чтобы ваш ресурс был быстрым, гибким и готовым к любым вызовам цифрового мира. Свяжитесь с нами для консультации — создадим что-то выдающееся вместе.