JavaScript 实现图片延迟加载的三种原生方法详解

陆遥
陆遥
发布时间:2026-10-13 | 6人浏览过
原创

图片延迟加载(Lazy Load)是前端性能优化的重要手段,尤其适用于图片较多的长页面。其核心原理是:页面初始只加载可视区域内的图片,当用户滚动页面时,检测到目标图片进入可视区域后,再将其真实地址赋值给 src 属性进行加载。本文将详细解析三种基于原生 JavaScript 的实现方案。

基础方法:监听滚动与 DOM 遍历

这是最直观的实现方式。通过监听 window 的 scroll 事件,获取当前滚动高度和屏幕可视高度,遍历所有标记为需延迟加载的图片元素,计算其距离页面顶部的偏移量。若该偏移量小于“滚动高度 + 屏幕高度”,说明图片已进入可视范围,此时将自定义属性(如 alt 或 data-src)的值赋给 src 并移除标记类名。

<script type="text/javascript">
var temp = -1; // 记录上次滚动位置,用于判断滚动方向
window.onscroll = function() {
    var imgElements = document.getElementsByTagName("img");
    var lazyImgArr = [];
    
    // 筛选出带有 'lazy' 类的图片
    for(var i=0; i<imgElements.length; i++) {
        if(imgElements[i].className == "lazy"){
            lazyImgArr.push(imgElements[i]);
        }
    }
    
    var scrollHeight = document.body.scrollTop || document.documentElement.scrollTop;
    var bodyHeight = document.documentElement.clientHeight;
    
    // 仅向下滚动时才执行检测
    if(temp < scrollHeight) {
        for(var k=0; k<lazyImgArr.length; k++) {
            var imgTop = lazyImgArr[k].offsetTop;
            // 判断图片顶部是否在当前可视区域内
            if((imgTop - scrollHeight) <= bodyHeight) {
                // 将真实图片路径赋给 src
                lazyImgArr[k].src = lazyImgArr[k].alt;
                lazyImgArr[k].className = "notlazy";
            }
        }
        temp = scrollHeight;
    }
};
</script>

关键点:需要确保图片初始 src 为一个极小的占位图,真实路径可存储在 alt 或其他自定义属性中。此外,必须处理向上滚动的情况以避免重复计算。

进阶封装:IIFE 模式与队列管理

为了提升代码的可维护性和复用性,可以将逻辑封装在一个立即执行函数表达式(IIFE)中。该方法使用 map_element 对象,以图片距文档顶部的绝对距离作为 key,保存待加载元素的索引数组。这种方式将元素按垂直位置分组,便于批量处理。

lazyLoad = (function() {
  var map_element = {};   // { position: [index1, index2] }
  var element_obj = [];   // 存储所有待加载元素引用
  var download_count = 0; // 剩余待加载数量
  var last_offset = -1;   // 上次处理的视口底端位置
  var doc_body;
  var doc_element;
  var lazy_load_tag = ["img", "iframe"];

  function initVar() {
    doc_body = document.body;
    doc_element = document.compatMode == 'BackCompat' ? doc_body : document.documentElement;
  };

  function getAbsoluteTop(element) {
    var offsetTop = element.offsetTop;
    while (element = element.offsetParent) {
      offsetTop += element.offsetTop;
    }
    return offsetTop;
  }

  function initElementMap() {
    var all_elements = [];
    // 收集所有带 lazy_src 属性的元素
    for (var i = 0; i < lazy_load_tag.length; i++) {
      var el = document.getElementsByTagName(lazy_load_tag[i]);
      for (var j = 0; j < el.length; j++) {
        if (el[j].getAttribute("lazy_src")) {
          all_elements.push(el[j]);
        }
      }
    }
    
    // 按绝对位置分组
    for (var i = 0; i < all_elements.length; i++) {
      var o_img = all_elements[i];
      var t_index = getAbsoluteTop(o_img);
      element_obj.push(o_img);
      
      if (!map_element[t_index]) {
        map_element[t_index] = [];
        download_count++;
      }
      map_element[t_index].push(i);
    }
  };

  function checkAndDownload() {
    if (download_count === 0) return;
    
    var scrollTop = window.pageYOffset !== undefined ? window.pageYOffset : (document.compatMode=="CSS1Compat" ? document.documentElement.scrollTop : document.body.scrollTop);
    var clientHeight = document.documentElement.clientHeight;
    var viewBottom = scrollTop + clientHeight;

    // 如果视口底端没有变化,避免频繁触发
    if (last_offset == viewBottom) {
      setTimeout(checkAndDownload, 200);
      return;
    }
    last_offset = viewBottom;

    // 检查并加载进入视口的元素
    for (var key in map_element) {
      if (viewBottom > key) { // key 是元素顶部位置
        var indices = map_element[key];
        for (var l = 0; l < indices.length; l++) {
          var target = element_obj[indices[l]];
          target.src = target.getAttribute("lazy_src");
        }
        delete map_element[key];
        download_count--;
      }
    }
    setTimeout(checkAndDownload, 200);
  };

  function init() {
    initVar();
    initElementMap();
    checkAndDownload();
  };

  return { init: init };
})();

