前端排坑指南(六)

文章详细解释了jQuery-lazyload插件的工作原理,包括初始化配置、合并用户设置、滚动监听、图片元素的处理以及如何利用数据属性动态赋值实现图片的延迟加载。此外,还介绍了如何处理隐藏元素和不同触发事件,以及提供了相关事件和函数的调用时机。
摘要由CSDN通过智能技术生成

lazyload 懒加载 详解

突然想了解jquery-lazyload的原理,就巴拉巴拉了代码。

帮助更好的了解 lazyload.js vue-lazyload

源码设计思路如下:

  1. 初始化配置默认值

  2. 配置默认值和传入的配置进行按需合并

  3. 设置滚动监听对象

  4. 遍历渲染图片元素

  5. 每个元素定义加载状态为false

  6. 如果元素是img,并且没有绑定src, 则赋值src为占位loading提示图片

  7. 给图片元素绑定一次appear渲染完成事件。

    • 如果配置了渲染前执行函数,执行渲染前执行函数
    • 新创建一个img元素,并绑定加载完成事件load
    • 然后给新建img元素设置src为图片元素的图片链接地址,加载完成函数load触发
    • 再次获取图片链接地址,隐藏图片元素,设置图片元素src为图片链接地址,使用配置动画显示图片元素
    • 修改加载状态为true,筛选删除掉加载过的图片元素,获取新的元素遍历列表
    • 如果配置了渲染后执行函数,执行渲染后执行函数
      (原理:利用动态获取data-值得,动态赋值src实现图片懒加载)
  8. 如果设置得触发事件不是滚动事件,则事件绑定在图片元素上面

  9. 创造滚动监听方法,

  10. 如果监听方法是滚动,绑定给监听元素,窗口大小改变事件,文档加载完成事件

  11. 滚动监听方法

  12. 遍历渲染图片元素

  13. 如果图片元素存在隐藏,根据配置判断是否加载隐藏得元素图片

  14. 通过滚动距离逻辑判断是否执行 4.3 步骤绑定得事件

  15. 滚动距离监听逻辑(竖向滚动和横向滚动距离逻辑完全一样)

    • 竖向滚动距离 大于 (图片到窗口顶部得距离 + 额外加载距离 + 元素本身高度) 时,不进行渲染

      此时元素初始化已经滚动到上面去了,并没有在页面可视区域内

    • 竖向滚动距离 小于 (图片到窗口顶部的距离 -额外加载距离) 时,不进行渲染
      此时元素初始化已经滚动到下面去了,并没有在页面可视区域内

  16. 额外距离:可以立即为给图片上方多加一截高度。

  17. 如果图片元素在可视区域上面,然后图片元素又设置了一些定位等属性导致元素出现高度偏差,可以设置每次加载数量,解决这个问题。

  18. 扩展jquery的方法

源码已经中文注释解析:(源码仅有部分结构顺序的改动,帮助代码理解)

/*!
 * Lazy Load - jQuery plugin for lazy loading images
 *
 * Copyright (c) 2007-2015 Mika Tuupola
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Project home:
 *   http://www.appelsiini.net/projects/lazyload
 *
 * Version:  1.9.7
 *
 */

