Манипуляция DOM
Манипуляция DOM — создание, вставка, удаление и изменение HTML-элементов и их содержимого через JavaScript API.
Зачем нужно
Динамические веб-приложения постоянно меняют содержимое страницы: показывают данные с сервера, реагируют на действия пользователя, строят интерфейс на лету. Все эти задачи решаются через манипуляцию DOM.
Где используется
- Отрисовка данных из API
- Создание динамических форм
- Модальные окна и уведомления
- Списки задач, чаты, комментарии
- Любой SPA-фреймворк внутри делает то же самое
Предпосылки
DOM дерево, Поиск элементов, Навигация по DOM
Создание элементов
// createElement — создаёт элемент (ещё НЕ в DOM!)
const div = document.createElement('div');
div.className = 'card';
div.id = 'card-1';
div.textContent = 'Новая карточка';
// createTextNode — текстовый узел
const text = document.createTextNode('Просто текст');
// createDocumentFragment — лёгкий контейнер
// Не создаёт лишних оберток в DOM
const fragment = document.createDocumentFragment;
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Элемент ${i}`;
fragment.appendChild(li);
}
document.querySelector('ul').appendChild(fragment); // один reflow!
// cloneNode — клонирование
const clone = div.cloneNode(false); // без детей (shallow)
const deep = div.cloneNode(true); // с детьми (deep)
Вставка элементов
const parent = document.querySelector('.container');
const newEl = document.createElement('div');
newEl.textContent = 'Новый элемент';
// Современные методы (рекомендуются)
parent.append(newEl); // в конец (дети)
parent.prepend(newEl); // в начало (дети)
parent.before(newEl); // перед parent (сосед)
parent.after(newEl); // после parent (сосед)
// append/prepend принимают несколько аргументов и строки
parent.append('Текст', document.createElement('br'), 'Ещё текст');
// replaceWith — замена элемента
const old = document.querySelector('.old');
const replacement = document.createElement('div');
replacement.textContent = 'Новый';
old.replaceWith(replacement);
// Старые методы (для совместимости)
parent.appendChild(newEl); // в конец
parent.insertBefore(newEl, parent.firstChild); // перед элементом
parent.replaceChild(newEl, oldEl); // замена
// insertAdjacentHTML — вставка HTML-строки
const card = document.querySelector('.card');
card.insertAdjacentHTML('beforebegin', '<p>Перед card</p>'); // <!-- перед -->
card.insertAdjacentHTML('afterbegin', '<p>В начало card</p>'); // в начало
card.insertAdjacentHTML('beforeend', '<p>В конец card</p>'); // в конец
card.insertAdjacentHTML('afterend', '<p>После card</p>'); // <!-- после -->
// insertAdjacentElement / insertAdjacentText — аналоги
card.insertAdjacentElement('afterend', newEl);
card.insertAdjacentText('beforeend', 'Текст');
Удаление элементов
// Современный способ
const el = document.querySelector('.remove()-me');
el.remove();
// Старый способ (через родителя)
const parent = el.parentNode;
parent.removeChild(el);
// Очистка всех дочерних элементов
const container = document.querySelector('.container');
// Способ 1: innerHTML (быстро, но уничтожает обработчики)
container.innerHTML = '';
// Способ 2: replaceChildren (чисто)
container.replaceChildren;
// Способ 3: цикл (контроль)
while (container.firstChild) {
container.removeChild(container.firstChild);
}
Изменение содержимого
const el = document.querySelector('.content');
// textContent — безопасно (экранирует HTML)
el.textContent = 'Привет <b>мир</b>'; // теги отобразятся как текст
// innerHTML — парсит HTML (осторожно с XSS!)
el.innerHTML = 'Привет <b>мир</b>'; // <b> станет жирным
// outerHTML — заменяет сам элемент
el.outerHTML = '<section>Замена</section>'; // el больше не существует!
// Важно: переменная el всё ещё указывает на старый (отсоединённый) элемент
// innerText — учитывает CSS (скрытый текст не виден)
el.innerText = 'Видимый текст';
Работа с атрибутами
const img = document.querySelector('img');
// Чтение
img.getAttribute('src');
img.getAttribute('alt');
img.getAttribute('data-id');
// Установка
img.setAttribute('alt', 'Описание');
img.setAttribute('loading', 'lazy');
// Удаление
img.removeAttribute('title');
// Проверка
img.hasAttribute('alt'); // true
// Все атрибуты (NamedNodeMap)
for (const attr of img.attributes) {
console.log(`${attr.name} = ${attr.value}`);
}
// dataset — удобный доступ к data-*
// <div data-user-id="42" data-role="admin">
const div = document.querySelector('div');
div.dataset.userId; // "42" (data-user-id → camelCase)
div.dataset.role; // "admin"
div.dataset.newProp = 'x'; // создаёт data-new-prop="x"
delete div.dataset.role; // удаляет data-role
Работа с классами
const el = document.querySelector('.card');
// classList — DOMTokenList (рекомендуется)
el.classList.add('active'); // добавить
el.classList.add('highlight', 'big'); // несколько сразу
el.classList.remove('old'); // удалить
el.classList.toggle('visible'); // переключить
el.classList.toggle('dark', isDark); // force: true/false
el.classList.contains('active'); // проверить (boolean)
el.classList.replace('old', 'new'); // заменить
// className — строка (перезаписывает ВСЕ классы)
el.className = 'card active'; // задать
el.className += ' highlight'; // добавить (неудобно)
Работа со стилями
const el = document.querySelector('.box');
// Inline-стили (свойство style)
el.style.width = '200px';
el.style.backgroundColor = 'blue'; // camelCase!
el.style.fontSize = '16px';
el.style.cssText = 'width: 200px; color: red;'; // строка (перезаписывает)
// Удаление inline-стиля
el.style.width = ''; // сброс к CSS-значению
// Чтение вычисленного стиля (из CSS)
const computed = getComputedStyle(el);
console.log(computed.width); // "200px"
console.log(computed.fontSize); // "16px"
console.log(computed.display); // "block"
// getComputedStyle — только чтение!
Частые ошибки
1. innerHTML и XSS
// Опасно — пользовательский ввод может содержать скрипты
const userInput = '<img src=x onerror="alert(1)">';
el.innerHTML = userInput; // XSS-атака!
// Безопасно — textContent экранирует HTML
el.textContent = userInput; // покажет как текст
2. Перерисовка при каждом изменении (layout thrashing)
// Плохо — каждая запись вызывает reflow
const list = document.querySelector('ul');
for (let i = 0; i < 1000; i++) {
list.innerHTML += `<li>${i}</li>`; // перестраивает DOM 1000 раз!
}
// Хорошо — DocumentFragment или массив строк
const fragment = document.createDocumentFragment;
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = i;
fragment.appendChild(li);
}
list.appendChild(fragment); // один reflow
// Или через innerHTML одним разом
list.innerHTML = Array.from({ length: 1000 }, (_, i) => `<li>${i}</li>`).join('');
3. outerHTML не обновляет переменную
const el = document.querySelector('.old');
el.outerHTML = '<div class="new">Новый</div>';
console.log(el.className); // "old" — переменная ссылается на старый элемент!
// Нужно заново найти элемент
const newEl = document.querySelector('.new');
Практика
- Создай список из 10 элементов через
createElementиfragment - Реализуй функцию, которая оборачивает выбранный элемент в
<div class="wrapper"> - Построй таблицу из массива объектов
[{ name, age, city }] - Реализуй drag-and-drop перестановку элементов списка