// 使用方式:页面底部调用 lazyLoad.init();

优势:通过定时器递归调用而非直接绑定 scroll 事件,结合 last_offset 判断,有效减少了高频事件触发的开销。同时支持 iframe 等多类媒体元素的延迟加载。

优化方案:IntersectionObserver 思维的原生简化版

虽然 IntersectionObserver API 是现代浏览器推荐的方式,但在不支持的环境或需要兼容旧版环境时,我们可以采用更简洁的事件委托思路。此方法利用 data-url 属性存储真实图片链接,初始 src 为占位图。通过初始化检查和滚动监听相结合,确保首屏图片不被遗漏。

<script type="text/javascript">
(function(){
    var v = {};
    
    // 获取元素集合
    function tagName(tagName){
        return document.getElementsByTagName(tagName);
    }
    
    // 统一添加事件监听
    function addEvent(obj, type, func){
        if(obj.addEventListener){
            obj.addEventListener(type, func, false);    
        } else if(obj.attachEvent){
            obj.attachEvent('on'+type, func);
        }
    }

    function loadImages(elements) {
        var screenHeight = document.documentElement.clientHeight;
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        var limitHeight = scrollTop + screenHeight;

        for(var i=0; i<elements.length; i++){
            var img = elements[i];
            // 判断是否在可视区域且有未加载标志
            if(img.offsetTop <= limitHeight && img.getAttribute('data-url')){
                img.setAttribute('src', img.getAttribute('data-url'));
                img.removeAttribute('data-url');
            }
        }
    }

    // 初始化:加载首屏可见图片
    function init(){
        var imgs = tagName('img');
        loadImages(imgs);
        // 后续滚动继续加载
        var scrollTimer = null;
        addEvent(window,'scroll', function(){
            clearTimeout(scrollTimer);
            scrollTimer = setTimeout(function(){
                loadImages(imgs);
            }, 150); // 简单的节流处理
        });
    }
    
    init();
})()
</script>

注意事项:在动态切换内容(如 Tab 页签切换)的场景下,新显示区域的图片可能不会自动触发加载。此时需要在内容切换完成后,手动执行类似 target.find("img[data-url]").each(...) 的处理逻辑,即时渲染新插入的图片。

总结

以上介绍了三种不同复杂度的 JavaScript 图片延迟加载实现方案。从基础的 DOM 遍历到模块化封装,再到针对动态内容的处理技巧,开发者可根据项目实际需求选择合适的方法。无论采用哪种方案,核心都在于准确判断元素与视口的位置关系,并在合适的时机触发资源加载。

以上就是 JavaScript 实现图片延迟加载方法汇总的详细内容,更多关于 JavaScript 前端优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表