阿里云主机折上折
  • 微信号
Current Site:Index > Lazy Load: Delay loading the "coffee beans" resources

Lazy Load: Delay loading the "coffee beans" resources

Author:Chuan Chen 阅读数:40634人阅读 分类: 前端综合

Performance optimization is key to enhancing user experience, and lazy loading technology is like adjusting the brewing time of coffee, allowing resources to "release their aroma" only when needed. By rationally controlling the timing of resource loading, initial load can be effectively reduced, and page responsiveness can be improved.

What is Lazy Load?

Lazy loading is a technique that delays the loading of non-critical resources until they are actually needed. It's like grinding coffee beans in batches based on drinking needs rather than grinding all at once. In front-end development, common lazy loading targets include:

  • Images (especially those below the fold)
  • iframe content
  • Third-party scripts
  • Non-critical CSS/JS
  • Chunked loading of large data lists

Native Implementation of Image Lazy Loading

HTML5 natively supports lazy loading of images through the loading="lazy" attribute, which is the simplest implementation:

<img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="Example image">

When the image enters the viewport, the browser automatically loads the actual image resource. This method has good compatibility in modern browsers, but for cases requiring finer control, a JavaScript solution may be needed.

Intersection Observer API Solution

The Intersection Observer API provides more powerful intersection observation capabilities, suitable for implementing custom lazy loading logic:

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

Corresponding HTML structure:

<img class="lazy" data-src="image-to-lazy-load.jpg" src="placeholder.jpg" alt="Example">

Lazy Loading Practices in React

In the React ecosystem, you can use React Lazy with Suspense to achieve component-level lazy loading:

import React, { Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./LazyComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

For image resources, you can encapsulate a LazyImage component:

import React, { useState, useRef, useEffect } from 'react';

function LazyImage({ src, alt, placeholder }) {
  const [isLoading, setIsLoading] = useState(true);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef();
  
  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.unobserve(entry.target);
        }
      });
    });
    
    if (imgRef.current) {
      observer.observe(imgRef.current);
    }
    
    return () => {
      if (imgRef.current) {
        observer.unobserve(imgRef.current);
      }
    };
  }, []);
  
  return (
    <div ref={imgRef}>
      {isInView ? (
        <img 
          src={src} 
          alt={alt}
          onLoad={() => setIsLoading(false)}
          style={{ opacity: isLoading ? 0.5 : 1 }}
        />
      ) : (
        <img src={placeholder} alt="Loading placeholder" />
      )}
    </div>
  );
}

Advanced Lazy Loading Techniques

1. Resource Priority Hints

Combine <link rel="preload"> with lazy loading to optimize the loading order of critical resources:

<link rel="preload" href="hero-image.jpg" as="image">
<img src="placeholder.jpg" data-src="hero-image.jpg" class="lazy" alt="Hero image">

2. Adaptive Loading Strategy

Dynamically adjust lazy loading thresholds based on network conditions:

const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
let threshold = 0.1; // Default threshold

if (connection) {
  if (connection.effectiveType === 'slow-2g' || connection.saveData === true) {
    threshold = 0.5; // Load earlier on slow networks
  }
}

const observer = new IntersectionObserver(callback, {
  rootMargin: `${threshold * 100}% 0px`
});

3. Skeleton Screen Optimization

Display skeleton screens before content loads to improve perceived performance:

<div class="card lazy-card">
  <div class="card-skeleton">
    <div class="skeleton-image"></div>
    <div class="skeleton-line"></div>
    <div class="skeleton-line short"></div>
  </div>
  <div class="card-content" hidden>
    <!-- Actual content -->
  </div>
</div>
// When the card enters the viewport
observerCallback = (entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const card = entry.target;
      const content = card.querySelector('.card-content');
      content.hidden = false;
      card.classList.remove('lazy-card');
    }
  });
};

Performance Monitoring and Optimization

After implementing lazy loading, verify the effect using performance metrics:

// Monitor using Performance API
const perfObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'first-contentful-paint') {
      console.log('FCP:', entry.startTime);
    }
  }
});

perfObserver.observe({ type: 'paint', buffered: true });

// Custom metric: Lazy loading completion time
const lazyLoadTiming = {
  start: performance.now(),
  end: null
};

window.addEventListener('load', () => {
  lazyLoadTiming.end = performance.now();
  console.log(`Lazy loading duration: ${lazyLoadTiming.end - lazyLoadTiming.start}ms`);
});

Common Issues and Solutions

1. Layout Shift Issues

