Олег Марков
Динамические стили dynamic-styles - практическое руководство для разработчиков
Введение
Динамические стили (или dynamic-styles) – это подход, при котором внешний вид интерфейса меняется во время работы приложения. Стили уже не «захардкожены» в одном CSS-файле, а зависят от:
- действий пользователя (ховер, клик, ввод данных)
- состояния приложения (режим админа, авторизация, ошибка, загрузка)
- настроек среды (темная / светлая тема, системные предпочтения)
- данных с сервера (ролевая модель, A/B-тесты, фича-флаги)
Смотрите, я покажу вам, как на практике вы можете:
- менять стили прямо из JavaScript
- работать с CSS-переменными и темами
- использовать динамические классы
- управлять стилями в React / Vue
- избегать типичных ошибок с производительностью и поддерживаемостью
Цель статьи – системно разобрать dynamic-styles с разных сторон, чтобы вы могли осознанно выбирать подход, а не просто «дописать inline-style, чтобы заработало».
Базовые способы задания стилей и их динамическое изменение
Чтобы понимать динамические стили, важно вспомнить, какие вообще есть способы задать стиль элементу.
Классический набор способов оформления
- Внешний CSS-файл и классы
- Встроенный
<style>в HTML - Инлайн-стили через атрибут
style - Стили, добавленные или измененные из JavaScript
Давайте кратко напомним каждый способ, а потом посмотрим, как его «оживить».
Классы и внешний CSS
Такой подход наиболее привычен и удобен для больших проектов.
<!-- index.html -->
<button class="button button-primary">Отправить</button>
/* styles.css */
.button {
padding: 8px 16px; /* базовый отступ */
border-radius: 4px; /* скругление углов */
border: none; /* без рамки */
cursor: pointer; /* курсор в виде руки */
}
.button-primary {
background: #007bff; /* основной цвет фона */
color: #ffffff; /* цвет текста */
}
Чтобы сделать стили динамическими, вы меняете классы из JavaScript.
<button id="submitBtn" class="button">Отправить</button>
// Находим кнопку по id
const btn = document.getElementById('submitBtn');
// Добавляем класс при клике
btn.addEventListener('click', () => {
// Здесь мы добавляем класс button-primary, чтобы визуально выделить кнопку
btn.classList.add('button-primary');
});
Вы не трогаете сами CSS-правила, вы просто меняете набор классов на элементе. Этот подход чаще всего и подразумевают под dynamic-styles в классическом вебе.
Инлайн-стили
Стили можно задать напрямую в HTML:
<button style="background: red; color: white">Удалить</button>
Или из JavaScript:
const btn = document.querySelector('#delete');
// Здесь мы задаем инлайн-стиль для фона
btn.style.background = 'red';
// Здесь задаем цвет текста
btn.style.color = 'white';
Инлайн-стили легко сделать динамическими, но они плохо масштабируются и часто конфликтуют с общими стилями. Мы еще вернемся к этому минусу.
Динамическое изменение стилей через классы
Самый управляемый и предсказуемый способ динамики – работа с классами. Вы заранее описываете стили, а в рантайме переключаете классы.
Основные методы работы с classList
Смотрите, я покажу вам минимальный набор, который удобно использовать:
const el = document.querySelector('.item');
// Добавить класс
el.classList.add('active');
// Удалить класс
el.classList.remove('active');
// Переключить класс
el.classList.toggle('active');
// Проверить наличие класса
if (el.classList.contains('active')) {
// Здесь вы можете выполнить дополнительную логику
}
Теперь давайте разберемся на примере элемента со скрытием/показом.
<div id="panel" class="panel panel-hidden">
Секретная информация
</div>
<button id="toggleBtn">Показать / скрыть</button>
.panel {
transition: opacity 0.3s ease; /* плавное изменение прозрачности */
}
.panel-hidden {
opacity: 0; /* элемент невидим */
pointer-events: none; /* нельзя кликнуть */
}
.panel-visible {
opacity: 1; /* элемент виден */
pointer-events: auto; /* события включены */
}
const panel = document.getElementById('panel');
const btn = document.getElementById('toggleBtn');
btn.addEventListener('click', () => {
// Здесь мы переключаем два класса в зависимости от текущего состояния
panel.classList.toggle('panel-visible');
panel.classList.toggle('panel-hidden');
});
Как видите, весь «динамический» аспект – в управлении классами. Такой паттерн хорошо читается и легко расширяется.
Динамические классы в зависимости от состояния
Частая задача – меняем стиль в зависимости от конкретного значения, например статуса заказа.
<div class="order" data-status="pending">Заказ 123</div>
<div class="order" data-status="completed">Заказ 124</div>
<div class="order" data-status="canceled">Заказ 125</div>
.order {
padding: 8px; /* отступ внутри блока */
border-radius: 4px; /* скругление углов */
margin-bottom: 4px; /* отступ между заказами */
}
.order-pending {
background: #fff3cd; /* желтоватый фон для ожидания */
}
.order-completed {
background: #d4edda; /* зеленый фон для выполненных заказов */
}
.order-canceled {
background: #f8d7da; /* красный фон для отмененных заказов */
}
const orders = document.querySelectorAll('.order');
orders.forEach(order => {
// Здесь мы читаем значение статуса из data-атрибута
const status = order.dataset.status;
// Здесь формируем имя класса из статуса
const statusClass = `order-${status}`;
// Здесь добавляем новый класс в зависимости от статуса
order.classList.add(statusClass);
});
Вы отделяете данные (data-status) от представления (order-completed) и можете при необходимости менять маппинг.
Инлайн-стили как форма динамических стилей
Инлайн-стили дают максимальный контроль, но требуют осторожности.
Когда инлайн-подход оправдан
- нужно задать единичное вычисленное значение (например, ширину в процентах)
- нужно анимировать числовое значение, подсчитываемое в JS
- используется сложный калькулятор размеров, зависящий от данных
Пример – прогресс-бар, ширина которого зависит от процента:
<div class="progress">
<div class="progress-bar" id="progressBar"></div>
</div>
.progress {
width: 100%; /* прогресс занимает всю ширину контейнера */
background: #eee; /* светлый фон */
border-radius: 4px; /* скругления */
overflow: hidden; /* обрезаем выступающие части */
height: 16px; /* фиксированная высота */
}
.progress-bar {
background: #007bff; /* цвет заполнения */
height: 100%; /* высота по родителю */
width: 0; /* начальное значение ширины */
transition: width 0.3s; /* плавное изменение ширины */
}
function updateProgress(percent) {
// Здесь мы ограничиваем значение от 0 до 100
const safePercent = Math.min(Math.max(percent, 0), 100);
const bar = document.getElementById('progressBar');
// Здесь мы задаем инлайн-стиль ширины в процентах
bar.style.width = safePercent + '%';
}
// Здесь мы обновляем прогресс до 45%
updateProgress(45);
Инлайн-ширина тут логична: это конкретный вычисленный параметр. Общие вещи (цвета, скругления, анимации) остаются в CSS.
Минусы чрезмерного использования инлайн-стилей
- сложно переопределять и переиспользовать стили
- растет объем HTML-кода
- перекрываются стили из CSS-файлов (из-за более высокого приоритета)
- неудобно рефакторить и менять дизайн
Хорошее правило: инлайн-стиль – для динамического числа (конкретное значение), классы – для логики и состояния.
CSS-переменные как основа dynamic-styles
CSS-переменные (custom properties) – один из самых удобных инструментов для динамических стилей. Вы можете менять одно значение, а оно повлияет на все связанные элементы.
Основная идея
Вы объявляете переменную:
:root {
--primary-color: #007bff; /* основной цвет приложения */
}
И используете ее в разных местах:
.button {
background: var(--primary-color); /* цвет берем из переменной */
color: #ffffff; /* белый текст */
}
.link {
color: var(--primary-color); /* тот же цвет, но как цвет текста */
}
Теперь вы можете изменить --primary-color из JavaScript.
// Здесь мы меняем значение переменной на уровне :root (документа)
document.documentElement.style.setProperty('--primary-color', '#ff5722');
Смотрите, как просто вы изменили цвет одновременно для всех элементов, которые используют эту переменную.
Темизация через CSS-переменные
Давайте разберемся на примере темной и светлой темы.
:root {
--bg-color: #ffffff; /* цвет фона по умолчанию */
--text-color: #000000; /* цвет текста по умолчанию */
}
body {
background: var(--bg-color); /* фон зависит от переменной */
color: var(--text-color); /* цвет текста тоже */
}
/* Дополнительный класс для темной темы */
.theme-dark {
--bg-color: #121212; /* темный фон */
--text-color: #f0f0f0; /* светлый текст */
}
<body id="appBody">
<button id="toggleTheme">Переключить тему</button>
<p>Текст приложения</p>
</body>
const body = document.getElementById('appBody');
const btn = document.getElementById('toggleTheme');
btn.addEventListener('click', () => {
// Здесь мы переключаем класс theme-dark на body
body.classList.toggle('theme-dark');
});
Как только на body появляется theme-dark, переопределяются значения переменных, и вся тема меняется автоматически.
Комбинация JavaScript и CSS-переменных
Можно сделать гибкий контрол с возможностью пользовательской настройки акцентного цвета.
<label>
Акцентный цвет
<input type="color" id="accentPicker" />
</label>
:root {
--accent-color: #ff4081; /* акцент по умолчанию */
}
a {
color: var(--accent-color); /* цвет ссылки зависит от акцента */
}
.button-accent {
background: var(--accent-color); /* фон кнопки - акцент */
color: #ffffff; /* белый текст */
}
const picker = document.getElementById('accentPicker');
picker.addEventListener('input', (event) => {
// Здесь мы получаем выбранный цвет из поля input
const color = event.target.value;
// Здесь мы устанавливаем новое значение CSS-переменной
document.documentElement.style.setProperty('--accent-color', color);
});
Теперь вы увидите, как пользователь буквально «красит» интерфейс, а вам не нужно трогать отдельные элементы.
Dynamic-styles в контексте компонентов (React, Vue)
В современных фреймворках динамические стили – часть повседневной работы: вы постоянно меняете вид компонента в зависимости от его состояния.
React: динамические классы и инлайн-стили
Покажу вам простой пример компонента кнопки, которая меняет стиль при нажатии:
// Здесь мы импортируем хук useState
import { useState } from 'react';
function ToggleButton() {
// Здесь мы заводим состояние isActive
const [isActive, setIsActive] = useState(false);
// Здесь вычисляем список классов в зависимости от состояния
const className = `btn ${isActive ? 'btn-active' : 'btn-inactive'}`;
return (
<button
className={className} // Здесь передаем классы в кнопку
onClick={() => setIsActive(!isActive)} // Здесь переключаем состояние
>
{isActive ? 'Активно' : 'Не активно'} {/* Здесь меняем текст */}
</button>
);
}
.btn {
padding: 8px 12px; /* отступы внутри кнопки */
border-radius: 4px; /* скругленные углы */
border: 1px solid #ccc; /* тонкая рамка */
cursor: pointer; /* курсор руки */
}
.btn-active {
background: #28a745; /* зеленый фон для активной кнопки */
color: #ffffff; /* белый текст */
}
.btn-inactive {
background: #f8f9fa; /* светлый фон для неактивной */
color: #333333; /* темный текст */
}
Здесь динамика реализована через вычисление className.
Инлайн-стили удобно использовать, когда значение нужно посчитать на лету:
function Progress({ percent }) {
// Здесь мы ограничиваем значение от 0 до 100
const safe = Math.min(Math.max(percent, 0), 100);
// Здесь формируем объект со стилями
const barStyle = {
width: safe + '%', // ширина зависит от процента
};
return (
<div className="progress">
{/* Здесь мы передаем динамический стиль внутрь элемента */}
<div className="progress-bar" style={barStyle}></div>
</div>
);
}
Vue: директивы v-bind для классов и стилей
В Vue идея та же, но синтаксис другой.
<template>
<button
class="btn"
:class="{ 'btn-active': isActive, 'btn-inactive': !isActive }"
@click="isActive = !isActive"
>
{{ isActive ? 'Активно' : 'Не активно' }}
</button>
</template>
<script>
// Здесь мы экспортируем компонент Vue
export default {
data() {
// Здесь задаем начальное состояние
return {
isActive: false,
};
},
};
</script>
Для инлайн-стилей:
<template>
<div class="progress">
<!-- Здесь мы привязываем объект стилей к элементу -->
<div class="progress-bar" :style="{ width: safePercent + '%' }"></div>
</div>
</template>
<script>
export default {
props: {
percent: {
type: Number,
required: true,
},
},
computed: {
// Здесь мы вычисляем безопасный процент
safePercent() {
return Math.min(Math.max(this.percent, 0), 100);
},
},
};
</script>
Dynamic-styles во фреймворках – естественное продолжение тех же идей: классы для логики, инлайн-стили для конкретных значений.
CSS-in-JS и библиотечные решения для dynamic-styles
Во многих проектах используют CSS-in-JS или утилитарные библиотеки. Они дают еще больше контроля над динамическими стилями.
Пример: styled-components (React)
Здесь стили описываются прямо в JavaScript и могут зависеть от пропсов компонента.
// Здесь мы импортируем styled из библиотеки styled-components
import styled from 'styled-components';
// Здесь создаем компонент с динамическими стилями
const Button = styled.button`
padding: 8px 16px; /* базовые отступы */
border-radius: 4px; /* скругленные углы */
border: none; /* убираем рамку */
cursor: pointer; /* курсор руки */
background: ${props => (props.primary ? '#007bff' : '#6c757d')}; /* фон зависит от пропса */
color: #ffffff; /* белый текст */
/* Здесь мы описываем состояние hover */
&:hover {
opacity: 0.9; /* немного уменьшаем прозрачность */
}
`;
function App() {
return (
<>
{/* Здесь передаем проп primary и получаем синюю кнопку */}
<Button primary>Основная</Button>
{/* Здесь не передаем primary и получаем серую кнопку */}
<Button>Вторичная</Button>
</>
);
}
Здесь динамическая часть – условный выбор цвета. При этом все стили лежат рядом с компонентом.
Пример: Tailwind CSS и динамические классы
Tailwind – утилитарный CSS-фреймворк, где вы собираете стили из готовых классов. Dynamic-styles реализуются через условное добавление классов.
function Alert({ type, children }) {
// Здесь мы подбираем классы в зависимости от типа
const base = 'px-4 py-2 rounded text-sm'; // базовые утилиты
const color =
type === 'success'
? 'bg-green-100 text-green-700'
: type === 'error'
? 'bg-red-100 text-red-700'
: 'bg-gray-100 text-gray-700';
return <div className={`${base} ${color}`}>{children}</div>;
}
Dynamic-styles сводятся к вычислению строки с классами.
Динамические стили и медиа-запросы
Динамика может зависеть не только от состояния приложения, но и от окружения: ширина окна, системная тема, плотность пикселей и так далее.
Пример: динамика через медиа-запросы без JS
Вы можете использовать медиа-запрос для автоматического переключения стилей.
body {
font-size: 16px; /* базовый размер шрифта */
}
/* Если ширина экрана меньше 600px - уменьшаем шрифт */
@media (max-width: 600px) {
body {
font-size: 14px; /* более мелкий шрифт для узких экранов */
}
}
Это тоже своего рода dynamic-styles, только решаемый силами CSS.
Комбинация медиа-запросов и классов
Можно привязывать классы к breakpoint-логике. В Tailwind это делается автоматически, но вы можете реализовать и вручную через JS.
function applyMobileClass() {
// Здесь мы проверяем, меньше ли ширина окна 600 пикселей
const isMobile = window.innerWidth < 600;
const body = document.body;
// Здесь мы в зависимости от условия добавляем или убираем класс
if (isMobile) {
body.classList.add('is-mobile');
} else {
body.classList.remove('is-mobile');
}
}
// Здесь мы вызываем функцию сразу при загрузке страницы
applyMobileClass();
// Здесь мы подписываемся на изменение размера окна
window.addEventListener('resize', applyMobileClass);
/* Базовое оформление */
.menu {
display: flex; /* горизонтальное меню */
}
/* Мобильная версия */
.is-mobile .menu {
flex-direction: column; /* на мобильном меню вертикальное */
}
Теперь шаблон может реагировать на ширину окна, но логику вы контролируете из JavaScript.
Практические подходы к организации dynamic-styles
Когда приложение растет, простой el.style или «добавим еще один класс» быстро превращается в хаос. Давайте посмотрим, как систематизировать подход.
Принцип: состояние → класс → стиль
Удобно раскладывать задачу на три уровня:
- Есть состояние (active, error, loading, disabled).
- У этого состояния есть класс (
button-error,field-loading). - У класса есть стили в CSS.
Тогда любая динамика – это преобразование состояния в класс.
Пример: поле ввода с ошибкой.
<input id="email" class="input" type="email" />
<div id="emailError" class="error-message hidden">Некорректный email</div>
.input {
border: 1px solid #ccc; /* базовая рамка */
padding: 6px 10px; /* отступы */
border-radius: 4px; /* скругленные углы */
outline: none; /* убираем стандартный outline */
}
.input-error {
border-color: #dc3545; /* красная рамка для ошибки */
}
.error-message {
color: #dc3545; /* красный текст ошибки */
font-size: 12px; /* размер шрифта */
margin-top: 4px; /* отступ сверху */
}
.hidden {
display: none; /* скрываем элемент */
}
const emailInput = document.getElementById('email');
const emailError = document.getElementById('emailError');
function validateEmail() {
// Здесь мы берем текущий текст поля ввода
const value = emailInput.value;
// Здесь простая проверка на наличие символа @
const isValid = value.includes('@');
if (!isValid) {
// Здесь мы добавляем класс ошибки к полю
emailInput.classList.add('input-error');
// Здесь показываем текст ошибки
emailError.classList.remove('hidden');
} else {
// Здесь убираем оформление ошибки
emailInput.classList.remove('input-error');
// Здесь скрываем текст ошибки
emailError.classList.add('hidden');
}
}
emailInput.addEventListener('blur', validateEmail);
Логика в JS просто выбирает, добавлять или убирать классы, а как выглядят эти классы – дело CSS.
Избегаем «магических чисел» в стилях
Если вы жестко прописали значения прямо в JS (el.style.marginLeft = '13px'), через полгода никто не поймет, откуда взялась цифра 13. Лучше:
- вынести значение в CSS-переменную
- или задать классы с понятными названиями (
card-offset-small,card-offset-large)
.card-offset-small {
margin-left: 8px; /* небольшой отступ слева */
}
.card-offset-large {
margin-left: 24px; /* большой отступ слева */
}
function applyOffset(el, size) {
// Здесь мы сначала удаляем оба класса
el.classList.remove('card-offset-small', 'card-offset-large');
// Здесь в зависимости от размера добавляем нужный
if (size === 'small') {
el.classList.add('card-offset-small');
} else if (size === 'large') {
el.classList.add('card-offset-large');
}
}
Так код лучше читается и легче поддерживается.
Как не превратить dynamic-styles в анти-паттерн
Коротко по наиболее частым ошибкам:
Смешивание логики и дизайна
В JS хранится информация видаif (theme === 'dark') bg = '#121212'. Лучше переложить цвета в CSS.Слишком много инлайн-стилей
Если вы задаете больше 2–3 свойств через.style, вероятно, лучше ввести класс.Разные имена для одного и того же состояния
В одном местеis-active, в другомactive. Старайтесь договориться о единых правилах именования.Отсутствие централизованной темы
Цвета и отступы должны жить в одном месте (tokens, CSS-переменные, theme-объекты), а не быть раскиданы по файлам.
Производительность и dynamic-styles
Динамические стили могут влиять на производительность, особенно если они меняются часто и на большом количестве элементов.
Что затратно для браузера
При изменении некоторых CSS-свойств браузеру приходится:
- пересчитывать layout (размещение элементов на странице)
- заново рисовать часть или всю страницу
Чаще всего это происходит при изменении:
- размеров (width, height)
- отступов (margin, padding)
- положения (top, left и т.п., если layout завязан на них)
- шрифта (font-size и т.п.)
Изменение цветов и прозрачности, как правило, дешевле.
Рекомендации
- Для анимаций используйте transform и opacity, а не top/left
- Старайтесь не трогать стиль в цикле по большому числу элементов; лучше менять класс на контейнере
- Избегайте частых изменений стилей в обработчиках scroll/resize без троттлинга/дебаунса
- При необходимости группируйте изменения (например, меняйте классы, а не отдельные свойства по одному)
Пример: вместо инлайн-изменения нескольких свойств задайте заранее класс «expanded».
.card {
height: 100px; /* исходная высота */
overflow: hidden; /* лишний текст скрываем */
transition: height 0.3s; /* плавное изменение высоты */
}
.card-expanded {
height: 300px; /* увеличенная высота */
}
function toggleCard(cardEl) {
// Здесь мы просто переключаем класс для элемента
cardEl.classList.toggle('card-expanded');
}
Заключение
Dynamic-styles – это не один конкретный прием, а целое семейство подходов:
- управление классами через JavaScript
- использование инлайн-стилей для вычисляемых значений
- применение CSS-переменных для темизации и настройки
- динамика в компонентах фреймворков (React, Vue и других)
- CSS-in-JS и утилитарные фреймворки, где стили завязаны на пропсы и состояние
Ключевая идея – разделять роли:
- CSS описывает, КАК выглядит состояние
- JavaScript описывает, КОГДА и КАКОЕ состояние наступает
- переменные и темы связывают всё это в единую систему
Когда вы придерживаетесь этого разделения, стили остаются управляемыми даже при высокой динамике интерфейса. Попробуйте начать с простого контроля классов и CSS-переменных, а затем постепенно добавляйте более сложные техники динамических стилей по мере необходимости.
Частозадаваемые технические вопросы по теме dynamic-styles
Как динамически подключать и отключать целые CSS-файлы (например, для разных тем)
- Добавьте в
<head>тег<link>с id.
<link id="themeStyles" rel="stylesheet" href="light.css" />
- В JS меняйте атрибут
href.
// Здесь мы находим link по id
const themeLink = document.getElementById('themeStyles');
// Здесь мы переключаем файл стилей на темную тему
themeLink.href = 'dark.css';
Так вы полностью меняете набор стилей без инлайн-кода. Следите, чтобы в двух файлах были одинаковые селекторы, но разные значения.
Как динамически генерировать и добавлять CSS-правила из JavaScript
- Создайте элемент
<style>и добавьте в документ.
// Здесь мы создаем новый тег style
const styleEl = document.createElement('style');
// Здесь добавляем его в head
document.head.appendChild(styleEl);
- Используйте свойство
sheet.insertRule.
// Здесь мы добавляем новое CSS-правило в созданный стиль
styleEl.sheet.insertRule('.dynamic-box { border: 2px solid red; }', 0);
Так можно программно создавать целые наборы стилей, например, для данных, пришедших с сервера.
Как безопасно комбинировать dynamic-styles и SSR (рендеринг на сервере)
- Старайтесь, чтобы критические стили были доступны без JS (через статические классы и CSS).
- Динамику, завязанную на пользовательские действия, применяйте уже после гидратации.
- Если состояние известно на сервере (например, тема), отдавайте сразу нужный класс на
<body>, а не переключайте его на клиенте.
Это уменьшит «мигание стилей» при загрузке.
Как синхронизировать динамическую тему с системной (prefers-color-scheme)
- В CSS задайте базовые правила для темной схемы.
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #f0f0f0;
}
}
- В JS прочитайте предпочтение и установите класс темы.
// Здесь мы используем matchMedia для определения системной темы
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) {
// Здесь мы включаем темную тему по умолчанию
document.body.classList.add('theme-dark');
}
Дальше пользователь может переключать тему вручную, а вы сохраняете выбор в localStorage.
Как тестировать dynamic-styles автоматически
- Для unit-тестов компонентов (React/Vue) проверяйте наличие классов и инлайн-стилей в DOM.
- Для e2e-тестов (Cypress, Playwright) проверяйте визуальное состояние через
should('have.css', 'property', 'value'). - Не полагайтесь только на скриншотные тесты, они плохо ловят мелкие изменения логики классов.
Так вы сможете уверенно рефакторить dynamic-styles, не боясь сломать визуальное поведение.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев