阿里云主机折上折
  • 微信号
Current Site:Index > DOM performance optimization

DOM performance optimization

Author:Chuan Chen 阅读数:30525人阅读 分类: JavaScript

The Impact of DOM Operations on Performance

DOM operations are typically one of the most performance-intensive parts of JavaScript. Each DOM update triggers the browser's reflow and repaint processes, both of which are highly resource-consuming. Frequent DOM operations can cause page lag and degrade user experience.

// Inefficient DOM operation example
for (let i = 0; i < 1000; i++) {
  document.getElementById('container').innerHTML += `<div>Item ${i}</div>`;
}

Minimizing DOM Access

Each DOM access causes the browser to recalculate layouts, so direct DOM access should be minimized. DOM references should be cached for reuse.

// Optimized code
const container = document.getElementById('container');
let html = '';
for (let i = 0; i < 1000; i++) {
  html += `<div>Item ${i}</div>`;
}
container.innerHTML = html;

Using DocumentFragment

DocumentFragment is a lightweight document object that can temporarily store DOM nodes before inserting them into the real DOM all at once.

const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = `Item ${i}`;
  fragment.appendChild(div);
}
document.getElementById('container').appendChild(fragment);

Batch Style Modifications

Directly modifying an element's style property triggers repaints. Instead, modify styles in bulk by changing classes.

// Not recommended
element.style.width = '100px';
element.style.height = '200px';
element.style.backgroundColor = 'red';

// Recommended
element.classList.add('active-style');

Event Delegation

Binding event handlers to numerous child elements consumes significant memory. Event delegation should be used instead.

// Traditional way - binding events to each button
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick);
});

// Event delegation - binding a single event to the parent
document.getElementById('button-container').addEventListener('click', function(e) {
  if (e.target.classList.contains('btn')) {
    handleClick(e);
  }
});

Using requestAnimationFrame

For animations or frequent visual updates, use requestAnimationFrame instead of setTimeout or setInterval.

function animate() {
  // Animation logic
  element.style.left = `${pos}px`;
  
  if (pos < 100) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

Virtual DOM Technology

Modern frontend frameworks like React and Vue use virtual DOM technology to minimize actual DOM operations by comparing changes in the virtual DOM tree.

// React example
function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

Optimizing Selector Performance

Complex selectors increase the time browsers take to match elements. Simple selectors should be preferred.

// Not recommended
document.querySelector('div.container > ul.list > li.item');

// Recommended
document.getElementById('list-item');

Avoiding Forced Synchronous Layouts

Reading certain properties (like offsetWidth) forces the browser to perform synchronous layout calculations. Avoid frequently reading these properties in loops.

// Forced synchronous layout example - poor performance
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = elements[i].offsetWidth + 10 + 'px';
}

// Optimized code
const widths = [];
for (let i = 0; i < elements.length; i++) {
  widths[i] = elements[i].offsetWidth;
}
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = widths[i] + 10 + 'px';
}

Using CSS Animations Instead of JavaScript Animations

CSS animations generally perform better than JavaScript animations as they can leverage hardware acceleration.

/* CSS animation */
.box {
  transition: transform 0.3s ease;
}
.box.move {
  transform: translateX(100px);
}

Lazy Loading and Virtual Lists

For long lists or large numbers of DOM elements, lazy loading or virtual list techniques should be employed.

// Virtual list implementation example
class VirtualList {
  constructor(container, itemHeight, renderItem) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.renderItem = renderItem;
    this.visibleItems = Math.ceil(container.clientHeight / itemHeight);
    this.data = [];
    
    container.addEventListener('scroll', this.handleScroll.bind(this));
  }
  
  setData(data) {
    this.data = data;
    this.render();
  }
  
  handleScroll() {
    this.render();
  }
  
  render() {
    const scrollTop = this.container.scrollTop;
    const startIdx = Math.floor(scrollTop / this.itemHeight);
    const endIdx = startIdx + this.visibleItems;
    
    this.container.innerHTML = '';
    for (let i = startIdx; i <= endIdx && i < this.data.length; i++) {
      const item = document.createElement('div');
      item.style.height = `${this.itemHeight}px`;
      item.style.position = 'absolute';
      item.style.top = `${i * this.itemHeight}px`;
      item.style.width = '100%';
      this.renderItem(item, this.data[i]);
      this.container.appendChild(item);
    }
    
    this.container.style.height = `${this.data.length * this.itemHeight}px`;
  }
}

Using MutationObserver Instead of DOM Events

For monitoring DOM changes, MutationObserver is more efficient than traditional DOM events.

