Lazy loading and on-demand loading
The Concept of Lazy Loading and On-Demand Loading
Lazy Loading and On-Demand Loading are two commonly used techniques in front-end performance optimization. Lazy loading typically refers to delaying the loading of non-critical resources, such as images or videos, until they are about to enter the viewport. On-demand loading, on the other hand, focuses more on splitting code or modules, dynamically loading corresponding resources only when needed. Both techniques can effectively reduce initial page load time and improve user experience.
Implementation of Image Lazy Loading
Image lazy loading is the most common application scenario. Traditional image loading methods request all image resources at once, while lazy loading only loads images when they are about to appear in the viewport.
<img data-src="image.jpg" class="lazy" alt="Example Image">
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);
});
}
});
This implementation uses the IntersectionObserver API to detect whether an image enters the viewport. When the image enters the viewport, the value of the data-src
attribute is assigned to the src
attribute, triggering the image load.
Route-Level On-Demand Loading
In single-page applications (SPAs), route-level on-demand loading can significantly improve the initial load speed. Taking React as an example:
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
}
The React.lazy
function allows dynamic importing of components, while the Suspense
component provides a fallback UI during loading. When the user navigates to the /about
route, the code for the About
component is loaded.
Component-Level On-Demand Loading
For complex components in large applications, more granular on-demand loading can be implemented:
const loadModal = () => import('./Modal');
function App() {
const [showModal, setShowModal] = useState(false);
const [ModalComponent, setModalComponent] = useState(null);
const openModal = async () => {
const { default: Modal } = await loadModal();
setModalComponent(<Modal />);
setShowModal(true);
};
return (
<div>
<button onClick={openModal}>Open Modal</button>
{showModal && ModalComponent}
</div>
);
}
In this approach, the Modal
component and its dependencies are only loaded when the user clicks the button.
On-Demand Loading for Third-Party Libraries
Many large third-party libraries also support on-demand loading. Taking lodash as an example:
// Traditional method - import the entire library
import _ from 'lodash';
// On-demand loading method
import debounce from 'lodash/debounce';
button.addEventListener('click', debounce(() => {
console.log('Debounced click event');
}, 300));
For modern libraries that support ES modules, dynamic imports can also be used:
const loadUtility = async () => {
const { default: _ } = await import('lodash-es');
// Use lodash functionality
};
Considerations for Lazy Loading
When implementing lazy loading, several key factors should be considered:
- Placeholder Design: Design appropriate placeholders for lazy-loaded elements to avoid layout shifts.
- Preloading Strategy: For resources that may be needed soon, a small amount of preloading can be done in advance.
- Error Handling: Network requests may fail, so retry mechanisms and fallbacks should be provided.
- SEO Impact: Search engines may not execute JavaScript, so critical content should not rely on lazy loading.
<div class="lazy-container">
<img data-src="image.jpg" alt="Product Image"
class="lazy"
loading="lazy"
onerror="this.src='fallback.jpg'">
<div class="placeholder"></div>
</div>
Performance Optimization for On-Demand Loading
Combined with build tools like Webpack, further optimization for on-demand loading can be achieved:
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
Such configurations help automatically split code chunks for more efficient on-demand loading.
Native Browser Support
Modern browsers natively support some lazy loading features:
<!-- Native image lazy loading -->
<img src="image.jpg" loading="lazy" alt="Example">
<!-- iframe lazy loading -->
<iframe src="content.html" loading="lazy"></iframe>
The loading
attribute supports three values:
lazy
: Delays loadingeager
: Loads immediatelyauto
: Let the browser decide
Handling in Server-Side Rendering
Implementing lazy loading in SSR applications requires special handling:
// Dynamic import in Next.js
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(
() => import('../components/hello'),
{
loading: () => <p>Loading...</p>,
ssr: false // Disable server-side rendering
}
);
function Home() {
return (
<div>
<DynamicComponent />
</div>
)
}
This approach maintains the benefits of SSR while achieving client-side on-demand loading.
Performance Monitoring and Tuning
After implementing lazy loading and on-demand loading, actual performance should be monitored:
// Use Performance API to monitor resource loading
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(`Resource: ${entry.name}, Load time: ${entry.duration}ms`);
});
});
observer.observe({entryTypes: ["resource"]});
// Record the time from user interaction to load completion
button.addEventListener('click', () => {
const start = performance.now();
import('./module').then(() => {
const loadTime = performance.now() - start;
console.log(`Module load time: ${loadTime}ms`);
});
});
This data can help further optimize loading strategies and split points.
Special Considerations for Mobile
In mobile network environments, special considerations are needed:
- More Aggressive Lazy Loading Thresholds: Start loading earlier to offset mobile network latency.
- Connection Type Awareness: Adjust strategies based on network type.
- Data Saver Mode: Respect user settings for data restrictions.
// Detect network conditions
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
if (connection.effectiveType === 'slow-2g') {
// More aggressive lazy loading strategy
}
connection.addEventListener('change', updateLoadingStrategy);
}
Combining with Caching Strategies
Reasonable caching strategies can improve loading performance for repeat visits:
// Cache on-demand loaded modules in Service Worker
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/dist/') &&
event.request.url.endsWith('.js')) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((response) => {
const responseToCache = response.clone();
caches.open('dynamic-cache').then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
}
});
This strategy ensures that after the first load, subsequent on-demand loads can be read directly from the cache.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn