DOM Architecture: Nodes, Rendering Pipeline & Reflow
JS akela kya karega? Screen pe dikhane ke liye DOM chahiye. Pipeline samjho — Reflow = mahanga, Composite = sasta.
The DOM is a tree-structured representation of your HTML. The browser parses your HTML, builds a DOM tree, and JavaScript reads or modifies it — then the browser re-renders the screen.
Every HTML tag becomes an Element node, every piece of text becomes a Text node, even comments become Comment nodes.
Node types: Document (9), Element (1), Text (3), Comment (8), DocumentFragment (11), DocumentType (10).
Node vs Element: Node is generic — it includes text, comments, everything. Element is a specific Node with a tag name, attributes, and children.
Text nodes are real nodes — whitespace between tags creates text nodes!
// The DOM tree for this HTML:
// <p>Hello <strong>world</strong></p>
//
// <p> (Element node)
// ├── "Hello " (Text node — note the space!)
// ├── <strong> (Element node)
// │ └── "world" (Text node)
// └── "" (Text node — possible trailing whitespace)
// Check node types
const p = document.querySelector("p");
p.childNodes.forEach(node => {
console.log(node.nodeType); // 3 for text, 1 for element
console.log(node.nodeName); // "#text" or "STRONG"
});
getElementsByClassName returns a LIVE HTMLCollection — it auto-updates when the DOM changes. querySelectorAll returns a STATIC NodeList — it's a snapshot that doesn't update.
Live collection danger: iterating and removing elements skips every other element because the collection shrinks!
NodeList from querySelectorAll has .forEach(). HTMLCollection does NOT have .forEach().
Always convert live collections to arrays before modifying: [...document.getElementsByClassName("item")]
// ❌ DANGER: Iterating live collection while modifying
const items = document.getElementsByClassName("item");
// Suppose there are 5 elements: [0,1,2,3,4]
items[0].remove(); // Collection is now [1,2,3,4] (shifted!)
// What was items[1] is now items[0]
// Your loop index moves to 1, but items[1] is now the ORIGINAL items[2]
// Result: you SKIP the original items[1]!
// ✅ FIX 1: Iterate backwards
for (let i = items.length - 1; i >= 0; i--) {
items[i].remove(); // Safe! Removing from end doesn't shift
}
// ✅ FIX 2: Convert to static array first
const staticItems = [...document.getElementsByClassName("item")];
staticItems.forEach(item => item.remove()); // Safe!
// ✅ FIX 3: Use querySelectorAll (always static)
document.querySelectorAll(".item").forEach(item => item.remove());
DocumentFragment is a lightweight container — NOT part of the live DOM tree. Append children to the fragment, then append the fragment to the DOM — single reflow instead of N reflows.
When you append a fragment to the DOM, the fragment's children are moved — the fragment itself is NOT inserted.
This is the MOST important performance pattern for bulk DOM operations.
DocumentFragment is also used as a "build zone" — construct complex UI offline, then attach once.
// ❌ SLOW: 100 individual appends = up to 100 reflows
const list = document.getElementById("list");
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = "Item " + i;
list.appendChild(li); // Each append MAY trigger reflow!
}
// ✅ FAST: Build in fragment, single append = 1 reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = "Item " + i;
fragment.appendChild(li); // No reflow — fragment isn't in DOM!
}
list.appendChild(fragment); // ONE reflow for 100 elements!
// Modern alternative: <template> element
const template = document.createElement("template");
template.innerHTML = "<li>Item 1</li><li>Item 2</li>";
list.appendChild(template.content); // template.content IS a DocumentFragment!
When JS modifies the DOM, the browser runs a 4-step pipeline to update the screen.
Step 1 — Style Calculation: Figure out which CSS rules apply to each element.
Step 2 — Layout (Reflow): Calculate geometry — position, size, margins for every affected element.
Step 3 — Paint: Fill pixels — colors, text, images, borders, shadows.
Step 4 — Composite: Combine layers — stack painted layers in correct order, apply transforms.
The pipeline is not always 4 steps — some changes skip steps.
Reading layout properties (offsetWidth, getBoundingClientRect) FORCES the browser to complete layout immediately (forced reflow).
// ❌ FORCED REFLOW: Reading layout property after DOM change
const box = document.getElementById("box");
box.style.width = "100px"; // Marks layout as dirty
console.log(box.offsetWidth); // FORCES layout calculation NOW!
box.style.height = "200px"; // Marks layout as dirty AGAIN
console.log(box.offsetHeight); // FORCES layout AGAIN!
// Two layout calculations instead of one. Expensive!
// ✅ BATCH READS, THEN BATCH WRITES
const box2 = document.getElementById("box2");
const width = box2.offsetWidth; // Read 1
const height = box2.offsetHeight; // Read 2
box2.style.width = width + 10 + "px"; // Write 1
box2.style.height = height + 20 + "px"; // Write 2
// Only ONE layout calculation. Fast!Reflow (Layout) = MOST expensive. Triggers: changing width, height, margin, padding, display, position, font-size, adding/removing elements.
Repaint = Moderate cost. Triggers: changing color, background, visibility, box-shadow, border-color. Does NOT change geometry.
Composite = CHEAPEST. Triggers: changing transform (translate, rotate, scale), opacity. GPU-accelerated, separate layer.
CSS property cheat sheet: Use transform instead of top/left. Use opacity instead of visibility:hidden. Use will-change to hint the browser.
requestAnimationFrame: Runs BEFORE the render step — modify DOM in rAF, changes painted in same frame.
// ❌ SLOW: Animating with top/left triggers reflow every frame
// .element {
// position: absolute;
// transition: top 0.3s, left 0.3s;
// }
// .element.moved {
// top: 100px; // REFLOW on every frame!
// left: 200px; // REFLOW on every frame!
// }
// ✅ FAST: Animating with transform triggers only composite
// .element {
// transition: transform 0.3s;
// will-change: transform; // Hint browser to create separate layer
// }
// .element.moved {
// transform: translate(200px, 100px); // COMPOSITE only! GPU!
// }
// ✅ FAST: Opacity animations also composite-only
// .element {
// transition: opacity 0.3s;
// will-change: opacity;
// }
// .element.hidden {
// opacity: 0; // COMPOSITE only! No repaint!
// }
transform and opacity. For smooth 60fps animations, ONLY animate these two properties. Everything else triggers layout or paint, which can cause frame drops.Lo kar liya — Key Points:
- ✅ The DOM is a tree of nodes — Element nodes, Text nodes, Comment nodes — each with a nodeType and nodeName
- ✅ Live HTMLCollections (getElementsBy*) auto-update when DOM changes; static NodeLists (querySelectorAll) are snapshots
- ✅ Iterating a live collection while modifying the DOM skips elements — convert to array or iterate backwards
- ✅ DocumentFragment allows building DOM structures offline and inserting in a single operation (one reflow)
- ✅ The rendering pipeline: Style → Layout → Paint → Composite — each step is progressively cheaper
- ✅ Reflow is most expensive (geometry change), Repaint is moderate (visual change), Composite is cheapest (transform/opacity only)
- ✅ Only animate transform and opacity for 60fps — everything else triggers layout or paint
- ✅ Reading layout properties (offsetWidth, getBoundingClientRect) forces immediate reflow — batch reads before writes
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login