阿里云主机折上折
  • 微信号
Current Site:Index > Techniques to reduce repaints and reflows

Techniques to reduce repaints and reflows

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

Understanding Repaint and Reflow

When a browser renders a page, it goes through steps such as parsing HTML to build the DOM tree, parsing CSS to build the CSSOM tree, merging them into a render tree, layout calculations, and pixel painting. When the appearance of a DOM element changes without affecting the layout (e.g., modifying the color), a repaint is triggered. When layout properties change (e.g., width, position), a reflow is triggered, causing the browser to recalculate the geometric properties of all affected elements. Reflow inevitably triggers a repaint, but repaint does not necessarily trigger reflow.

// Operations that trigger reflow
element.style.width = '200px'; 
// Operations that only trigger repaint
element.style.color = 'red';

Avoid Frequent Style Manipulations

Consecutive style modifications can cause the browser to reflow multiple times. The best practice is to combine multiple style changes into a single operation:

// Bad example - triggers multiple reflows
el.style.margin = '5px';
el.style.padding = '10px';
el.style.width = '100px';

// Correct approach - use cssText or class
el.style.cssText = 'margin:5px; padding:10px; width:100px';
// Or
el.classList.add('active-style');

Optimize DOM Operations with Document Fragments

Direct DOM manipulation triggers immediate reflow. DocumentFragment can serve as an in-memory container for DOM nodes:

const fragment = document.createDocumentFragment();
for(let i=0; i<100; i++){
  const item = document.createElement('div');
  item.textContent = `Item ${i}`;
  fragment.appendChild(item);
}
document.body.appendChild(fragment); // Only one reflow

Batch Read Layout Properties

Forced synchronous layout (Layout Thrashing) occurs when reading and writing layout properties alternately:

// Inefficient approach - causes multiple reflows
for(let i=0; i<boxes.length; i++) {
  boxes[i].style.width = boxes[i].offsetWidth + 10 + 'px';
}

// Optimized approach - read first, then write
const widths = boxes.map(box => box.offsetWidth);
boxes.forEach((box, i) => {
  box.style.width = widths[i] + 10 + 'px';
});

Use Transform and Opacity for Animations

CSS3 properties do not trigger layout calculations:

.animate {
  transform: translateX(100px); /* Uses GPU acceleration */
  opacity: 0.5; /* Optimizes composite layers */
  will-change: transform; /* Informs the browser in advance */
}

Optimize Stylesheet Structure

Selector matching occurs from right to left; avoid deep nesting:

/* Inefficient selector */
div ul li a span.highlight { ... }

/* Optimized selector */
.highlight { ... }

Use Virtual Lists for Long Lists

Only render elements within the visible area:

function renderVisibleItems(container, items, scrollTop) {
  const itemHeight = 50;
  const startIdx = Math.floor(scrollTop / itemHeight);
  const endIdx = startIdx + Math.ceil(container.clientHeight / itemHeight);
  
  container.innerHTML = '';
  for(let i=startIdx; i<=endIdx; i++) {
    const item = document.createElement('div');
    item.textContent = items[i] || '';
    container.appendChild(item);
  }
}

Separate Read and Write Operations

Leverage the browser's rendering queue mechanism:

// Triggers one reflow
element.style.display = 'none'; // Write
// Other operations can be inserted here
element.style.width = '100px';  // Write
element.style.display = 'block'; // Write

Use Flexbox/Grid Layouts

Modern layout methods are more efficient than traditional floats/positioning:

.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

Manage Event Listeners Properly

High-frequency events require throttling/debouncing:

function debounce(fn, delay) {
  let timer;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, arguments), delay);
  };
}

window.addEventListener('resize', debounce(handleResize, 100));

Use the content-visibility Property

Skip rendering off-screen elements:

.long-list-item {
  content-visibility: auto;
  contain-intrinsic-size: 100px 500px;
}

Optimize Image Loading

Use the correct size and format:

<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg"> 
  <img src="image.jpg" loading="lazy" decoding="async">
</picture>

Avoid CSS Expressions

Avoid dynamically calculated CSS values:

/* Avoid using */
width: expression(document.body.clientWidth > 800 ? "800px" : "auto");

Use requestAnimationFrame

Concentrate visual changes during the browser's repaint cycle:

function animate() {
  element.style.transform = `translateX(${position}px)`;
  position += 1;
  if(position < 100) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

Offline DOM Operations

For complex DOM manipulations, first remove them from the document flow:

const container = document.getElementById('container');
container.style.display = 'none'; // Triggers one reflow
// Perform extensive DOM operations
container.style.display = 'block'; // Triggers one reflow

Avoid Table Layouts

Tables delay rendering until all cells are ready:

<!-- Avoid -->
<table>
  <tr><td>Content</td></tr>
</table>

<!-- Recommended -->
<div class="grid-container">
  <div class="grid-item">Content</div>
</div>

Use CSS Containment

Limit the scope of repaints/reflows:

.widget {
  contain: layout paint style;
}

Optimize Font Loading

Avoid layout shifts and invisible text flashes:

@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  font-display: swap;
}

