Групування змін DOM
Коротко: мета - менше "чіпати" DOM і layout, а якщо вже чіпати - робити це пакетно: спочатку всі читання (measure), потім усі записи (mutate), і не частіше одного кадру.
Практичні способи групування змін DOM
1) "Measure -> then Mutate" (розділяй читання і записи)
Читання (getBoundingClientRect, offsetHeight, scrollTop, getComputedStyle) змушують браузер синхронно перерахувати layout. Якщо чергувати читання і запис - отримаєш layout thrashing.
Правильно:
// Збираємо все читання геометрії…
const rects = items.map(el => el.getBoundingClientRect());
// …а потім однією пачкою застосовуємо записи (стилі/класи)
requestAnimationFrame(() => {
items.forEach((el, i) => {
el.style.transform = `translateY(${rects[i].top}px)`; // тільки запис
});
});2) Бетчинг через requestAnimationFrame
rAF коалесує зміни в один кадр (~16.6 мс при 60 FPS).
let pending = [];
function batchStyle(fn) {
pending.push(fn);
if (pending.length === 1) {
requestAnimationFrame(() => {
const jobs = pending; pending = [];
jobs.forEach(job => job());
});
}
}
// Використання
batchStyle(() => el.classList.add('active'));
batchStyle(() => el.style.opacity = '1');3) Будувати вузли "осторонь", а потім одним вставити
- DocumentFragment
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Row ${i}`;
frag.appendChild(li);
}
list.appendChild(frag); // один reflow замість 1000<template>:
const tpl = document.getElementById('row-tpl');
const frag = document.createDocumentFragment();
data.forEach(d => {
const node = tpl.content.cloneNode(true);
node.querySelector('.title').textContent = d.title;
frag.appendChild(node);
});
list.appendChild(frag);innerHTML(коли безпечно): зібрати рядок і вставити один раз.
4) Змінюй клас замість десятків інлайнових стилів
Один classList.add('expanded') дешевший, ніж 10 style.*.
el.classList.add('expanded'); // у CSS вже описані всі властивості5) "Сховати -> змінити -> показати"
Іноді вигідно виключити блок з потоку (зменшити вплив на layout), внести пачку правок, а потім повернути.
container.style.display = 'none';
/* багато DOM-операцій */
container.style.display = '';Використовуй акуратно (може скинути суб-виміри, фокуси тощо).
6) Анімуй тільки transform і opacity
Вони не викликають reflow, зазвичай лише repaint/composite (дешевше).
.card { will-change: transform, opacity; }7) CSS-контейнери та пропуски рендера
content-visibility: auto;- пропускає layout/paint невидимого контенту.contain: layout paint;- обмежує область перерахунку.
.section {
content-visibility: auto;
contain-intrinsic-size: 1000px; /* щоб не стрибав layout */
}8) Дебаунс/тротлінг подій
Скрол/резайз/інпути можуть тригерити сотні апдейтів на секунду - збирай їх у пачки.
const onScroll = throttle(() => {
requestAnimationFrame(updateStickyHeader);
}, 100);9) Віртуалізація великих списків
Рендери тільки видимі елементи (react-window, virtual-scroller, власний велосипед). Це радикально скорочує обсяг DOM -> менше layout/paint.
10) Мінімізуй "forced reflow"
Спочатку прочитай усе потрібне:
// погано: write -> read -> write -> read …
el.style.width = '400px';
el.offsetHeight; // forced reflow
el.style.height = '200px';
el.getBoundingClientRect(); // forced reflow
// добре: всі читання, потім усі записи
const h = el.offsetHeight;
const r = el.getBoundingClientRect();
requestAnimationFrame(() => {
el.style.width = '400px';
el.style.height = '200px';
});Міні-шаблон "measure / mutate" (готовий до копіпасти)
const reads = [];
const writes = [];
export function read(fn) {
reads.push(fn);
schedule();
}
export function write(fn) {
writes.push(fn);
schedule();
}
let scheduled = false;
function schedule() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
// 1) всі читання
let r; while ((r = reads.shift())) r();
// 2) всі записи
let w; while ((w = writes.shift())) w();
scheduled = false;
});
}Використання:
read(() => { boxRect = box.getBoundingClientRect(); });
write(() => { box.style.transform = `translateY(${boxRect.top}px)`; });Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.