В Telegram Web при получении нового сообщения кнопка «Прокрутить вниз» появляется с анимацией — JavaScript добавляет CSS-класс. В Figma при выборе объекта панель свойств подсвечивается. В интернет-магазинах кнопка «В корзину» меняет вид при добавлении товара. Всё это — управление классами и стилями через JavaScript.
Статичный HTML не может реагировать на действия пользователя. JavaScript нужен способ динамически менять внешний вид элементов: добавлять/убирать классы, менять инлайновые стили, переключать темы.
Свойство classList предоставляет удобный API для управления CSS-классами:
element.classList.add('active') // добавить класс
element.classList.remove('active') // убрать класс
element.classList.toggle('active') // переключить
element.classList.toggle('open', true) // принудительно добавить (force=true)
element.classList.toggle('open', false) // принудительно убрать (force=false)
element.classList.contains('active') // true/false
element.classList.replace('btn-old', 'btn-new') // заменить// className — строка, перезаписывает все классы
element.className = 'btn btn-primary' // заменяет ВСЕ классы
// classList — точечное управление (предпочтительно)
element.classList.add('btn-primary') // добавляет к существующимДля значений, которые нельзя задать CSS-классами (динамические координаты, цвета из API):
element.style.color = 'red'
element.style.backgroundColor = 'blue' // camelCase вместо kebab-case
element.style.fontSize = '16px' // значение — строка с единицей!
element.style.transform = 'translateX(100px)'
element.style.display = 'none' // скрыть элемент
element.style.display = '' // вернуть к CSS-значению (сбросить инлайн)element.style показывает только инлайновые стили. Чтобы получить финальное значение (с учётом CSS-файлов и браузерных стилей) используй getComputedStyle:
const styles = getComputedStyle(element)
styles.color // 'rgb(255, 0, 0)'
styles.fontSize // '16px'
styles.display // 'block'data-* атрибуты хранят данные прямо в HTML-элементах:
// HTML: <button data-product-id="42" data-action="add-to-cart">Купить</button>
btn.dataset.productId // '42' — всегда строка!
btn.dataset.action // 'add-to-cart'
btn.dataset.newProp = 'value' // добавит data-new-prop// CSS: .modal { opacity: 0; transition: opacity 0.3s }
// .modal.visible { opacity: 1; pointer-events: auto }
// JS: только переключает класс, не трогает стили
function showModal(modal) { modal.classList.add('visible') }
function hideModal(modal) { modal.classList.remove('visible') }Это лучше чем менять стили напрямую: CSS отвечает за оформление, JS — за логику состояний.
Ошибка 1: className затирает все существующие классы
// Элемент имеет: class="btn btn-large"
element.className = 'active' // Теперь только 'active' — потеряли btn и btn-large!
// Правильно
element.classList.add('active') // Добавляет к существующим классамОшибка 2: забыли единицу в style
element.style.width = 100 // не работает! нет 'px'
element.style.width = '100px' // правильно
element.style.fontSize = 16 // не работает
element.style.fontSize = '16px' // правильноОшибка 3: читают style вместо getComputedStyle
// Если стиль задан через CSS-файл, а не инлайн:
element.style.color // '' — пустая строка! не видит CSS-файлы
// Правильно для чтения итогового значения
getComputedStyle(element).color // 'rgb(33, 150, 243)'dark на body или через CSS-переменныеloading пока данные загружаютсяvisible с CSS-анимациейСимуляция classList API через Set и работа с dataset
// Симуляция element.classList через Set
function createMockElement(tag, initialClasses) {
if (initialClasses === undefined) initialClasses = []
const classSet = new Set(initialClasses)
const dataset = {}
return {
tag,
dataset,
classList: {
add(...classes) {
classes.forEach(c => classSet.add(c))
},
remove(...classes) {
classes.forEach(c => classSet.delete(c))
},
toggle(cls, force) {
if (force !== undefined) {
force ? classSet.add(cls) : classSet.delete(cls)
} else {
classSet.has(cls) ? classSet.delete(cls) : classSet.add(cls)
}
return classSet.has(cls)
},
contains(cls) { return classSet.has(cls) },
replace(oldCls, newCls) {
if (classSet.has(oldCls)) {
classSet.delete(oldCls)
classSet.add(newCls)
return true
}
return false
},
get value() { return [...classSet].join(' ') }
}
}
}
// Кнопка добавления в корзину
const btn = createMockElement('button', ['btn', 'btn-primary'])
console.log(btn.classList.value) // 'btn btn-primary'
// Добавляем в корзину — меняем вид кнопки
btn.classList.replace('btn-primary', 'btn-success')
btn.classList.add('btn--added')
console.log(btn.classList.value) // 'btn btn-success btn--added'
console.log(btn.classList.contains('btn-success')) // true
// dataset — хранение данных в элементе
btn.dataset.productId = '42'
btn.dataset.quantity = '1'
console.log(btn.dataset.productId) // '42'
// Skeleton-экран: переключение состояния загрузки
const card = createMockElement('div', ['card'])
card.classList.add('loading')
console.log(card.classList.contains('loading')) // true
card.classList.remove('loading')
card.classList.add('loaded')
console.log(card.classList.value) // 'card loaded'
// toggle с force — принудительное состояние (открыть/закрыть меню)
const menu = createMockElement('nav', ['menu'])
menu.classList.toggle('open', true) // открыть
console.log(menu.classList.contains('open')) // true
menu.classList.toggle('open', false) // закрыть
console.log(menu.classList.contains('open')) // falseВ Telegram Web при получении нового сообщения кнопка «Прокрутить вниз» появляется с анимацией — JavaScript добавляет CSS-класс. В Figma при выборе объекта панель свойств подсвечивается. В интернет-магазинах кнопка «В корзину» меняет вид при добавлении товара. Всё это — управление классами и стилями через JavaScript.
Статичный HTML не может реагировать на действия пользователя. JavaScript нужен способ динамически менять внешний вид элементов: добавлять/убирать классы, менять инлайновые стили, переключать темы.
Свойство classList предоставляет удобный API для управления CSS-классами:
element.classList.add('active') // добавить класс
element.classList.remove('active') // убрать класс
element.classList.toggle('active') // переключить
element.classList.toggle('open', true) // принудительно добавить (force=true)
element.classList.toggle('open', false) // принудительно убрать (force=false)
element.classList.contains('active') // true/false
element.classList.replace('btn-old', 'btn-new') // заменить// className — строка, перезаписывает все классы
element.className = 'btn btn-primary' // заменяет ВСЕ классы
// classList — точечное управление (предпочтительно)
element.classList.add('btn-primary') // добавляет к существующимДля значений, которые нельзя задать CSS-классами (динамические координаты, цвета из API):
element.style.color = 'red'
element.style.backgroundColor = 'blue' // camelCase вместо kebab-case
element.style.fontSize = '16px' // значение — строка с единицей!
element.style.transform = 'translateX(100px)'
element.style.display = 'none' // скрыть элемент
element.style.display = '' // вернуть к CSS-значению (сбросить инлайн)element.style показывает только инлайновые стили. Чтобы получить финальное значение (с учётом CSS-файлов и браузерных стилей) используй getComputedStyle:
const styles = getComputedStyle(element)
styles.color // 'rgb(255, 0, 0)'
styles.fontSize // '16px'
styles.display // 'block'data-* атрибуты хранят данные прямо в HTML-элементах:
// HTML: <button data-product-id="42" data-action="add-to-cart">Купить</button>
btn.dataset.productId // '42' — всегда строка!
btn.dataset.action // 'add-to-cart'
btn.dataset.newProp = 'value' // добавит data-new-prop// CSS: .modal { opacity: 0; transition: opacity 0.3s }
// .modal.visible { opacity: 1; pointer-events: auto }
// JS: только переключает класс, не трогает стили
function showModal(modal) { modal.classList.add('visible') }
function hideModal(modal) { modal.classList.remove('visible') }Это лучше чем менять стили напрямую: CSS отвечает за оформление, JS — за логику состояний.
Ошибка 1: className затирает все существующие классы
// Элемент имеет: class="btn btn-large"
element.className = 'active' // Теперь только 'active' — потеряли btn и btn-large!
// Правильно
element.classList.add('active') // Добавляет к существующим классамОшибка 2: забыли единицу в style
element.style.width = 100 // не работает! нет 'px'
element.style.width = '100px' // правильно
element.style.fontSize = 16 // не работает
element.style.fontSize = '16px' // правильноОшибка 3: читают style вместо getComputedStyle
// Если стиль задан через CSS-файл, а не инлайн:
element.style.color // '' — пустая строка! не видит CSS-файлы
// Правильно для чтения итогового значения
getComputedStyle(element).color // 'rgb(33, 150, 243)'dark на body или через CSS-переменныеloading пока данные загружаютсяvisible с CSS-анимациейСимуляция classList API через Set и работа с dataset
// Симуляция element.classList через Set
function createMockElement(tag, initialClasses) {
if (initialClasses === undefined) initialClasses = []
const classSet = new Set(initialClasses)
const dataset = {}
return {
tag,
dataset,
classList: {
add(...classes) {
classes.forEach(c => classSet.add(c))
},
remove(...classes) {
classes.forEach(c => classSet.delete(c))
},
toggle(cls, force) {
if (force !== undefined) {
force ? classSet.add(cls) : classSet.delete(cls)
} else {
classSet.has(cls) ? classSet.delete(cls) : classSet.add(cls)
}
return classSet.has(cls)
},
contains(cls) { return classSet.has(cls) },
replace(oldCls, newCls) {
if (classSet.has(oldCls)) {
classSet.delete(oldCls)
classSet.add(newCls)
return true
}
return false
},
get value() { return [...classSet].join(' ') }
}
}
}
// Кнопка добавления в корзину
const btn = createMockElement('button', ['btn', 'btn-primary'])
console.log(btn.classList.value) // 'btn btn-primary'
// Добавляем в корзину — меняем вид кнопки
btn.classList.replace('btn-primary', 'btn-success')
btn.classList.add('btn--added')
console.log(btn.classList.value) // 'btn btn-success btn--added'
console.log(btn.classList.contains('btn-success')) // true
// dataset — хранение данных в элементе
btn.dataset.productId = '42'
btn.dataset.quantity = '1'
console.log(btn.dataset.productId) // '42'
// Skeleton-экран: переключение состояния загрузки
const card = createMockElement('div', ['card'])
card.classList.add('loading')
console.log(card.classList.contains('loading')) // true
card.classList.remove('loading')
card.classList.add('loaded')
console.log(card.classList.value) // 'card loaded'
// toggle с force — принудительное состояние (открыть/закрыть меню)
const menu = createMockElement('nav', ['menu'])
menu.classList.toggle('open', true) // открыть
console.log(menu.classList.contains('open')) // true
menu.classList.toggle('open', false) // закрыть
console.log(menu.classList.contains('open')) // falseНапиши функцию createClassList(initial) которая возвращает объект с методами classList API: add(...classes), remove(...classes), toggle(cls, force), contains(cls), entries() — возвращает массив всех классов, и геттер length. Реализация через Set.
classes — Set. add: newClasses.forEach(c => classes.add(c)). remove: removeClasses.forEach(c => classes.delete(c)). toggle без force: classes.has(cls) ? classes.delete(cls) : classes.add(cls); return classes.has(cls). entries(): return [...classes].