Lazy loading may cause sudden content appearance, leading to layout shifts (CLS). Solution:

.lazy-container {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16/9; /* Maintain container aspect ratio */
}

.lazy-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #f0f0f0;
}

2. SEO Impact

Search engines may not crawl lazy-loaded content. Solution:

<noscript>
  <img src="important-image.jpg" alt="SEO-important image">
</noscript>

3. Browser Compatibility

Fallback for browsers that don't support Intersection Observer:

if (!('IntersectionObserver' in window)) {
  lazyImages.forEach(lazyImage => {
    lazyImage.src = lazyImage.dataset.src;
  });
  return;
}

Framework Integration Solutions

Lazy Loading in Vue

Use the vue-lazyload plugin:

import Vue from 'vue';
import VueLazyload from 'vue-lazyload';

Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: 'error-image.png',
  loading: 'loading-spinner.gif',
  attempt: 3
});

Usage in templates:

<img v-lazy="imageUrl" alt="Description">

Lazy Loading in Angular

Use the built-in NgOptimizedImage directive:

import { NgOptimizedImage } from '@angular/common';

@Component({
  selector: 'app-image',
  standalone: true,
  imports: [NgOptimizedImage],
  template: `
    <img 
      ngSrc="image.jpg" 
      width="800" 
      height="600" 
      priority
      alt="Example image"
    >
  `
})

Server-Side Coordination Strategies

The server can return different quality images for progressive loading:

// Express example
app.get('/image/:id', (req, res) => {
  const quality = req.query.quality || 'high';
  const imageId = req.params.id;
  
  if (quality === 'low') {
    return res.sendFile(`/low-quality/${imageId}.jpg`);
  } else {
    return res.sendFile(`/high-quality/${imageId}.jpg`);
  }
});

Frontend progressive loading implementation:

function loadImage(imageId) {
  // First load low-quality image
  const img = new Image();
  img.src = `/image/${imageId}?quality=low`;
  
  // When low-quality image loads
  img.onload = () => {
    // Start loading high-quality version
    const hiResImg = new Image();
    hiResImg.src = `/image/${imageId}?quality=high`;
    hiResImg.onload = () => {
      document.getElementById('target').src = hiResImg.src;
    };
  };
}

Performance Impact of Lazy Loading

Use Chrome DevTools' Coverage tool to analyze resource utilization:

  1. Open DevTools (F12)
  2. Switch to the Coverage tab
  3. Start recording and interact with the page
  4. View the proportion of unused CSS/JS

Typical before-and-after optimization comparison:

Metric Before After
First Contentful Paint 2.8s 1.2s
Total Transfer Size 1.4MB 680KB
Largest Contentful Paint 2.1s 0.9s

Lazy Loading Anti-Patterns

While lazy loading is powerful, it can backfire in certain scenarios:

  1. Lazy Loading Above-the-Fold Content: Increases perceived load time
  2. Lazy Loading Tiny Resources: May not be worth the effort
  3. Excessive Chunking: Leads to too many network requests
  4. No Fallback Mechanism: No backup plan for network failures

Correct approach example:

function loadCriticalResources() {
  return new Promise((resolve, reject) => {
    // Load critical resources
    const criticalScript = document.createElement('script');
    criticalScript.src = 'critical.js';
    criticalScript.onload = resolve;
    criticalScript.onerror = reject;
    document.head.appendChild(criticalScript);
  });
}

function loadLazyResources() {
  // Lazy load non-critical resources
  if (document.readyState === 'complete') {
    loadLazy();
  } else {
    window.addEventListener('load', loadLazy);
  }
}

function loadLazy() {
  // Actual lazy loading logic
}

Lazy Loading with Modern Image Formats

Combine with modern image formats like WebP/AVIF for further optimization:

<picture>
  <source 
    data-srcset="image.avif" 
    type="image/avif"
    loading="lazy"
  >
  <source 
    data-srcset="image.webp" 
    type="image/webp"
    loading="lazy"
  >
  <img 
    src="placeholder.jpg" 
    data-src="image.jpg" 
    loading="lazy"
    alt="Example image"
  >
</picture>

Check browser support:

function checkAvifSupport() {
  return new Promise(resolve => {
    const avif = new Image();
    avif.onload = () => resolve(true);
    avif.onerror = () => resolve(false);
    avif.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
  });
}

async function loadBestImage() {
  const supportsAvif = await checkAvifSupport();
  const images = document.querySelectorAll('source[data-srcset]');
  
  images.forEach(img => {
    if (supportsAvif && img.type === 'image/avif') {
      img.srcset = img.dataset.srcset;
    } else if (img.type === 'image/webp') {
      img.srcset = img.dataset.srcset;
    }
  });
}

Lazy Loading and State Management

In Single Page Applications (SPAs), lazy loading needs to integrate with state management:

// Vuex module dynamic registration example
const lazyLoadModule = (store, moduleName) => {
  import(`@/store/modules/${moduleName}.js`)
    .then(module => {
      store.registerModule(moduleName, module.default);
    })
    .catch(error => {
      console.error(`Module load failed: ${moduleName}`, error);
    });
};

// Usage
lazyLoadModule(store, 'userProfile');

React + Redux solution:

import { createSlice } from '@reduxjs/toolkit';

const lazySlice = (sliceName) => {
  return import(`./slices/${sliceName}`)
    .then(module => {
      return createSlice(module.default);
    });
};

// Dynamic reducer injection
const injectReducer = async (store, sliceName) => {
  const slice = await lazySlice(sliceName);
  store.injectReducer(slice.name, slice.reducer);
};

Animation Transitions for Lazy Loading

Add smooth transition effects for lazy-loaded content:

.lazy-item {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.3s ease-out, transform 0.3s ease-out;
}

.lazy-item.loaded {
  opacity: 1;
  transform: translateY(0);
}

JavaScript trigger:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('loaded');
      observer.unobserve(entry.target);
    }
  });
}, { threshold: 0.1 });

Lazy Loading with Web Workers

Move resource processing logic to Web Workers for true asynchronicity:

// worker.js
self.onmessage = function(e) {
  const { type, data } = e.data;
  
  if (type === 'loadImage') {
    fetch(data.url)
      .then(response => response.blob())
      .then(blob => {
        self.postMessage({
          type: 'imageLoaded',
          data: URL.createObjectURL(blob)
        });
      });
  }
};

// Main thread
const worker = new Worker('worker.js');

function lazyLoadWithWorker(imageElement) {
  worker.postMessage({
    type: 'loadImage',
    data: { url: imageElement.dataset.src }
  });
  
  worker.onmessage = function(e) {
    if (e.data.type === 'imageLoaded') {
      imageElement.src = e.data.data;
    }
  };
}

Caching Strategies for Lazy Loading

Implement smart caching with Service Worker:

// service-worker.js
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  
  if (url.pathname.includes('/lazy/')) {
    event.respondWith(
      caches.match(event.request).then(response => {
        // If cached and not expired, return directly
        if (response) {
          return response;
        }
        
        // Otherwise fetch and cache
        return fetch(event.request).then(networkResponse => {
          const clonedResponse = networkResponse.clone();
          caches.open('lazy-cache').then(cache => {
            cache.put(event.request, clonedResponse);
          });
          return networkResponse;
        });
      })
    );
  }
});

Error Handling for Lazy Loading

Robust lazy loading requires comprehensive error handling:

function lazyLoadWithRetry(element, retries = 3, delay = 1000) {
  const src = element.dataset.src;
  let attempts = 0;
  
  function attemptLoad() {
    attempts++;
    
    const img = new Image();
    img.src = src;
    
    img.onload = () => {
      element.src = src;
      element.classList.add('loaded');
    };
    
    img.onerror = () => {
      if (attempts < retries) {
        setTimeout(attemptLoad, delay * attempts);
      } else {
        element.src = 'fallback.jpg';
        element.alt = 'Load failed: ' + element.alt;
      }
    };
  }
  
  attemptLoad();
}

Lazy Loading and Virtual Scrolling

For ultra-long lists, combine with virtual scrolling for ultimate performance:

function VirtualList({ items, itemHeight, renderItem }) {
  const [startIndex, setStartIndex] = useState(0);
  const containerRef = useRef();
  
  const visibleCount = Math.ceil(window.innerHeight / itemHeight) + 2;
  const visibleItems = items.slice(startIndex, startIndex + visibleCount);
  
  useEffect(() => {
    const handleScroll = () => {
      const scrollTop = containerRef.current.scrollTop;
      const newStartIndex = Math.floor(scrollTop / itemHeight);
      setStartIndex(newStartIndex);
    };
    
    containerRef.current.addEventListener('scroll', handleScroll);
    return () => {
      containerRef.current.removeEventListener('scroll', handleScroll);
    };
  }, []);
  
  return (
    <div 
      ref={containerRef} 
      style={{ height: '100vh', overflow: 'auto' }}
    >
      <div style={{ height: `${items.length * item

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

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