$ sudo teach IT
РАЗДЕЛ 9 · УРОК 4

Изменение стилей и классов

Два способа управлять внешним видом элементов через JavaScript: напрямую менять CSS-свойства через style, или управлять CSS-классами через classList. Разберём оба подхода и поймём, когда что использовать.

⏱ ~20 минут 🎨 Стили и классы 🎓 Для новичков

🖌️ element.style — прямое управление стилями

У каждого DOM-элемента есть свойство style. Через него можно задавать инлайновые CSS-свойства прямо из JavaScript — как если бы вы писали style="..." в HTML.

const box = document.querySelector('.box');

// Устанавливаем стили через style.СВОЙСТВО
box.style.backgroundColor = 'blue';  // background-color → backgroundColor
box.style.color = 'white';
box.style.fontSize = '20px';         // font-size → fontSize
box.style.padding = '16px 24px';
box.style.borderRadius = '8px';      // border-radius → borderRadius
box.style.display = 'none';          // скрыть элемент
box.style.display = '';              // вернуть исходное значение (пустая строка!)

⚠️ Важное правило — camelCase! CSS-свойства с дефисом пишутся в camelCase: background-color → backgroundColor, font-size → fontSize, border-top-left-radius → borderTopLeftRadius. Забыть про это — одна из самых частых ошибок новичков.

// Читаем инлайновые стили
const box = document.querySelector('.box');
box.style.color = 'red';
console.log(box.style.color); // "red"

// НО! style читает только инлайновые стили (заданные через style="...")
// Стили из CSS-файла через .style не читаются!
// Для этого нужен getComputedStyle:

const computed = getComputedStyle(box);
console.log(computed.backgroundColor); // читает реальный цвет (из CSS-файла тоже)
console.log(computed.fontSize);        // "16px" — итоговое значение

Можно задать несколько стилей сразу через cssText — но осторожно, это перезаписывает все существующие инлайновые стили:

// cssText — строка как в атрибуте style=""
box.style.cssText = 'background-color: blue; color: white; padding: 20px;';
// Все предыдущие инлайновые стили стёрты!

🤔 Когда прямые стили — плохая идея

Прямое изменение style — это быстро и просто, но в большинстве случаев лучше управлять классами, а не стилями напрямую. Почему?

❌ Плохо: стили в JavaScript
// Смешиваем логику и представление — сложно поддерживать
button.addEventListener('click', function() {
  box.style.backgroundColor = '#1e40af';
  box.style.color = '#ffffff';
  box.style.borderRadius = '12px';
  box.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
  box.style.transform = 'scale(1.05)';
  // Куча стилей в JS — ужас при командной работе
});
✅ Хорошо: классы в JS, стили в CSS
/* В CSS-файле: */
.box.active {
  background-color: #1e40af;
  color: #ffffff;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  transform: scale(1.05);
  transition: all 0.3s ease;
}

// В JavaScript — только логика:
button.addEventListener('click', function() {
  box.classList.toggle('active'); // одна строка!
});

💡 Правило разделения ответственности: CSS отвечает за то, как выглядит элемент в разных состояниях. JavaScript управляет тем, в каком состоянии находится элемент. Это делает код понятным, лёгким в поддержке и позволяет дизайнеру и программисту работать независимо.

Когда прямые стили всё-таки нужны: для динамических значений, которые вычисляются в рантайме — например, позиция элемента при перетаскивании, ширина прогресс-бара в процентах, цвет из пользовательского выбора.

// Динамические значения — тут прямые стили оправданы
const progressBar = document.querySelector('.progress');
progressBar.style.width = progress + '%'; // значение вычисляется динамически

const draggable = document.querySelector('.draggable');
draggable.style.left = mouseX + 'px'; // координаты при перетаскивании
draggable.style.top = mouseY + 'px';

🏷️ classList — правильный способ работы с классами

classList — это объект, который предоставляет удобные методы для управления CSS-классами элемента. Он решает все проблемы, которые возникают при ручном изменении строки className.

<div id="box" class="card featured">Контент</div>
const box = document.getElementById('box');

// Текущие классы: "card featured"
console.log(box.classList); // DOMTokenList ["card", "featured"]

// ADD — добавить класс (если нет — добавит, если уже есть — пропустит)
box.classList.add('active');
// Теперь: "card featured active"

box.classList.add('highlight', 'big'); // можно несколько сразу
// Теперь: "card featured active highlight big"

// REMOVE — убрать класс (если нет — ничего не произойдёт, без ошибки)
box.classList.remove('featured');
// Теперь: "card active highlight big"

box.classList.remove('highlight', 'big'); // несколько сразу

// TOGGLE — добавить если нет, убрать если есть (переключатель!)
box.classList.toggle('active');
// Было "active" → убрал → "card"
box.classList.toggle('active');
// Не было "active" → добавил → "card active"

// CONTAINS — проверить, есть ли класс (возвращает true/false)
console.log(box.classList.contains('card'));   // true
console.log(box.classList.contains('hidden')); // false

// REPLACE — заменить один класс другим
box.classList.replace('card', 'panel');
// "panel active" (card заменён на panel)

🆚 classList vs className — в чём разница

У элемента есть свойство className — это просто строка с классами. Его тоже можно читать и менять, но это неудобно и ведёт к ошибкам:

