Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lazyload #50

Open
webfansplz opened this issue Nov 1, 2020 · 0 comments
Open

lazyload #50

webfansplz opened this issue Nov 1, 2020 · 0 comments

Comments

@webfansplz
Copy link
Owner

webfansplz commented Nov 1, 2020

仓库:

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 使用有了更进一步的理解

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant