How to optimize DOM manipulation in JavaScript
DOM manipulation is one of the slowest operations in web applications, causing layout recalculations and repaints that impact performance. As the creator of CoreUI with over 25 years of JavaScript experience since 2000, I’ve optimized DOM operations in complex UI frameworks to maintain 60fps interactions. The most effective approach batches DOM updates using DocumentFragment, minimizes layout thrashing by reading then writing, and caches DOM references. These techniques can improve rendering performance by orders of magnitude.
Use DocumentFragment to batch multiple DOM insertions.
// ❌ Slow - causes reflow on each append
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div')
div.textContent = `Item ${i}`
container.appendChild(div)
}
// ✅ Fast - single reflow
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div')
div.textContent = `Item ${i}`
fragment.appendChild(div)
}
container.appendChild(fragment)
The slow version triggers layout recalculation 1000 times. DocumentFragment accumulates changes in memory. A single appendChild adds all elements at once. This reduces reflows from 1000 to 1.
Avoiding Layout Thrashing
Read all measurements before making any changes.
// ❌ Layout thrashing - alternating read/write
elements.forEach(el => {
const height = el.offsetHeight // Read (forces layout)
el.style.height = height + 10 + 'px' // Write
})
// ✅ Batch reads, then writes
const heights = elements.map(el => el.offsetHeight)
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'
})
Reading layout properties like offsetHeight forces synchronous layout calculation. Batching reads together avoids multiple recalculations. First collect all data, then apply all changes.
Using innerHTML for Multiple Elements
Build HTML strings for large updates.
// Slow with createElement
const items = data.map(item => {
const div = document.createElement('div')
div.className = 'item'
div.textContent = item.name
return div
})
// Fast with innerHTML
const html = data.map(item => `<div class="item">${item.name}</div>`).join('')
container.innerHTML = html
The innerHTML approach is faster for creating many elements. The browser’s HTML parser is highly optimized. Use this for initial renders or complete replacements.
Caching DOM References
Store frequently accessed elements in variables.
// ❌ Multiple queries
document.getElementById('button').addEventListener('click', () => {
document.getElementById('output').textContent = 'Clicked'
document.getElementById('output').className = 'active'
})
// ✅ Cache reference
const output = document.getElementById('output')
document.getElementById('button').addEventListener('click', () => {
output.textContent = 'Clicked'
output.className = 'active'
})
DOM queries are expensive. Cache references for elements used multiple times. This applies especially inside loops or event handlers.
Using CSS Classes Instead of Inline Styles
Toggle classes rather than setting individual styles.
// Slow - multiple style changes
element.style.width = '100px'
element.style.height = '100px'
element.style.background = 'red'
element.style.border = '1px solid black'
// Fast - single class toggle
element.className = 'styled-element'
Each style change can trigger recalculation. CSS classes apply multiple styles atomically. Define styles in CSS and toggle classes in JavaScript.
Best Practice Note
This is the same DOM optimization approach we use in CoreUI components for smooth interactions with large datasets. Always profile with Chrome DevTools Performance tab before optimizing. Use virtual DOM libraries like React or Vue for complex UIs—they handle batching automatically. For simple cases, vanilla JS with these techniques is sufficient. Remember: premature optimization wastes time, but knowing these patterns helps when performance matters.



