阿里云主机折上折
  • 微信号
Current Site:Index > Avoid layout thrashing

Avoid layout thrashing

Author:Chuan Chen 阅读数:13917人阅读 分类: 性能优化

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:

  1. JavaScript: Executes script logic
  2. Style: Computes style rules
  3. Layout: Calculates element geometry
  4. Paint: Generates paint commands
  5. 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

  1. Record page interactions
  2. Analyze purple "Layout" events in the timeline
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.