Back to Blog
· 3 min read

懒加载完全指南:提升页面性能的必备技术

性能优化

打开一个图片丰富的页面,结果等待好几秒才能看到内容?首屏加载时间太长,影响用户体验和 SEO?这些都是懒加载可以解决的问题。懒加载(Lazy Loading)是前端性能优化的核心技术。通过延迟加载非首屏资源,显著提升首屏加载速度和用户体验。

1. 懒加载原理

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

核心思想:按需加载,只在需要时加载。


2. 图片懒加载

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 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 组件懒加载

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 路由懒加载

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 动态导入

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 代码分割配置

// 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. 最佳实践

5.1 占位符

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 预留尺寸

/* 为图片预留空间,避免布局偏移 */
.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 渐进式加载

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. 总结

懒加载是提升性能的关键技术:

  • 图片懒加载: 使用 Intersection Observer API
  • 组件懒加载: React.lazy + Suspense
  • 路由懒加载: 按需加载路由模块
  • 代码分割: Webpack 动态导入

合理使用懒加载,能显著提升首屏加载速度和用户体验。


相关阅读