最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 图片懒加载竟如此简单

    正文概述 掘金(前端橘子君)   2020-12-10   699

    这两天在看JavaScript的API时发现了这样一个东西IntersectionObserver(准确说它是浏览器原生提供的一个异步API),它能够对一个元素进行监听,判断该元素是否在当前窗口中。

    好官方的描述!!!

    不用管它,简单点说,IntersectionObserver就是观察一个元素是否在当前窗口中。

    注意事项:该API是异步的

    使用方法

    /**
     * @param {Function} callback 必填项,监听元素进入窗口时的回调函数
     * @param {Object} options 非必填项,配置参数
     * @param {Dom Element} options.root 监听元素的根元素,默认为全文档
     * @param {String} options.rootMargin 元素到根元素边缘时的矩形偏移量,支持 px 和 %
     * @param {Array} options.thresholds 交叉区域与边界区域的比例
    */
    
    var io = new IntersectionObserver(callback, options);
    
    io.observe(document.querySelector('img')); // 开始观察,接受一个DOM节点对象
    io.unobserve(element); // 停止观察 接受一个element元素
    io.disconnect(); // 关闭观察器
    

    懵了么?还好,API的方法和属性比较少,先来撸一个案例你就明白怎么用了。

    案例

    <div id='app'>
      <img src='./images/01.png'>
      <img src='./images/01.png' >
      <img src='./images/01.png' >
      <img src='./images/01.png' >
      <img src='./images/01.png' >
      <img src='./images/01.png' >
    </div>
    
    <script>
      // 创建IntersectionObserver实例
      const io = new IntersectionObserver(function (entrys) {
        console.log(entrys);
      }, {threshold: [1]});
    
      function update () {
        const els = document.querySelectorAll('img');
        els.forEach(ele => {
          io.observe(ele); // 对元素进行监听
        });
      }
    
      update();
    </script>
    

    此时,控制台会打印出监听的列表,注意列表中的每个元素都是一个监听器,其中target属性就是监听的元素,isIntersecting就是该元素是否在窗口内的标识。

    图片懒加载

    首先要明确我们的懒加载要有什么内容?

    • 1、默认占位图
    • 2、img元素上具有真实地址属性,如data-src
    • 3、我们刚刚看到的options.thresholds,即交叉区域与边界区域的比例
    class InterObser {
      constructor (attr = 'data-src', defaultImg, options = {}) {
        this.attr = attr;
        this.defaultImg = defaultImg;
        this.options = options;
        
        this.install();
        this.update();
      }
      install () {
        const that = this;
        // 初始化 IntersectionObserver 实例
        that.io = new IntersectionObserver(function (entrys) {
          // 当监听到元素进入窗口时,循环每个监听器
          entrys.forEach(ele => {
            // 如果该监听器进入窗口,则将真实地址替换占位图
            if (ele.isIntersecting) {
              that.imgLoad(ele);
              // 停止对该监听器的监听,防止多次操作
              that.io.unobserve(ele.target);
            }
          });
        }, that.options);
      }
      // 图片加载
      imgLoad (ele) {
        const url = ele.target.dataset.src;
        ele.target.src = url;
      }
      update () {
        const els = document.querySelectorAll(`[${this.attr}]`);
        // 为每个监听元素设置默认值
        els.forEach(ele => {
          ele.src = this.defaultImg;
          // 开始监听
          this.io.observe(ele);
        });
      }
    }
    
    // 调用
    new InterObser('data-src', './images/01.png', {threshold: [1]});
    

    自建vue懒加载插件

    既然我们知道了用IntersectionObserver做图片懒加载,那么我们是否可以将该功能做成插件呢?我目前的项目主要以vue为主,那么我就将其做成vue插件。

    src目录下创建lib/index.js文件。

    class InterObser {
      install (Vue, options) {
        const that = this;
        // 创建指令,使用方法是v-lazy='http://xxx'
        Vue.directive('lazy', {
          bind(el, binding) {
            // 为元素添加默认占位图
            that.init(el, binding.value, options.defaultSrc);
            // 创建IntersectionObserver实例
            that.initObserve(options);
          },
          inserted (el, binding) {
            that.observe(el, binding); // 对该元素开启监听
          }
        })
      }
      init (el, val, def) {
        // 将v-lazy的值挂载到该元素上,因为编译后元素上并没有v-lazy属性
        el.setAttribute('data-src', val)
        // 设置默认占位符
        el.src = def
      }
      initObserve (options) {
        // 该只需创建一次
        var io = new IntersectionObserver(function (entrys) {
          // 循环每个监听的元素,当该元素进入窗口,则替换真实地址,并取消监听
          entrys.forEach(entry => {
            if (entry.isIntersecting) { // 当isIntersecting为true,则代表该元素进入了窗口
              const el = entry.target;
              el.src = el.dataset.src; // 用真实地址替换占位符
              el.removeAttribute('data-src')); // 移除多余的data-src属性
              setTimeout(function () {
                io.unobserve(el); // 取消监听
              }, 0);
            }
          })
        }, options.interOptions || {});
    
        this.io = io;
      }
      observe (el) {
        this.io.observe(el);
      }
    }
    
    export default new InterObser();
    

    使用

    main.js中进行配置

    import Vue from 'vue';
    import LazyLoad from './lib/index.js';
    // 全局配置
    Vue.use(LazyLoad, {
      defaultSrc: 'http://localhost:3000/01.jpg',
      // IntersectionObserver原生配置项
      interOptions: {
        threshold: [1]
      }
    });
    

    在组件中使用

    <template>
      <div>
        <img v-lazy='lazySrc'>
        <img v-lazy='lazySrc'>
        <img v-lazy='lazySrc'>
        <img v-lazy='lazySrc'>
        <img v-lazy='lazySrc'>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
          lazySrc: 'http://localhost:3000/02.jpg'
        };
      }
    };
    </script>
    

    是不是非常简单,我们来看看传统的图片懒加载是怎么实现的。

    传统的图片懒加载

    传统图片懒加载主要是依赖监听scroll和获取getBoundingClientRect().topgetBoundingClientRect().bottom来做的,但是这里涉及到性能问题,比如getBoundingClientRect会引起回流等,我们能够做的就是减少这些性能上的操作。

    我们可以将上述插件做一下优化,当浏览器不支持IntersectionObserver时,我们就采用传统懒加载的方式。

    class InterObser {
      install () {
        .....
      }
      initObserve (options) {
        // 多了一个判断条件而已
        // 仅当浏览器支持该API时才初始化
        if (IntersectionObserver) {
          var io = new IntersectionObserver(function () {
            .......
          }, options.interOptions || {});
          this.io = io;
        }
      }
      observe (el) {
        // 如果浏览器支持IntersectionObserver,则使用,否则使用传统懒加载方式
        if (IntersectionObserver) {
          this.io.observe(el);
        } else {
          this.listenerScroll(el);
        }
      }
      listenerScroll (el) {
        const that = this;
        that.load(el); // 为了使当前窗口的图片能够正常加载
        window.addEventListener('scroll', function (e) {
          // 防止多次触发
          // 减少getBoundingClientRect操作从而达到优化的目的
          if (el.dataset.src) {
            that.load(el);
          }
        });
      }
      load (el) {
        // 获取窗口高度
        var docHeight = document.documentElement.clientHeight;
        var boundingClientRect = el.getBoundingClientRect();
        var bottom = boundingClientRect.bottom;
        var top = boundingClientRect.top;
        // 当元素进入窗口时,才加载真实图片
        if (top < docHeight && bottom > 0) {
          el.src = el.dataset.src;
          el.removeAttribute('data-src');
        }
      }
    }
    

    遗留问题

    这里还遗留几个问题

    • 当用户传入了options.interOptions时没有做兼容处理
    • 除了判断el.dataset.src进行优化之外还可配合节流函数
    • 默认图片和v-lazy中的图片只能是网络图片

    这几个问题后期慢慢补充,个人觉得可以完善这个插件,并且不止限于img标签,还可以对video等进行懒加载。


    更多相关文档,请见:

    线上地址 【前端橘子君】

    GitHub仓库【前端橘子君】


    起源地下载网 » 图片懒加载竟如此简单

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元