首页图片懒加载实现方案解析

1. IntersectionObserver API(现代推荐方案)

实现原理: 通过观察目标元素与视窗的交叉状态,自动触发回调函数。

代码示例:

javascript 复制代码
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      imageObserver.unobserve(img);
    }
  });
}, { rootMargin: '50px' });

images.forEach(img => imageObserver.observe(img));

优点:

✅ 原生浏览器支持,性能最优

✅ 异步执行,不阻塞主线程

✅ 支持阈值配置和根元素偏移

✅ 代码简洁,易于维护

✅ 支持所有现代浏览器(IE需polyfill)

缺点:

❌ IE不原生支持(需polyfill)

❌ 部分复杂场景配置需要理解

2. 传统滚动监听 + getBoundingClientRect

实现原理: 监听滚动事件,通过getBoundingClientRect()计算元素位置。

代码示例:

javascript 复制代码
function lazyLoadTraditional() {
  const images = document.querySelectorAll('img[data-src]');
  const windowHeight = window.innerHeight;
  
  images.forEach(img => {
    const rect = img.getBoundingClientRect();
    if (rect.top < windowHeight + 100) {
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
    }
  });
}

// 添加防抖优化
const debouncedLazyLoad = debounce(lazyLoadTraditional, 100);
window.addEventListener('scroll', debouncedLazyLoad);
window.addEventListener('resize', debouncedLazyLoad);

优点:

✅ 兼容性好(支持所有浏览器)

✅ 无需额外依赖

✅ 控制精细,可自定义计算逻辑

缺点:

❌ 频繁触发,性能较差

❌ 需要手动优化(防抖/节流)

❌ 代码相对复杂

❌ 计算可能引起重排

3. offsetTop/scrollTop 计算

实现原理: 通过元素相对于文档的偏移位置计算。

代码示例:

javascript 复制代码
function lazyLoadOffset() {
  const images = document.querySelectorAll('img[data-src]');
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const windowHeight = window.innerHeight;
  
  images.forEach(img => {
    if (img.offsetTop < scrollTop + windowHeight + 200) {
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
    }
  });
}

优点:

✅ 兼容性极好

✅ 计算相对简单

缺点:

❌ 准确性受布局影响

❌ 频繁重排影响性能

❌ 需要处理多种滚动容器

4. 基于 CSS 的 content-visibility

实现原理: 利用CSS新特性延迟渲染不可见内容。

代码示例:

javascript 复制代码
.lazy-container {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* 预估高度 */
}

/* 图片完全加载后移除占位 */
img:not([src]) {
  background: #f0f0f0;
}

优点:

✅ 性能优秀(浏览器原生优化)

✅ 简单易用

✅ 适用于大量列表

缺点:

❌ 兼容性一般(Chrome 85+)

❌ 不是专门为懒加载设计

❌ 需要预估内容尺寸

5. 第三方库方案

常见库:

lozad.js(基于IntersectionObserver) lazysizes(功能全面) vanilla-lazyload(轻量级)

代码示例(lozad.js):

javascript 复制代码
import lozad from 'lozad';

const observer = lozad('.lozad', {
  rootMargin: '100px',
  loaded: (el) => {
    el.classList.add('loaded');
  }
});
observer.observe();

优点:

✅ 功能丰富(支持背景图、iframe等)

✅ 自动降级处理

✅ 社区维护,测试充分

✅ 支持多种加载策略

缺点:

❌ 增加包体积

❌ 学习第三方API

❌ 可能包含不需要的功能

方案详细对比

建议与最佳实践

推荐方案选择

1. 首选方案:IntersectionObserver + 优雅降级

javascript 复制代码
// 示例:带降级的实现
const lazyLoad = (() => {
  if ('IntersectionObserver' in window) {
    // 使用IntersectionObserver
    return (images) => {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            loadImage(entry.target);
            observer.unobserve(entry.target);
          }
        });
      }, { rootMargin: '50px' });
      
      images.forEach(img => observer.observe(img));
    };
  } else {
    // 降级到传统方案
    return (images) => {
      const lazyLoadTraditional = debounce(() => {
        const windowHeight = window.innerHeight;
        images.forEach(img => {
          const rect = img.getBoundingClientRect();
          if (rect.top < windowHeight + 100) {
            loadImage(img);
          }
        });
      }, 100);
      
      window.addEventListener('scroll', lazyLoadTraditional);
      window.addEventListener('resize', lazyLoadTraditional);
      lazyLoadTraditional(); // 初始执行
    };
  }
})();

2. 关键优化建议:

占位符:使用固定宽高比容器避免布局抖动

渐进加载:先加载低质量占位图(LQIP)

错误处理:添加加载失败的回退方案

预加载:提前加载即将进入视口的图片

3. HTML标记示例:

javascript 复制代码
<div class="image-container" style="aspect-ratio: 16/9;">
  <img 
    data-src="image.jpg" 
    data-srcset="image-small.jpg 300w, image-large.jpg 800w"
    data-sizes="auto"
    alt="描述文本"
    class="lazy-image"
    width="800"
    height="450"
  >
  <!-- 低质量占位 -->
  <div class="lazy-placeholder"></div>
</div>

4. 性能监控:

javascript 复制代码
// 监控图片加载性能
const perfObserver = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log('Lazy-loaded image timing:', {
      name: entry.name,
      duration: entry.duration,
      startTime: entry.startTime
    });
  });
});
perfObserver.observe({ entryTypes: ['resource'] });

总结

  1. 对于现代应用:IntersectionObserver是首选方案,提供最佳性能和开发体验。

  2. 需要兼容旧浏览器:使用IntersectionObserver polyfill或第三方库(如lazysizes)自动处理降级。

  3. 简单页面/少量图片:传统的滚动监听方案仍然可用,但必须添加防抖/节流优化。

  4. 内容密集型页面:考虑结合CSS content-visibility和懒加载,获得双重性能提升。

  5. 企业级项目:评估使用成熟的第三方库,节省开发时间,获得更多高级功能。

相关推荐
卸任17 小时前
Vibe coding工具使用心得
前端·ai编程
brien918 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance18 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M18 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写18 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李18 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易19 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马19 小时前
前沿视界
前端
kyriewen19 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易19 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员