Стили и classList

изменение≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

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

Разделы темы:·

Свойство style — это не «все стили элемента», а ровно тот текст, который лежит в его атрибуте style. Отсюда два следствия: имена пишутся в camelCase (background-color становится backgroundColor), а значение — всегда строка с единицами измерения. Число без px браузер молча выбросит.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Если цвет пришёл из CSS-файла, element.style.color вернёт пустую строку — в атрибуте ведь ничего нет. Итоговое, реально применённое значение показывает getComputedStyle(element): браузер прогоняет все правила, наследование и единицы и отдаёт результат в пикселях и rgb().

Объект от getComputedStyle — только для чтения, и попытка записи не проходит молча, а бросает исключение: объект описывает, что получилось, а не что вы хотите. Меняют по-прежнему style или класс. Чем вычисленный стиль отличается от заданного, почему длины приходят пикселями и что показывает скрытый элемент — в следующей статье.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

className — обычная строка, и добавить к ней класс, не сломав остальные, неудобно. classList — набор с понятными методами: add, remove, contains, toggle. add и remove принимают сколько угодно имён сразу, дубликаты не создаются, а удаление несуществующего класса не ошибка. А вот contains берёт ровно одно имя, и у toggle второй аргумент — не второе имя, а положение переключателя: toggle("open", "wide") не тронет wide, а намертво включит open.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

toggle отличается тем, что возвращает результат: true, если класс в итоге на месте. А второй аргумент превращает переключатель в выключатель с заданным положением: toggle("open", isOpen) добавит класс при true и снимет при false. Это надёжнее, чем if с двумя ветками, потому что состояние класса выводится из данных, а не накапливается вручную.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

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

ЗадачаstyleКласс
Покрасить один элемент по расчётуподходитна каждое значение нужен новый класс
Одинаково оформить сотню элементовцикл и сотня атрибутоводно правило в CSS
:hover, медиазапросы, анимациинедоступныработают
Вернуть как былопомнить прежнее значениеclassList.remove()
Найти, откуда взялся видискать по всему JSискать по CSS

Есть и пятый метод — replace: он меняет один класс на другой и возвращает false, если менять было нечего. Такой ответ — удобный признак того, что элемент оказался не в том состоянии, которое вы ожидали.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

У style есть свойство cssText — весь атрибут одной строкой. Присваивание в него выглядит компактно, но переписывает атрибут целиком, стирая всё, что там уже было, включая правила, которые поставил другой участок кода.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

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

Тёмная тема целой страницы — это один класс на корневом элементе. CSS заранее описывает оба варианта селекторами вроде .theme-dark .card, а JS остаётся одна строка. В настоящем проекте корень — это document.documentElement (тег <html>) и выбор темы обычно кладут в localStorage; в примере ниже роль корня играет обычный контейнер.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Второй приём того же семейства — CSS-переменные. Их нельзя записать как обычное свойство: style.someName знает только стандартные имена, а кастомные ставятся через setProperty и читаются через getPropertyValue. Одна переменная на корне перекрашивает всё, что на неё ссылается.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

classList.toggle("theme-dark") и root.dataset.theme = "dark" решают одну задачу. Класс короче и привычнее, зато у него только два положения: есть или нет. Data-атрибут держит значение — light, dark, sepia — и в CSS выбирается селектором [data-theme="dark"].

Практическое правило: два состояния — класс, три и больше — data-атрибут. Смешивать оба способа для одной сущности не стоит, иначе у темы окажется два источника правды и когда-нибудь они разойдутся.

  • element.style — это атрибут элемента, а не итоговые стили: camelCase в именах, строки с единицами в значениях, пустая строка удаляет свойство.
  • Реально применённое значение показывает getComputedStyle(element), и этот объект только для чтения.
  • classList даёт add, remove, contains, toggle и replace; toggle возвращает true, если класс остался, а второй аргумент задаёт положение явно.
  • Классы держат внешний вид в CSS: работают :hover, медиазапросы и анимации, а откат состояния — одна строка remove.
  • Тема страницы — класс или data-атрибут на корневом элементе; переменные меняются через style.setProperty("--accent", ...).

1/3Что окажется в el.style.fontSize после строки el.style.fontSize = 24;?

2/3Элемент не имеет класса open. Что вернёт el.classList.toggle("open") и что произойдёт с классом?

3/3У элемента фон задан правилом из CSS-файла. Что вернёт el.style.backgroundColor?

Переключатель темы, который не путается

Напишите функцию setTheme(el, theme): она держит на элементе класс theme-dark в соответствии с темой и записывает саму тему в data-theme. Функция должна выдерживать повторный вызов с той же темой — состояние не должно «моргать».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

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

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Вариант с if и парой add/remove тоже рабочий, но длиннее и оставляет шанс забыть одну ветку.

Класс empty у пустого поля

Подпишитесь на событие input и держите класс empty ровно тогда, когда поле пустое. Событие в примере вызывается кодом через dispatchEvent, ждать пользователя не нужно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Одна подписка и один toggle со вторым аргументом: класс всегда выводится из значения поля, а не накапливается вручную.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Обратите внимание, что цвет рамки здесь не задаётся из JS вовсе — за него отвечает правило .field.empty в CSS.