const observer = new MutationObserver(mutations => {
  mutations.forEach(mutation => {
    // Handle DOM changes
  });
});

observer.observe(document.getElementById('target'), {
  attributes: true,
  childList: true,
  subtree: true
});

Optimizing Image Loading

While image elements aren't DOM operations, they impact overall performance. Image loading strategies should be optimized.

// Lazy loading images
document.addEventListener('DOMContentLoaded', () => {
  const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));
  
  if ('IntersectionObserver' in window) {
    const lazyImageObserver = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });
    
    lazyImages.forEach(lazyImage => {
      lazyImageObserver.observe(lazyImage);
    });
  }
});

Using Web Workers for Complex Calculations

Offload complex calculations to Web Workers to avoid blocking the main thread and DOM updates.

// Main thread code
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataSet });
worker.onmessage = function(e) {
  document.getElementById('result').textContent = e.data.result;
};

// worker.js
self.onmessage = function(e) {
  const result = processData(e.data.data);
  self.postMessage({ result });
};

function processData(data) {
  // Complex calculations
  return processedResult;
}

Proper Use of Debouncing and Throttling

For frequently triggered events (like scroll, resize), use debouncing or throttling techniques.

// Throttle implementation
function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  }
}

window.addEventListener('scroll', throttle(function() {
  // Handle scroll event
}, 200));

Using CSS Containment

The CSS Containment property tells browsers that an element's subtree is independent of the rest of the page, optimizing rendering performance.

.container {
  contain: layout paint style;
}

Avoiding Table Layouts

Table layouts force browsers to wait for the entire table to load before rendering. Table layouts should be avoided.

<!-- Not recommended -->
<table>
  <tr>
    <td>Content 1</td>
    <td>Content 2</td>
  </tr>
</table>

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

Using will-change Property

For elements that will change, use the will-change property to hint browsers to optimize in advance.

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

Optimizing SVG Performance

SVG elements are part of the DOM. Large numbers of SVGs impact performance, so SVG usage should be optimized.

// Reuse SVG elements instead of creating new instances
const svgNS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(svgNS, 'svg');
const use = document.createElementNS(svgNS, 'use');
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#icon');
svg.appendChild(use);
document.body.appendChild(svg);

Using CSS Variables to Reduce Style Calculations

CSS variables can reduce style calculation time, especially when styles need frequent modification.

:root {
  --main-color: #06c;
}

.element {
  color: var(--main-color);
}

Avoiding @import

@import in CSS blocks parallel loading. Use <link> tags instead.

<!-- Not recommended -->
<style>
  @import url('styles.css');
</style>

<!-- Recommended -->
<link rel="stylesheet" href="styles.css">

Using content-visibility Property

The content-visibility property can skip rendering off-screen content, significantly improving initial load performance.

.container {
  content-visibility: auto;
  contain-intrinsic-size: 500px;
}

Optimizing Font Loading

Font files impact rendering performance. Font loading strategies should be optimized.

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

Using Intersection Observer for Lazy Loading

The Intersection Observer API efficiently detects when elements enter the viewport.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('img.lazy').forEach(img => {
  observer.observe(img);
});

Reducing DOM Depth

Excessively deep DOM trees increase style calculation and layout time. DOM nesting should be minimized.

<!-- Not recommended -->
<div>
  <div>
    <div>
      <div>
        <p>Content</p>
      </div>
    </div>
  </div>
</div>

<!-- Recommended -->
<div>
  <p>Content</p>
</div>

Using CSS Grid and Flexbox Layouts

Modern layout techniques (CSS Grid and Flexbox) generally perform better than traditional layouts.

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

Avoiding Frequent Reading of Layout Properties

Frequently reading layout properties like offsetWidth and offsetHeight in JavaScript triggers forced synchronous layouts.

// Not recommended
function resizeElements(elements) {
  elements.forEach(element => {
    element.style.width = (element.offsetWidth + 10) + 'px';
  });
}

// Recommended
function resizeElements(elements) {
  const widths = elements.map(element => element.offsetWidth);
  elements.forEach((element, index) => {
    element.style.width = (widths[index] + 10) + 'px';
  });
}

Using CSS Containment to Optimize Complex Components

For complex components, use CSS Containment to isolate their rendering impact.

.widget {
  contain: layout paint style;
}

Optimizing Third-Party Script Loading

Third-party scripts often impact page performance. Their loading should be optimized.

<!-- Deferred loading -->
<script src="analytics.js" defer></script>

<!-- Asynchronous loading -->
<script src="analytics.js" async></script>