const box = document.getElementById('box');
// box.className = "card featured"

// ❌ ПЛОХО: ручная работа со строкой
// Чтобы добавить класс через className:
box.className += ' active'; // "card featured active" — работает

// Но чтобы убрать класс — нужно парсить строку руками!
box.className = box.className.replace(' active', ''); // "card featured" — опасно!
// А если 'active' стоит первым? А если два пробела? Куча граблей...

// Проверить наличие — тоже через строку
if (box.className.includes('active')) { ... } // ненадёжно!

// ✅ ХОРОШО: classList решает все эти проблемы
box.classList.add('active');    // элегантно
box.classList.remove('active'); // безопасно
box.classList.contains('active'); // надёжно

✅ Вывод: всегда используйте classList. className оставьте для чтения всего списка классов как строки — в остальном classList удобнее, безопаснее и понятнее.

🛠️ Практические примеры

Пример 1: Кнопка переключения тёмной темы

Классический пример: кнопка добавляет/убирает класс dark на body. В CSS прописаны стили для тёмной темы.

<!-- HTML -->
<button id="theme-btn">Переключить тему</button>
<p>Текст страницы</p>
/* CSS */
body.dark {
  background-color: #1a1a2e;
  color: #e0e0e0;
}

body.dark button {
  background: #404080;
  color: white;
}

/* Плавный переход */
body {
  transition: background-color 0.3s ease, color 0.3s ease;
}
// JavaScript
const btn = document.getElementById('theme-btn');

btn.addEventListener('click', function() {
  document.body.classList.toggle('dark');

  // Меняем текст кнопки в зависимости от состояния
  if (document.body.classList.contains('dark')) {
    btn.textContent = 'Светлая тема';
  } else {
    btn.textContent = 'Тёмная тема';
  }
});

Пример 2: Анимация появления элемента

Элемент изначально невидим, при клике плавно появляется. Стили — в CSS, логика — в JS:

/* CSS */
.message {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.message.visible {
  opacity: 1;
  transform: translateY(0);
}
// JavaScript
const showBtn = document.getElementById('show-btn');
const message = document.getElementById('message');

showBtn.addEventListener('click', function() {
  message.classList.add('visible');
});

Пример 3: Активный пункт меню

<nav>
  <a href="#" class="nav-link active">Главная</a>
  <a href="#" class="nav-link">О нас</a>
  <a href="#" class="nav-link">Контакты</a>
</nav>
const links = document.querySelectorAll('.nav-link');

links.forEach(function(link) {
  link.addEventListener('click', function(event) {
    event.preventDefault(); // отменяем переход по ссылке

    // Убираем 'active' у всех ссылок
    links.forEach(l => l.classList.remove('active'));

    // Добавляем 'active' только текущей
    link.classList.add('active');
  });
});

👁️ Скрытие и показ элементов

Часто нужно показать или скрыть элемент. Есть несколько способов:

Способ 1: display none/block (убирает из потока)
element.style.display = 'none';  // скрыть — занимаемое место пропадает
element.style.display = 'block'; // показать блочный
element.style.display = '';      // вернуть исходное значение из CSS
Способ 2: visibility hidden (место сохраняется)
element.style.visibility = 'hidden'; // скрыть, место остаётся
element.style.visibility = 'visible'; // показать
Способ 3: класс (лучший — с анимацией)
/* CSS */
.hidden { display: none; }

/* JavaScript */
element.classList.add('hidden');    // скрыть
element.classList.remove('hidden'); // показать
element.classList.toggle('hidden'); // переключить

✅ Итоги урока

  • element.style.property — задаёт инлайновый стиль. CSS-свойства пишутся в camelCase
  • Прямые стили для динамических значений (позиция, проценты). Для состояний — используйте классы
  • classList.add('class') — добавить класс
  • classList.remove('class') — убрать класс
  • classList.toggle('class') — добавить если нет, убрать если есть (идеально для переключателей)
  • classList.contains('class') — проверить наличие, возвращает true/false
  • classList.replace('old', 'new') — заменить один класс другим
  • classList лучше, чем ручная работа со строкой className
  • Стили в CSS, логика в JavaScript — принцип разделения ответственности

В следующем уроке разберём события — как страница реагирует на действия пользователя: клики, ввод, нажатия клавиш. ⚡

Изменение стилей и классов

8 вопросов

Переключение класса через classList

// Дан элемент modal и кнопка toggleBtn. // Напишите код, который: // 1. Добавляет обработчик 'click' на toggleBtn // 2. В обработчике переключает класс 'active' на modal // 3. Проверяет, есть ли класс 'active', и меняет текст кнопки // на 'Скрыть' (если активен) или 'Показать' (если не активен) const modal = null; const toggleBtn = null; // Ваш код здесь

Прямые стили для динамических значений

// Дан элемент progressBar и число percent (от 0 до 100). // Установите ширину progressBar равной percent процентам // через style.width. Значение должно быть в процентах. // Также установите backgroundColor: // - зелёный (#4CAF50) если percent > 70 // - оранжевый (#FF9800) если percent >= 30 и <= 70 // - красный (#F44336) если percent < 30 const progressBar = null; const percent = 0; // Ваш код здесь