webfansplz / article

record and share

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

lazyload

webfansplz opened this issue · comments

commented

仓库:

lazyload-Vanilla JavaScript plugin for lazyloading images

源码实现:

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

(function (root, factory) {
  // umd export
  if (typeof exports === "object") {
    module.exports = factory(root);
  } else if (typeof define === "function" && define.amd) {
    define([], factory);
  } else {
    root.LazyLoad = factory(root);
  }
})(
  typeof global !== "undefined" ? global : this.window || this.global,
  function (root) {
    "use strict";

    if (typeof define === "function" && define.amd) {
      root = window;
    }

    const defaults = {
      src: "data-src",
      srcset: "data-srcset",
      selector: ".lazyload",
      root: null,
      rootMargin: "0px",
      threshold: 0,
    };

    /**
     * Merge two or more objects. Returns a new object.
     * @private
     * @param {Boolean}  deep     If true, do a deep (or recursive) merge [optional]
     * @param {Object}   objects  The objects to merge together
     * @returns {Object}          Merged values of defaults and options
     */
    const extend = function () {
      let extended = {};
      let deep = false;
      let i = 0;
      let length = arguments.length;

      /* Check if a deep merge */
      if (Object.prototype.toString.call(arguments[0]) === "[object Boolean]") {
        deep = arguments[0];
        i++;
      }

      /* Merge the object into the extended object */
      let merge = function (obj) {
        for (let prop in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, prop)) {
            /* If deep merge and property is an object, merge properties */
            if (
              deep &&
              Object.prototype.toString.call(obj[prop]) === "[object Object]"
            ) {
              extended[prop] = extend(true, extended[prop], obj[prop]);
            } else {
              extended[prop] = obj[prop];
            }
          }
        }
      };

      /* Loop through each object and conduct a merge */
      for (; i < length; i++) {
        let obj = arguments[i];
        merge(obj);
      }

      return extended;
    };

    function LazyLoad(images, options) {
      // merget default options and options
      this.settings = extend(defaults, options || {});
      this.images = images || document.querySelectorAll(this.settings.selector);
      this.observer = null;
      this.init();
    }

    LazyLoad.prototype = {
      init: function () {
        /* Without observers load everything and bail out early. */
        // 不支持IntersectionObserver API,直接加载图片
        if (!root.IntersectionObserver) {
          this.loadImages();
          return;
        }

        let self = this;
        let observerConfig = {
          root: this.settings.root, // 祖先元素,null或未设置,默认使用顶级文档元素
          rootMargin: this.settings.rootMargin, // 计算交叉时添加到根(root)边界盒bounding box的矩形偏移量
          threshold: [this.settings.threshold], // 阀值(0-1),监听对象的交叉区域与边界区域的比率
        };
        // 使用IntersectionObserver API观察目标元素与root元素的交叉状态
        this.observer = new IntersectionObserver(function (entries) {
          Array.prototype.forEach.call(entries, function (entry) {
            // 目标元素与root元素交叉区域超过threshold阀值
            if (entry.isIntersecting) {
              // 停止监听
              self.observer.unobserve(entry.target);
              // 目前元素 src等属性赋值
              let src = entry.target.getAttribute(self.settings.src);
              let srcset = entry.target.getAttribute(self.settings.srcset);
              // img元素对src赋值,否则对backgroundImage赋值
              if ("img" === entry.target.tagName.toLowerCase()) {
                if (src) {
                  entry.target.src = src;
                }
                if (srcset) {
                  entry.target.srcset = srcset;
                }
              } else {
                entry.target.style.backgroundImage = "url(" + src + ")";
              }
            }
          });
        }, observerConfig);
        Array.prototype.forEach.call(this.images, function (image) {
          // 对目标元素进行监听
          self.observer.observe(image);
        });
      },
      //  加载后销毁
      loadAndDestroy: function () {
        if (!this.settings) {
          return;
        }
        this.loadImages();
        this.destroy();
      },
      // 加载图片
      loadImages: function () {
        if (!this.settings) {
          return;
        }

        let self = this;
        Array.prototype.forEach.call(this.images, function (image) {
          let src = image.getAttribute(self.settings.src);
          let srcset = image.getAttribute(self.settings.srcset);
          if ("img" === image.tagName.toLowerCase()) {
            if (src) {
              image.src = src;
            }
            if (srcset) {
              image.srcset = srcset;
            }
          } else {
            image.style.backgroundImage = "url('" + src + "')";
          }
        });
      },
      // 销毁
      destroy: function () {
        if (!this.settings) {
          return;
        }
        // 解除监听
        this.observer.disconnect();
        this.settings = null;
      },
    };
    // window.lazyload register
    root.lazyload = function (images, options) {
      return new LazyLoad(images, options);
    };
    // jquery plugin register
    if (root.jQuery) {
      const $ = root.jQuery;
      $.fn.lazyload = function (options) {
        options = options || {};
        options.attribute = options.attribute || "data-src";
        new LazyLoad($.makeArray(this), options);
        return this;
      };
    }

    return LazyLoad;
  }
);

收获:

lazyload2.x 版本的核心实现主要是使用了 IntersectionObserver API。

IntersectionObserver 接口提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。可以很简单优雅的判断目标元素是否出现在可视区域内,从而进行处理。

IntersectionObserver API 的兼容性在 IE 全军覆没,但是 w3c 实现了IntersectionObserver polyfill,使得 IE 可以兼容到 7+,真香!

  • 深入了解了 layload 2.x 实现

  • 对 IntersectionObserver API 使用有了更进一步的理解