DOM Performance & Batch Updates
DOM operations are expensive — each modification can trigger layout recalculation (reflow) and repaint. Batch updates, minimize DOM access, and use efficient patterns to keep your UI fast.
DOM Optimization
- Minimize DOM access — Cache references: const el = document.querySelector('#id'). Don't query repeatedly
- Batch updates — Use DocumentFragment to add multiple elements at once (1 reflow instead of many)
- Reflow triggers — Changes to width, height, position, display. Reading offsetHeight forces immediate reflow
- innerHTML vs createElement — innerHTML is fine for single update. createElement for granular control
- Virtual scrolling — Only render visible items in long lists (100 visible out of 10,000)
- requestAnimationFrame — Schedule visual updates to match display refresh rate (60fps)
DOM Performance Code
// ❌ BAD: Multiple DOM updates (N reflows)
function badRender(items) {
const list = document.createElement("ul");
items.forEach(item => {
const li = document.createElement("li");
li.textContent = item;
// list.appendChild(li); // Each append could trigger reflow
});
return list;
}
// ✅ GOOD: Batch with DocumentFragment (1 reflow)
function goodRender(items) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement("li");
li.textContent = item;
fragment.appendChild(li); // No reflow — fragment is in memory
});
// document.querySelector("ul").appendChild(fragment); // 1 reflow!
return fragment;
}
// ❌ BAD: Reading layout in a loop (layout thrashing)
function badResize(elements) {
elements.forEach(el => {
// const height = el.offsetHeight; // forces reflow!
// el.style.height = height + 10 + "px"; // causes another reflow
});
}
// ✅ GOOD: Read all, then write all
function goodResize(elements) {
// Read phase
const heights = elements.map(el => el.offsetHeight); // one reflow
// Write phase
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + "px";
});
// Reflow happens once after all writes
}
// Performance measurement
const items = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);
console.time("Fragment render");
goodRender(items);
console.timeEnd("Fragment render");
// requestAnimationFrame for smooth animations
function smoothScroll() {
let position = 0;
function step() {
position += 2;
console.log("Position:", position);
if (position < 100) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
// smoothScroll();
console.log("DOM performance patterns demonstrated");Tip
Tip
Batch DOM reads first, then writes. Reading offsetHeight triggers layout. If you read, write, read, write — that's 2 layouts instead of 1. Read all values first, then make all changes.
Measure first! Code split, tree shake.
Common Mistake
Warning
Using innerHTML in a loop. Each assignment parses HTML and recreates the DOM subtree. Build the complete HTML string first, then set innerHTML once. Or use DocumentFragment for even better performance.
Practice Task
Note
DOM optimization: (1) Refactor a loop that reads and writes DOM alternately to batch reads and writes. (2) Replace individual appendChild calls with DocumentFragment. (3) Measure the performance difference.
Quick Quiz
Key Takeaways
- DOM operations are expensive — each modification can trigger layout recalculation (reflow) and repaint.
- Minimize DOM access — Cache references: const el = document.querySelector('#id'). Don't query repeatedly
- Batch updates — Use DocumentFragment to add multiple elements at once (1 reflow instead of many)
- Reflow triggers — Changes to width, height, position, display. Reading offsetHeight forces immediate reflow