Reduce Shadow and Gradient Usage

Complex visual effects increase repaint costs:

/* Before optimization */
box-shadow: 0 0 10px rgba(0,0,0,0.5), inset 0 0 5px #fff;

/* After optimization */
box-shadow: 0 0 5px rgba(0,0,0,0.3);

Use Web Workers for Computations

Move time-consuming tasks off the main thread:

// main.js
const worker = new Worker('compute.js');
worker.postMessage(data);
worker.onmessage = (e) => updateUI(e.data);

// compute.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

Avoid Forced Synchronous Layout

Certain APIs force immediate layout calculations:

// Triggers forced layout
const width = element.offsetWidth; // Read
element.style.width = width + 10 + 'px'; // Write

Use CSS Variables to Reduce Style Changes

Batch update styles via variables:

:root {
  --theme-color: #4285f4;
}
.button {
  background: var(--theme-color);
}

// JavaScript only needs to modify one place
document.documentElement.style.setProperty('--theme-color', '#ea4335');

Optimize Third-Party Script Loading

Use async/defer attributes:

<script src="analytics.js" defer></script>
<script src="widget.js" async></script>

Use Intersection Observer

Replace scroll event listeners:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if(entry.isIntersecting) {
      entry.target.classList.add('visible');
    }
  });
});

document.querySelectorAll('.lazy-load').forEach(el => {
  observer.observe(el);
});

Avoid Inline Styles

External stylesheets are more conducive to browser optimization:

<!-- Avoid -->
<div style="width:100px; color:red;"></div>

<!-- Recommended -->
<style>
  .box { width:100px; color:red; }
</style>
<div class="box"></div>

Use CSS will-change

Inform the browser of potential changes in advance:

.animated-element {
  will-change: transform, opacity;
}

Optimize SVG Usage

Reduce SVG complexity and use correctly:

<svg width="100" height="100">
  <rect x="10" y="10" width="80" height="80" fill="#4285f4"/>
</svg>

Reduce Layer Count

Excessive composite layers increase memory consumption:

/* Avoid unnecessary hardware acceleration */
.element {
  transform: translateZ(0); /* Use with caution */
}

Use CSS Content Property

Reduce additional DOM nodes:

.icon-check:before {
  content: "✓";
  color: green;
}

Optimize Scroll Performance

Enable hardware-accelerated scrolling:

.container {
  overflow-y: scroll;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
}

Use Media Queries for On-Demand Loading

Load resources based on device features:

<link rel="stylesheet" href="mobile.css" media="(max-width: 600px)">

Avoid @import Rules

Block parallel downloads:

/* Avoid */
@import url('styles.css');

/* Recommended */
<link rel="stylesheet" href="styles.css">

Use CSS Counters

Replace JavaScript counters:

ol {
  counter-reset: section;
}
li::before {
  counter-increment: section;
  content: counters(section, ".") " ";
}

Optimize Form Elements

Reduce layout complexity for form controls:

<input type="range" class="custom-slider">

Use CSS aspect-ratio

Avoid layout jumps:

.video-container {
  aspect-ratio: 16/9;
}

Reduce Pseudo-Element Abuse

Complex pseudo-elements increase painting costs:

/* Use in moderation */
.button::after {
  content: "";
  position: absolute;
  /* Simple effects */
}

Use CSS revert

Reset to browser default styles:

.reset-styles {
  all: revert;
}

Optimize Web Font Usage

Choose appropriate font formats and subsets:

@font-face {
  font-family: 'Open Sans';
  src: url('OpenSans.woff2') format('woff2');
  unicode-range: U+000-5FF; /* Latin character subset */
}

Use CSS gap Property

Replace margins for spacing:

.grid {
  display: grid;
  gap: 20px;
}

Avoid Frequent className Toggling

Combine style changes:

// Inefficient
element.classList.add('active');
element.classList.remove('inactive');

// Optimized
element.className = 'element active';

Use CSS Scroll Snap

Achieve precise scroll positioning:

.container {
  scroll-snap-type: y mandatory;
}
.slide {
  scroll-snap-align: start;
}

Optimize Canvas Drawing

Reduce canvas state changes:

// Batch set states
ctx.fillStyle = 'red';
ctx.fillRect(10,10,50,50);
ctx.fillRect(70,10,50,50);

// Use paths for batch drawing
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(50,50);
ctx.lineTo(10,50);
ctx.fill();

Use CSS prefers-reduced-motion

Respect user motion preferences:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Optimize Shadow Effects

Use appropriate shadow parameters:

/* Before optimization */
box-shadow: 0 0 20px 10px rgba(0,0,0,0.5);

/* After optimization */
box-shadow: 0 2px 4px rgba(0,0,0,0.1);