<!-- Dynamic loading -->
<script>
  window.addEventListener('load', function() {
    const script = document.createElement('script');
    script.src = 'analytics.js';
    document.body.appendChild(script);
  });
</script>

Using Service Worker to Cache Resources

Service Workers can cache resources needed for DOM operations, improving subsequent visit speeds.

// service-worker.js
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/script.js',
        '/data.json'
      ]);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

Using Passive Event Listeners

For events that won't call preventDefault(), use the passive option to improve scroll performance.

document.addEventListener('scroll', function(e) {
  // Handle scroll event
}, { passive: true });

Optimizing Canvas Performance

While Canvas operations aren't DOM-related, they impact overall performance. Canvas usage should be optimized.

// Optimizing Canvas drawing
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Offscreen Canvas
const offscreen = document.createElement('canvas');
offscreen.width = 100;
offscreen.height = 100;
const offscreenCtx = offscreen.getContext('2d');

// Draw complex graphics on offscreen Canvas
offscreenCtx.beginPath();
// ...Complex drawing operations
offscreenCtx.fill();

// Draw offscreen content on main Canvas
ctx.drawImage(offscreen, 0, 0);

Using ResizeObserver Instead of resize Event

ResizeObserver is more efficient than traditional resize events for monitoring element size changes.

const observer = new ResizeObserver(entries => {
  for (let entry of entries) {
    const { width, height } = entry.contentRect;
    // Handle size changes
  }
});

observer.observe(document.getElementById('resizable-element'));

Optimizing Web Font Usage

Web fonts impact text rendering performance. Font loading and usage should be optimized.

@font-face {
  font-family: 'OptimizedFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;
  font-weight: 400;
  font-style: normal;
  unicode-range: U+000-5FF; /* Only load needed character ranges */
}

Using CSS Content-Visibility to Optimize Long Lists

For long lists, the content-visibility property can significantly improve rendering performance.

.long-list-item {
  content-visibility: auto;
  contain-intrinsic-size: 100px 50px; /* Estimated size */
}

Optimizing Shadow and Blur Effects

CSS shadows and filter effects consume performance and should be used judiciously.

/* Not recommended */
.element {
  box-shadow: 0 0 20px 10px rgba(0,0,0,0.5);
  filter: blur(5px);
}

/* Recommended - more efficient shadows */
.element {
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

Using CSS Hardware Acceleration

For animated elements, enable hardware acceleration to improve performance.

.animate {
  transform: translateZ(0);
  will-change: transform;
}

Optimizing Media Queries

Complex media queries increase style calculation time. Media query logic should be optimized.

/* Not recommended - overly complex media queries */
@media screen and (min-width: 600px) and (max-width: 800px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 2) {
  /* Styles */
}

/* Recommended - simplified media queries */
@media (min-width: 600px) {
  /* Styles */
}

Using CSS Scroll Snap

CSS Scroll Snap provides smoother scrolling experiences and reduces JavaScript scroll handling.

.container {
  scroll-snap-type: y mandatory;
  overflow-y: scroll;
  height: 100vh;
}

.section {
  scroll-snap-align: start;
  height: 100vh;
}

Optimizing CSS Selectors

Complex selectors increase style calculation time. Selectors should be simplified.

/* Not recommended */
div.container > ul.list > li.item > a.link:hover {
  color: red;
}

/* Recommended */
.link:hover {
  color: red;
}

Using CSS Variables for Theme Switching

CSS variables can efficiently implement theme switching, avoiding massive style repaints.

:root {
  --primary-color: #0066cc;
  --background: white;
}

.dark-theme {
  --primary-color: #33a6ff;
  --background: #121212;
}

body {
  background: var(--background);
  color: var(--primary-color);
}

Optimizing Canvas Drawing Performance

For Canvas animations, optimize drawing logic to reduce repaint areas.

// Only repaint changed areas
function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // Only draw what needs updating
  updateParticles();
  drawParticles();
  requestAnimationFrame(animate);
}

Using CSS Containment to Isolate Animations

For complex animations, use CSS Containment to isolate their impact scope.

.animated-element {
  contain: strict;
  will-change: transform;
}

Optimizing Web Component Performance

Custom web components require performance optimization, especially for lifecycle methods.

class OptimizedElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    // Defer non-critical operations
    requestIdleCallback(() => {
      this.loadLazyContent();
    });
  }

  loadLazyContent() {
    // Load deferred content
  }
}

Using CSS Overscroll Behavior to Control Scroll Propagation

Prevent scroll events from propagating to parent elements.

.container {
  overscroll-behavior: contain;
}

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

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:事件处理机制

下一篇:window对象核心API

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 ☕.