Batch DOM updates
Batch DOM updates — техника группировки множества изменений DOM в одну операцию для минимизации reflow и repaint, критически влияющих на производительность страницы.
Зачем нужно
Каждое отдельное изменение DOM (особенно чтение/запись геометрических свойств вперемешку) вызывает принудительный reflow — браузер пересчитывает расположение всех элементов. При 100+ изменениях в цикле это приводит к «Layout Thrashing» и зависанию интерфейса. Группировка изменений в один батч позволяет браузеру выполнить один reflow вместо 100.
Где используется
- Рендер длинных списков: добавление сотен элементов в DOM
- Анимации через JavaScript: обновление позиций/размеров в requestAnimationFrame
- Таблицы с динамическими данными: пересчёт ширины столбцов
- Виртуализация: замена видимых строк при скролле
- Обновление множества стилей после сетевого запроса
Основной контент
Проблема: Layout Thrashing
// ПЛОХО: чтение и запись вперемешку — каждая пара вызывает reflow
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const height = box.offsetHeight; // чтение — браузер вынужден reflow
box.style.height = height * 2 + 'px'; // запись
// на следующей итерации offsetHeight снова вызовет reflow!
});
// ХОРОШО: сначала все чтения, потом все записи
const heights = Array.from(boxes).map(box => box.offsetHeight); // все чтения
boxes.forEach((box, i) => {
box.style.height = heights[i] * 2 + 'px'; // все записи
});
DocumentFragment для вставки множества элементов
const list = document.getElementById('list');
// ПЛОХО: каждый appendChild вызывает reflow
const items = Array.from({ length: 1000 }, (_, i) => i);
items.forEach(i => {
const li = document.createElement('li');
li.textContent = `Элемент ${i}`;
list.appendChild(li); // 1000 reflow!
});
// ХОРОШО: DocumentFragment — вставка за один раз
const fragment = document.createDocumentFragment;
items.forEach(i => {
const li = document.createElement('li');
li.textContent = `Элемент ${i}`;
fragment.appendChild(li); // без reflow
});
list.appendChild(fragment); // один reflow
innerHTML для массовой замены
// Для полной замены содержимого innerHTML эффективнее множества appendChild
function renderItems(container, items) {
// Строим HTML-строку, затем вставляем за раз
container.innerHTML = items
.map(item => `<li class="item" data-id="${item.id}">${item.name}</li>`)
.join('');
}
// Для частичных обновлений лучше сохранять ссылки на элементы
// и менять только textContent / className
requestAnimationFrame для анимаций
// Группируем все изменения стилей внутри rAF
function animateElements(elements, progress) {
// Читаем ВНЕ rAF, если нужно
requestAnimationFrame(() => {
// Пишем ВНУТРИ rAF — браузер объединит с рендером
elements.forEach((el, i) => {
el.style.transform = `translateX(${progress * i}px)`;
});
});
}
// FastDOM-паттерн: явное разделение reads и writes
const reads = ;
const writes = ;
function measure(fn) { reads.push(fn); }
function mutate(fn) { writes.push(fn); }
function flush() {
reads.forEach(fn => fn);
reads.length = 0;
writes.forEach(fn => fn);
writes.length = 0;
}
requestAnimationFrame(flush);
classList вместо style
// ПЛОХО: несколько style-изменений
el.style.color = 'red';
el.style.fontWeight = 'bold';
el.style.fontSize = '16px';
// ХОРОШО: один toggle класса — одно изменение
el.classList.add('highlighted'); // все стили в CSS
// или если несколько классов
el.classList.remove('hidden', 'inactive');
el.classList.add('visible', 'active');
Частые ошибки
- Чтение offsetHeight/width внутри цикла записи: это самая распространённая причина Layout Thrashing — разносите чтения и записи.
- Модификация DOM вне rAF в анимациях: изменения стилей вне
requestAnimationFrameмогут вызвать лишние перерисовки или вообще пропустить кадр. - innerHTML для частичных обновлений:
innerHTML = ...сбрасывает все обработчики событий на дочерних элементах — используйте только для полной замены. - Не использовать DocumentFragment: при вставке 50+ элементов отсутствие Fragment заметно влияет на производительность.