首屏加载慢?一文解决图片懒加载的兼容性、闪烁与性能难题🔥

509 阅读4分钟

一、底层原理

懒加载的核心思想是按需加载,将资源加载时机与用户行为绑定,主要基于以下技术实现:

1. 浏览器渲染机制

  • 浏览器解析 HTML 时,遇到<img><script>等标签会立即发起资源请求,可能阻塞主线程
  • 懒加载通过动态创建 DOM 元素或修改元素属性,将资源请求延迟到用户需要时触发。

2. 元素可视区域检测

  • 滚动事件监听:监听window.scroll事件,结合Element.getBoundingClientRect()判断元素是否进入视口。

二、代码完整案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图片懒加载</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    body {
      background-color: #000;
    }
    .image-item {
      width: 500px;
      height: 500px;
      margin-bottom: 20px;
    }
  </style>
</head>
<body>
  <!-- 图片标签结构说明:
       - src:初始占位图(小尺寸加载快的图片)
       - data-original:存储真实图片URL
       - lazyload:标记需要懒加载的元素
  -->
  <img class="image-item" lazyload="true" 
       src="https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif" 
       data-original="https://img.36krcdn.com/hsossms/20250313/v2_15ad8ef9eca34830b4a2e081bbc7f57a@000000_oswg172644oswg1536oswg722_img_000?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center" />
  <img  class="image-item" lazyload="true"
        src="https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif" 
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_aeaa7a1d51e74c3a8f909c96cd73a687@000000_oswg169950oswg1440oswg600_img_jpeg?x-oss-process=image/format,webp" />
  <img  class="image-item" lazyload="true" 
        src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" 
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_1c88dc26ff9341cf8738d670896ce3a8@5284654_oswg847922oswg1440oswg600_img_png?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center/format,webp" />
  
  <!-- 省略其他相似图片标签... -->

  <script>
    // 获取视口高度(浏览器可见区域高度)
    const viewHeight = document.documentElement.clientHeight;
    // 选中所有包含data-original和lazyload属性的img标签
    const eles = document.querySelectorAll('img[data-original][lazyload]');
    
    // 懒加载核心函数
    const lazyload = function() {
      // 将类数组转换为数组并遍历每个图片元素
      Array.prototype.forEach.call(eles, function(item, index) {
        // 若data-original为空则跳过(防御性判断)
        if (item.dataset.original === "") return;
        
        // 获取元素相对于视口的位置信息(矩形对象)
        const rect = item.getBoundingClientRect();
        
        // 判断元素是否在视口内或即将进入视口
        // rect.bottom >= 0:元素底部未完全超出视口顶部
        // rect.top < viewHeight:元素顶部已进入视口底部
        if (rect.bottom >= 0 && rect.top < viewHeight) {
          // 使用立即执行函数创建闭包,避免变量污染
          (function() {
            // 创建内存中的Image对象用于预加载图片
            var img = new Image();
            // 设置Image对象的src为真实图片地址
            img.src = item.dataset.original;
            
            // 图片加载成功的回调
            img.onload = function() {
              // 将真实图片地址赋值给DOM中的img标签
              item.src = item.dataset.original;
              // 移除data-original属性(避免重复加载)
              item.removeAttribute('data-original');
              // 移除lazyload标记(优化后续遍历性能)
              item.removeAttribute('lazyload');
            };
            
            // 注:代码中未处理onerror事件,实际应用中建议添加错误处理
          })();
        }
      });
    };
    
    // 监听滚动事件,滚动时触发懒加载检查
    window.addEventListener('scroll', lazyload);
    // DOM加载完成后立即检查一次(处理首屏可见的图片)
    document.addEventListener('DOMContentLoaded', lazyload);
  </script>
</body>
</html>
  1. Array.prototype.forEach
    • 数组原生的forEach方法,用于遍历数组元素。
  2. .call()
    • JavaScript 的函数上下文绑定方法,格式为func.call(thisArg, arg1, arg2, ...)
    • 作用:将forEach方法的执行上下文(this)指向第一个参数,并传递后续参数。
  3. eles
    • 一个类数组对象(如NodeListHTMLCollection或带有length属性的对象),非真正的数组。
  4. callback
    • 遍历每个元素时执行的回调函数,参数为(item, index, array)

核心原理分段解析

1. 页面结构与标记设计

  • 占位图与真实图分离

    • src属性指向小尺寸占位图(如 loading.gif 或透明图),避免浏览器首次加载大图片。
    • data-original存储真实图片 URL,通过自定义属性实现数据与 DOM 的解耦。
  • 懒加载标记

    • lazyload="true"属性用于标识需要懒加载的图片,便于 JS 筛选目标元素。

2. 视口检测机制

  • 核心 APIgetBoundingClientRect()

    • 返回值包含topbottom等属性,表示元素相对于视口的位置。
    • rect.bottom >= 0rect.top < viewHeight时,认为元素在视口可见范围内。
  • 视口高度获取

    • document.documentElement.clientHeight获取浏览器可视区域高度,用于判断元素是否进入视口。

3. 图片预加载逻辑

  • 内存预加载

    • 通过new Image()创建独立的图片对象,在内存中提前加载图片
    • 避免直接修改 DOM 元素的src,防止因图片加载慢导致的页面闪烁。
  • 事件回调处理

    • onload事件触发时,将真实图片地址赋值给 DOM 元素,并移除标记属性。
    • 优化点:加载完成后移除data-originallazyload,减少后续遍历的无效检查。

4. 事件监听与触发时机

  • 滚动事件监听

    • window.addEventListener('scroll', lazyload)在滚动时检查图片是否进入视口。
    • 注意:此实现未使用节流函数,高频滚动时可能触发多次函数调用,实际应用中建议优化。
  • 初始加载检查

    • DOMContentLoaded事件触发时立即执行一次懒加载,确保首屏可见图片被加载。

三、总结

懒加载实现基于以下核心技术:

  1. 延迟加载:通过占位图和自定义属性分离真实资源,避免首次加载时请求大图片。
  2. 视口检测:利用getBoundingClientRect()判断元素是否在可视区域,决定是否加载资源。
  3. 内存预加载:使用new Image()在内存中提前加载图片,减少 DOM 渲染阻塞。
  4. 事件驱动:通过滚动事件和 DOM 加载事件触发懒加载逻辑,实现按需加载。

这种方案在传统浏览器中兼容性较好,但需注意滚动事件的性能优化,建议结合节流函数或现代IntersectionObserver API 进一步提升性能。