JavaScript 实现图片懒加载

11 阅读4分钟

文档概述

本文基于原生 JavaScript IntersectionObserver API 实现的图片懒加载方案知识库说明,通过监听元素与视口的交叉状态,仅在图片进入视口时加载真实图片,减少初始页面加载资源消耗,提升页面性能与用户体验。

核心特性

  1. 原生 API 实现:基于 IntersectionObserver,无需引入第三方库,兼容性良好
  2. 性能优化:使用 DocumentFragment 批量操作 DOM,减少重绘重排
  3. 占位图策略:初始加载默认占位图,进入视口后替换为真实图片
  4. 自动停止观察:图片加载完成后自动取消观察,避免重复触发
  5. 视觉交互:包含卡片悬停缩放效果、自定义滚动条样式,提升用户体验

依赖说明

  • 核心技术:原生 JavaScript(ES6+)、HTML5、CSS3

  • 浏览器兼容性:支持 IntersectionObserver API 的现代浏览器(Chrome 51+、Firefox 55+、Safari 12.1+、Edge 79+)

  • 外部资源:

    • 默认占位图:https://pica.zhimg.com/v2-f052aa50ca65df4bad1c3b7e4084d00e_1440w.jpg
    • 动态图片模板:https://picsum.photos/400/600?r={index}

完整代码实现

1. HTML 结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <title>JavaScript Observer 实现懒加载</title>
  <link rel="stylesheet" href="./css/index.css" />
</head>
<body>
  <div class="card-list"></div>
  <script src="./js/index.js"></script>
</body>
</html>

2. CSS 样式

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background-color: #f5f6f7;
}

.card-list {
  --ap-gap: 16px;
  --ap-min-width: 300px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--ap-min-width), 1fr));
  gap: var(--ap-gap);
  padding: 16px;
}

.card-list .item {
  cursor: pointer;
  height: 497px;
  border-radius: 10px;
  box-shadow: 0 0 6px #000;
  overflow: hidden;
}

.card-list .item:hover img {
  transform: scale(1.5);
}

.card-list .item img {
  display: block;
  width: 100%;
  height: 100%;
  transition: all 0.32s;
}

/* 自定义滚动条样式 */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
::-webkit-scrollbar-track {
  background-color: #f5f5f5;
}
::-webkit-scrollbar-track-piece {
  border-radius: 6px;
  background-color: #f5f5f5;
}
::-webkit-scrollbar-thumb {
  border-radius: 6px;
  background-color: #ccc;
}
::-webkit-scrollbar-thumb:hover {
  background-color: #a8a8a8;
}
::-webkit-scrollbar-thumb:active {
  background-color: #787878;
}
::-webkit-scrollbar-corner {
  background-color: #f5f5f5;
}
::-webkit-resizer {
  background-repeat: no-repeat;
  background-position: bottom right;
}

3. JavaScript 逻辑

// 配置项
const TOTAL_ITEMS = 99; // 总图片数量
const DEFAULT_IMG = 'https://pica.zhimg.com/v2-f052aa50ca65df4bad1c3b7e4084d00e_1440w.jpg'; // 默认占位图
const IMG_URL_TEMPLATE = (index) => `https://picsum.photos/400/600?r=${index}`; // 动态图片模板

const cardList = document.querySelector('.card-list');

/**
 * 生成图片卡片
 * 使用 DocumentFragment 批量操作 DOM,减少重绘重排
 */
function generateItems() {
  const fragment = document.createDocumentFragment();
  for (let i = 0; i < TOTAL_ITEMS; i++) {
    const div = document.createElement('div');
    div.classList.add('item');
    
    const img = document.createElement('img');
    img.src = DEFAULT_IMG; // 初始设置占位图
    img.dataset.src = IMG_URL_TEMPLATE(i); // 真实图片地址存储在 data-src
    img.alt = `Image ${i + 1}`;
    
    div.appendChild(img);
    fragment.appendChild(div);
  }
  cardList.appendChild(fragment); // 一次性插入所有元素
}

/**
 * 初始化 Intersection Observer
 * 监听图片与视口的交叉状态,实现懒加载
 */
function initLazyLoad() {
  const observer = new IntersectionObserver(
    (entries, observer) => {
      entries.forEach((entry) => {
        if (!entry.isIntersecting) return; // 跳过未进入视口的元素
        
        const img = entry.target;
        img.src = img.dataset.src; // 替换为真实图片
        observer.unobserve(img); // 加载完成后停止观察
      });
    },
    {
      threshold: 0.01, // 交叉阈值:元素 1% 进入视口时触发
    }
  );

  // 观察所有带有 data-src 属性的图片
  document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img));
}

// 执行主逻辑
generateItems();
initLazyLoad();

核心实现说明

1. 图片卡片生成

  • DocumentFragment 优化:使用文档片段暂存所有卡片元素,最后一次性插入 DOM,避免多次操作 DOM 导致的性能损耗
  • 占位图策略:初始 img.src 设置为默认占位图,真实图片地址存储在 data-src 自定义属性中
  • 动态图片地址:通过模板函数 IMG_URL_TEMPLATE 生成唯一的动态图片地址,避免缓存

2. Intersection Observer 初始化

  • 交叉阈值(threshold) :设置为 0.01,即图片 1% 进入视口时触发加载,提前加载提升流畅度
  • 回调处理:遍历所有观察项,仅处理进入视口的元素(entry.isIntersectingtrue
  • 自动停止观察:图片加载完成后调用 observer.unobserve(img),避免重复触发回调,节省性能

API 说明

IntersectionObserver 配置

配置项类型默认值说明
thresholdnumber0元素与视口的交叉比例阈值,达到该比例时触发回调
rootElementnull观察的根元素,默认为视口
rootMarginstring'0px'根元素的外边距,用于提前或延迟触发

核心方法

方法名说明
observe(target)开始观察目标元素
unobserve(target)停止观察目标元素
disconnect()停止观察所有元素

使用示例

直接在浏览器中打开 HTML 文件即可体验:

  1. 页面初始加载 99 个带占位图的卡片
  2. 滚动页面,卡片进入视口时自动加载真实图片
  3. 鼠标悬停卡片,图片放大 1.5 倍
  4. 滚动条样式自定义,提升视觉体验

注意事项

  1. 浏览器兼容性IntersectionObserver 在 IE 及旧版本浏览器中不支持,如需兼容可引入 polyfill(如 intersection-observer
  2. 占位图优化:建议使用尺寸较小的占位图,减少初始加载资源
  3. 图片地址有效性:确保 data-src 存储的真实图片地址可访问
  4. 性能扩展:可结合 loading="lazy" 属性(原生懒加载)作为降级方案
  5. 错误处理:可添加图片加载失败的回调,替换为错误占位图
  6. 阈值调整:可根据实际需求调整 threshold 值,平衡加载时机与性能