Use CSS contain-intrinsic-size

Provide placeholder sizes for content-visibility:

.lazy-item {
  content-visibility: auto;
  contain-intrinsic-size: 300px 200px;
}

Avoid Unnecessary z-index

Complex stacking contexts increase calculation costs:

/* Avoid overuse */
.modal {
  z-index: 9999;
}

Use CSS line-clamp

Implement multi-line text truncation:

.ellipsis {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

Optimize Background Image Rendering

Avoid complex background patterns:

/* Before optimization */
background: linear-gradient(45deg, #000 25%, transparent 25%) -50px 0,
            linear-gradient(-45deg, #000 25%, transparent 25%) -50px 0;

/* After optimization */
background: #f5f5f5 url('simple-pattern.png');

Use CSS overscroll-behavior

Control scroll chaining behavior:

.modal-content {
  overscroll-behavior-y: contain;
}

Optimize CSS Variable Usage

Avoid frequent updates to CSS variables:

// Inefficient
element.style.setProperty('--x', x + 'px');
element.style.setProperty('--y', y + 'px');

// Optimized
element.style.cssText = `--x:${x}px; --y:${y}px`;

Use CSS revert-layer

Revert to styles from the previous layer:

@layer base {
  a { color: blue; }
}
@layer theme {
  a { color: red; }
  .special-link {
    color: revert-layer; /* Falls back to blue from base layer */
  }
}

Optimize Gradient Performance

Simplify gradient definitions:

/* Before optimization */
background: linear-gradient(to right, 
  #ff0000, #ff4000, #ff8000, #ffbf00, #ffff00);

/* After optimization */
background: linear-gradient(to right, #ff0000, #ffff00);

Use CSS accent-color

Quickly set form control theme colors:

input[type="checkbox"] {
  accent-color: #4285f4;
}

Optimize Filter Effects

Avoid complex filter chains:

/* Before optimization */
filter: blur(5px) brightness(1.2) contrast(0.8) saturate(1.5);

/* After optimization */
filter: brightness(1.1);

Use CSS :where() Selector

Reduce selector specificity:

/* High specificity */
article > .title { color: blue; }

/* Low specificity */
:where(article > .title) { color: blue; }

Optimize CSS Custom Properties

Organize variables logically:

:root {
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --color-primary: #4285f4;
}

.card {
  padding: var(--spacing-md);
  border: 1px solid var(--color-primary);
}

Use CSS :has() Selector

Reduce JavaScript dependency:

/* Add border when containing an image */
.card:has(img) {
  border: 1px solid #eee;
}

Optimize CSS Variable Calculations

Avoid complex calc() operations:

/* Before optimization */
--size: calc(var(--base-size) * 1.5 + 10px);

/* After optimization */
--size: 25px; /* Precomputed value */

Use CSS :is() Selector

Simplify selector groups:

/* Before simplification */
.header > h1,
.header > h2,
.header > h3 {
  margin: 0;
}

/* After simplification */
.header > :is(h1, h2, h3) {
  margin: 0;
}

Optimize CSS Media Query Order

Organize following mobile-first principles:

/* Base styles - mobile devices */
.container { padding: 10px; }

/* Medium screens */
@media (min-width: 768px) {
  .container { padding: 20px; }
}

/* Large screens */
@media (min-width: 1024px) {
  .container { padding: 30px; }
}

Use CSS :focus-visible

Improve keyboard navigation experience:

button:focus-visible {
  outline: 2px solid blue;
}

Optimize CSS Grid Layout

Define tracks explicitly for better performance:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-auto-rows: minmax(100px, auto);
}

Use CSS :empty Pseudo-Class

Handle empty content states:

.message:empty::before {
  content: "No messages";
  color: gray;
}

Optimize CSS Transitions and Animations

Specify specific properties for transitions:

/* Before optimization */
transition: all 0.3s ease;

/* After optimization */
transition: opacity 0.3s ease, transform 0.3s ease;

Use CSS @supports Rule

Progressive enhancement for styles:

@supports (display: grid) {
  .container {
    display: grid;
  }
}
@supports not (display: grid) {
  .container {
    display: flex;
  }
}

Optimize CSS Counter Usage

Reduce counter update frequency:

/* Use one counter for the entire list */
ol {
  counter-reset: item;
}
li {
  counter-increment: item;
}
li::before {
  content: counter(item);
}

Use CSS :target Pseudo-Class

Implement JS-free interactive effects:

.tab-content {
  display: none;
}
.tab-content:target {
  display: block;
}

Optimize CSS Blend Modes

Use blend-mode cautiously:

/* Only use when necessary */
.overlay {
  mix-blend-mode: multiply;
}

Use CSS :invalid Pseudo-Class

Enhance form validation UI:

input:invalid {
  border-color: red;
}

Optimize CSS Viewport Units

Combine with fixed values:

.header {
  height: calc

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.