(function ($, window, document, undefined) {
  var $window = $(window);

  // 扩展lazyload方法
  $.fn.lazyload = function (options) {
    // 初始化变量和默认值
    var elements = this; // 获取到的全部img集合
    var $container; // 监听对象
    var settings = {
      threshold: 0,
      failure_limit: 0,
      /*
        触发的事件,默认scroll.
        如果设置的事件是scroll,则绑定到监听对象上(container)
        否则,设置到给一个img元素上
      */
      event: "scroll", // 图片加载动画
      // effect_speed: null, // 加载动画执行事件, 默认没有设置
      effect: "show",
      container: window, // 滚动监听对象,默认window
      data_attribute: "original", // 设置获取src路径的data域名
      skip_invisible: false, // 如果元素以及设置display:none, 那么是否还继续加载图片。默认false继续加载
      appear: null, // 每张图片加载前回调
      load: null, // 每张图片加载后回调

      // 加载loading时,占位的loading图片
      placeholder:
        "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC",
    };

    // 把传入配置与初始化配置进行合并
    if (options) {
      /* Maintain BC for a couple of versions. */
      if (undefined !== options.failurelimit) {
        options.failure_limit = options.failurelimit;
        delete options.failurelimit;
      }
      if (undefined !== options.effectspeed) {
        options.effect_speed = options.effectspeed;
        delete options.effectspeed;
      }

      $.extend(settings, options);
    }

    // 设置滚动监听对象,没有设置默认监听windo
    /* Cache container as jQuery as object. */
    $container =
      settings.container === undefined || settings.container === window
        ? $window
        : $(settings.container);

    // 遍历获取到的img元素集合
    elements.each(function () {
      // 每个img元素
      var self = this;
      var $self = $(self);

      // 设置img的加载状态为false
      self.loaded = false;

      // 设置loading图片
      /*
        如果直接设置img的scr,就会立即获取图片。
        但是设置到data-属性,就可以等待到触发条件后,在设置到src属性里面,完成加载
      */
      /* If no src attribute given use data:uri. */
      if ($self.attr("src") === undefined || $self.attr("src") === false) {
        // 进行一次img元素检测
        if ($self.is("img")) {
          // 设置所有没有设置src的img,都是渲染图片。
          $self.attr("src", settings.placeholder);
        }
      }

      // 定义appear加载完成事件,并给每个图片都绑定一次
      /* When appear is triggered load original image. */
      $self.one("appear", function () {
        if (!this.loaded) {
          // 如果设置了加载前函数,调用设置的加载前函数
          if (settings.appear) {
            var elements_left = elements.length;
            settings.appear.call(self, elements_left, settings);
          }

          // 创建一个img,给这个img绑定bing函数,并获取data-的值赋值给src
          // 利用新的img标签的渲染逻辑,渲染加载图片的
          $("<img />")
            .bind("load", function () {
              // 再次获取data-的预设url
              var original = $self.attr("data-" + settings.data_attribute);

              // 设置隐藏
              $self.hide();

              // 再次判断是否是图片,如果不是,设置背景图片地址
              if ($self.is("img")) {
                $self.attr("src", original);
              } else {
                $self.css("background-image", "url('" + original + "')");
              }

              // 使用设置的动画效果和动画速度,把图片显示出来
              $self[settings.effect](settings.effect_speed);

              // 设置img的加载状态为true,下一次就不再加载了
              self.loaded = true;

              // 过滤掉加载完成的图片,重新赋值加载img元素列表
              /* Remove image from array so it is not looped next time. */
              var temp = $.grep(elements, function (element) {
                return !element.loaded;
              });
              elements = $(temp);

              // 如果设置了加载后函数,调用加载后函数
              if (settings.load) {
                var elements_left = elements.length;
                settings.load.call(self, elements_left, settings);
              }
            })
            .attr("src", $self.attr("data-" + settings.data_attribute));
        }
      });

      // 如果设置的不是scroll事件,把设置的事件绑定到img元素上
      /* When wanted event is triggered load original image */
      /* by triggering appear.                              */
      if (0 !== settings.event.indexOf("scroll")) {
        $self.bind(settings.event, function () {
          if (!self.loaded) {
            // 手动触发appear事件
            $self.trigger("appear");
          }
        });
      }
    });

    // 兼容iphone
    /* With IOS5 force loading images when navigating with back button. */
    /* Non optimal workaround. */
    if (/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)) {
      $window.bind("pageshow", function (event) {
        if (event.originalEvent && event.originalEvent.persisted) {
          elements.each(function () {
            $(this).trigger("appear");
          });
        }
      });
    }

    // 窗口滚动, 改变。文档加载完成后执行的函数
    function update() {
      var counter = 0;

      // 遍历img元素
      elements.each(function () {
        var $this = $(this);

        // 如果元素已经设置隐藏了,然后根据配置项判断是否不进行加载。默认false,进行加载
        // 如果是非滚动事件,则无法操作隐藏的元素事件,所以不需要在那边进行判断
        if (settings.skip_invisible && !$this.is(":visible")) {
          return;
        }

        console.log(this, "------------each");

        // 判断是否到达显示位置
        /* 
          滚动距离 大于 (图片元素距离顶部 + 设置的显示距离 + 元素自身距离) 不进行渲染
          滚动距离 小于 (图片元素距离顶部 + 设置的显示距离) 不进行渲染
          如果存在定位等印象渲染数量效果的情况,可以设置最大渲染数量解决
        */
        if ($.abovethetop(this, settings) || $.leftofbegin(this, settings)) {
          /* Nothing. */
        } else if (
          !$.belowthefold(this, settings) &&
          !$.rightoffold(this, settings)
        ) {
          $this.trigger("appear");
          /* if we found an image we'll load, reset the counter */
          counter = 0;
        } else {
          if (++counter > settings.failure_limit) {
            return false;
          }
        }
      });
    }

    // 如果设置的事件是scroll, 给监听对象绑定scroll方法
    /* Fire one scroll event per scroll. Not one scroll event per image. */
    if (0 === settings.event.indexOf("scroll")) {
      $container.bind(settings.event, function () {
        return update();
      });
    }

    // 监听窗口大小改变
    /* Check if something appears when window is resized. */
    $window.bind("resize", function () {
      update();
    });

    /* Force initial check if images should appear. */
    $(document).ready(function () {
      update();
    });

    return this;
  };

  /* Convenience methods in jQuery namespace.           */
  /* Use as  $.belowthefold(element, {threshold : 100, container : window}) */

  $.belowthefold = function (element, settings) {
    var fold;

    if (settings.container === undefined || settings.container === window) {
      fold =
        (window.innerHeight ? window.innerHeight : $window.height()) +
        $window.scrollTop();
    } else {
      fold =
        $(settings.container).offset().top + $(settings.container).height();
    }

    return fold <= $(element).offset().top - settings.threshold;
  };

  $.rightoffold = function (element, settings) {
    var fold;

    if (settings.container === undefined || settings.container === window) {
      fold = $window.width() + $window.scrollLeft();
    } else {
      fold =
        $(settings.container).offset().left + $(settings.container).width();
    }

    return fold <= $(element).offset().left - settings.threshold;
  };

  // 根据图片到顶部的距离 和 竖向滚动距离 大小关系,判断是否加载
  $.abovethetop = function (element, settings) {
    var fold;

    // 获取滚动监听对象的滚动距离,并赋值给fold
    if (settings.container === undefined || settings.container === window) {
      fold = $window.scrollTop();
    } else {
      fold = $(settings.container).offset().top;
    }

    // 返回 竖向滚动距离 > 图片距顶部的距离 + 设置显示
    return (
      fold >= $(element).offset().top + settings.threshold + $(element).height()
    );
  };

  // 根据图片到右部的距离 和 横向滚动距离 大小关系,判断是否加载
  $.leftofbegin = function (element, settings) {
    var fold;

    if (settings.container === undefined || settings.container === window) {
      fold = $window.scrollLeft();
    } else {
      fold = $(settings.container).offset().left;
    }

    // 返回 横向滚动距离 > 图片距顶部的距离 + 设置显示
    return (
      fold >= $(element).offset().left + settings.threshold + $(element).width()
    );
  };

  $.inviewport = function (element, settings) {
    return (
      !$.rightoffold(element, settings) &&
      !$.leftofbegin(element, settings) &&
      !$.belowthefold(element, settings) &&
      !$.abovethetop(element, settings)
    );
  };

  /* Custom selectors for your convenience.   */
  /* Use as $("img:below-the-fold").something() or */
  /* $("img").filter(":below-the-fold").something() which is faster */

  $.extend($.expr[":"], {
    "below-the-fold": function (a) {
      return $.belowthefold(a, { threshold: 0 });
    },
    "above-the-top": function (a) {
      return !$.belowthefold(a, { threshold: 0 });
    },
    "right-of-screen": function (a) {
      return $.rightoffold(a, { threshold: 0 });
    },
    "left-of-screen": function (a) {
      return !$.rightoffold(a, { threshold: 0 });
    },
    "in-viewport": function (a) {
      return $.inviewport(a, { threshold: 0 });
    },
    /* Maintain BC for couple of versions. */
    "above-the-fold": function (a) {
      return !$.belowthefold(a, { threshold: 0 });
    },
    "right-of-fold": function (a) {
      return $.rightoffold(a, { threshold: 0 });
    },
    "left-of-fold": function (a) {
      return !$.rightoffold(a, { threshold: 0 });
    },
  });
})(jQuery, window, document);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值