Back to Blog
By AriesZhou · · 3 min read

Lazy Loading Guide: Essential Techniques for Page Performance

Performance Optimization

Open an image-heavy page and wait several seconds before anything appears? First-screen load time too long, hurting user experience and SEO? These are exactly the problems lazy loading solves. Lazy loading is a core front-end performance optimization technique. By deferring non-first-screen resources, it significantly improves initial load speed and user experience.

1. How lazy loading works

graph LR
    A[首屏渲染] --> B[只加载可见内容]
    B --> C[用户滚动]
    C --> D[触发加载]
    D --> E[加载视口内资源]
    E --> C

Core idea: Load on demand, only when needed.


2. Image lazy loading

2.1 Intersection Observer API

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

document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});

2.2 Image lazy loading in React

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

function LazyImage({ src, alt, placeholder }) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.disconnect();
        }
      });
    });

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef} style={{ minHeight: '200px', background: '#f0f0f0' }}>
      {isInView && (
        <img
          src={src}
          alt={alt}
          style={{ opacity: isLoaded ? 1 : 0, transition: 'opacity 0.3s' }}
          onLoad={() => setIsLoaded(true)}
        />
      )}
    </div>
  );
}

3. React component lazy loading

3.1 React.lazy + Suspense

import { Suspense, lazy } from 'react';

// 懒加载组件
const HeavyChart = lazy(() => import('./HeavyChart'));
const Modal = lazy(() => import('./Modal'));
const Editor = lazy(() => import('./Editor'));

function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <h1>我的应用</h1>
      <Suspense fallback={<LoadingSpinner />}>
        <HeavyChart data={data} />
      </Suspense>

      <button onClick={() => setShowModal(true)}>打开弹窗</button>

      {showModal && (
        <Suspense fallback={<ModalLoading />}>
          <Modal onClose={() => setShowModal(false)} />
        </Suspense>
      )}
    </div>
  );
}

3.2 Route lazy loading

import { Routes, Route } from 'react-router-dom';
import { Suspense, lazy } from 'react';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<PageLoading />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

4. JavaScript dynamic imports

4.1 ES6 import()

// 点击按钮时才加载模块
button.addEventListener('click', async () => {
  const { doSomething } = await import('./utils.js');
  doSomething();
});

// 条件加载
if (condition) {
  const { heavyModule } = await import('./heavy.js');
}

4.2 Webpack code splitting configuration

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          minChunks: 2,
          priority: -10,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

5. Best practices

5.1 Placeholders

function ImageWithPlaceholder({ src, alt }) {
  return (
    <div style={{ position: 'relative', minHeight: '300px' }}>
      {/* 加载前显示占位 */}
      <div
        style={{
          position: 'absolute',
          inset: 0,
          background: 'linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)',
          backgroundSize: '200% 100%',
          animation: 'shimmer 1.5s infinite'
        }}
      />
      <img src={src} alt={alt} loading="lazy" />
    </div>
  );
}

5.2 Reserved dimensions

/* 为图片预留空间,避免布局偏移 */
.lazy-image-container {
  position: relative;
  width: 100%;
  padding-bottom: 56.25%; /* 16:9 比例 */
}

.lazy-image-container img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

5.3 Progressive loading

function ProgressiveImage({ lowResSrc, highResSrc, alt }) {
  const [loaded, setLoaded] = useState(false);

  return (
    <div>
      <img
        src={lowResSrc}
        alt={alt}
        style={{ filter: loaded ? 'none' : 'blur(10px)', transition: 'filter 0.3s' }}
      />
      <img
        src={highResSrc}
        alt={alt}
        style={{ opacity: loaded ? 1 : 0, transition: 'opacity 0.3s' }}
        onLoad={() => setLoaded(true)}
      />
    </div>
  );
}

6. Summary

Lazy loading is a key technique for improving performance:

  • Image lazy loading: using the Intersection Observer API
  • Component lazy loading: React.lazy + Suspense
  • Route lazy loading: Load route modules on demand
  • Code splitting: Webpack dynamic imports

Using lazy loading wisely can significantly improve first-screen load speed and user experience.