Lazy Load: Delay loading the "coffee beans" resources
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:
- Open DevTools (F12)
- Switch to the Coverage tab
- Start recording and interact with the page
- 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:
- Lazy Loading Above-the-Fold Content: Increases perceived load time
- Lazy Loading Tiny Resources: May not be worth the effort
- Excessive Chunking: Leads to too many network requests
- 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