Avoid layout thrashing
What is Layout Thrashing?
Layout thrashing refers to a performance issue caused by frequent reflows in the browser. When JavaScript repeatedly reads and writes DOM styles or geometric properties, the browser is forced to recalculate the layout multiple times, resulting in page rendering lag. This phenomenon is particularly common when dynamically manipulating the DOM, such as continuously modifying element dimensions or positions in a loop.
// Typical thrashing example: Alternating read/write of layout properties in a loop
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const width = box.offsetWidth; // Triggers reflow
box.style.width = width + 10 + 'px'; // Triggers another reflow
});
Browser Rendering Mechanism Principles
Modern browsers use a pipeline-style rendering process:
- JavaScript: Executes script logic
- Style: Computes style rules
- Layout: Calculates element geometry
- Paint: Generates paint commands
- Composite: Composites layers
When JavaScript reads layout properties like offsetTop
or scrollHeight
, the browser forces a synchronous execution of the Layout phase to ensure data accuracy. This forced synchronization is called "Forced Synchronous Layout."
Common Trigger Scenarios
Batch DOM Operations
Unoptimized batch insert operations can cause N reflows:
const list = document.getElementById('list');
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
list.appendChild(item); // Triggers reflow on each addition
}
Interleaved Read-Write Patterns
A classic anti-pattern is modifying properties immediately after reading them:
// Get height → Modify height → Get width → Modify width...
const block = document.querySelector('.block');
function resizeBlock() {
block.style.height = block.offsetHeight + 10 + 'px';
block.style.width = block.offsetWidth + 5 + 'px';
}
Improper Animation Frame Handling
Mixing read and write operations in requestAnimationFrame
:
function animate() {
elements.forEach(el => {
const current = el.offsetLeft;
el.style.left = current + 1 + 'px';
});
requestAnimationFrame(animate);
}
Optimization Strategies and Practices
Batch DOM Writes
Use document fragments to reduce reflow count:
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}
document.getElementById('list').appendChild(fragment);
Read-Write Separation Principle
Execute all read operations first, then perform writes uniformly:
// Read all required data first
const boxes = document.querySelectorAll('.box');
const dimensions = Array.from(boxes).map(box => ({
width: box.offsetWidth,
height: box.offsetHeight
}));
// Perform writes uniformly
boxes.forEach((box, i) => {
box.style.width = dimensions[i].width + 10 + 'px';
box.style.height = dimensions[i].height + 5 + 'px';
});
Using the FastDOM Library
FastDOM automates read-write batching through task scheduling:
fastdom.measure(() => {
const width = element.offsetWidth;
fastdom.mutate(() => {
element.style.width = width + 10 + 'px';
});
});
CSS Animations Over JS Animations
Prefer transform
and opacity
properties:
.animated {
transition: transform 0.3s ease;
transform: translateX(0);
}
.animated.move {
transform: translateX(100px);
}
Advanced Optimization Techniques
Virtual DOM Technology
Frameworks like React/Vue implement batch updates via virtual DOM diffing:
function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
Offline DOM Operations
Temporarily remove elements from the document flow:
const element = document.getElementById('dynamic');
element.style.display = 'none';
// Perform multiple modifications
element.style.width = '200px';
element.style.height = '300px';
element.style.backgroundColor = 'red';
element.style.display = 'block';
Using will-change
to Hint the Browser
Notify the browser of potential changes in advance:
.optimized {
will-change: transform, opacity;
}
Detection and Debugging Methods
Chrome DevTools Performance Panel
- Record page interactions
- Analyze purple "Layout" events in the timeline
- Check cumulative layout time and trigger count
Layout Shift Console Warnings
Enable "Layout Shift Regions" visualization in DevTools:
// Console input
PerformanceObserver.observe({type: 'layout-shift'});
Forced Synchronous Layout Detection
Add debug code to catch synchronous layouts:
const original = HTMLElement.prototype.getBoundingClientRect;
HTMLElement.prototype.getBoundingClientRect = function() {
console.trace('Forced synchronous layout');
return original.apply(this, arguments);
};
Real-World Case Studies
Infinite Scroll List Optimization
Faulty implementation:
window.addEventListener('scroll', () => {
if (isNearBottom()) {
loadMoreItems(); // Directly manipulates DOM internally
}
});
Optimized solution:
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking && isNearBottom()) {
requestAnimationFrame(() => {
loadMoreItems();
ticking = false;
});
ticking = true;
}
});
Dynamic Form Validation
Original version:
inputs.forEach(input => {
input.addEventListener('blur', () => {
const isValid = validate(input.value);
input.style.borderColor = isValid ? 'green' : 'red';
errorLabel.style.display = isValid ? 'none' : 'block';
});
});
Optimized version:
function validateAll() {
const changes = [];
inputs.forEach(input => {
const isValid = validate(input.value);
changes.push(() => {
input.style.borderColor = isValid ? 'green' : 'red';
errorLabel.style.display = isValid ? 'none' : 'block';
});
});
requestAnimationFrame(() => changes.forEach(fn => fn()));
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:CSS选择器性能优化
下一篇:减少重绘与回流的技术