Skip to content

实现图片懒加载的两种方案 #33

Description

@cookiepool

常见的懒加载方案

这个方案是借助滚动距离来判断的,获取document.documentElement的scrollTop与需要懒加载的图片标签的offsetTop进行比较,看图片是否进入可视区域,从而实现图片加载。

代码如下:

// 封装一个获取图片到顶部的距离
function getTop(elm){
  let tempTop = elm.offsetTop;
  //如果包裹img的父元素离顶部也有距离,那么也要把它加上
  while(elm = elm.offsetParent) { //注意这儿offsetParent返回的是元素不是数值,同时这儿是赋值语句
    tempTop += elm.offsetTop;
  }
  return tempTop;
}

function lazyLoad(imgs){
  //获取可视区域高度
  let viewHeight = document.documentElement.clientHeight || window.innerHeight;
  //获取滚动高度
  let scrollTopVal = document.documentElement.scrollTop || document.body.scrollTop;
  for(var i = 0; i < imgs.length; i++){
      if((viewHeight + scrollTopVal) > getTop(imgs[i])){
        imgs[i].src = imgs[i].getAttribute('data-src')
      }
  }
}

// 使用
// 给你需要懒加载的图片设置一个类名'lazy-load'
let imgs = document.querySelectorAll('.lazy-load');
lazyLoad(imgs);
// 进行滚动监听
window.onscroll = function() {
  lazyLoad(imgList);
}

使用交叉观察者实现懒加载

浏览器自带的API IntersectionObserver进行检测,十分方便

先贴上代码实现:

// 给你需要懒加载的图片设置一个类名'lazy-load'
let imgs = document.querySelectorAll('.lazy-load');

// 实例化一个交叉观察者对象
let obsever = new IntersectionObserver((entries) => {
  entries.forEach((item, index, arrSelf) => {
    // 判断检测的对象是否进入可视窗口
    if(item.isIntersecting) {
      // 进入后进行图片地址赋值
      item.target.src = item.target.dataset.src;
      // 实现图片加载后不在进行检测
      obsever.unobserve(item.target);
    }
  });
});

// 注册观察需要懒加载的img
imgs.forEach((item) => {
  obsever.observe(item);
});

IntersectionObserver

用法

new IntersectionObserver(callback, options);
  • callback 是一个回调函数,接受一个参数,返回当前已监听并且发生了交叉的目标集合。
  • options 配置参数,可选,是一个对象。
new IntersectionObserver((entries) => {

}, {
  // options参数
});

entries当前已监听并且发生了交叉的目标集合,我们可以把它用来循环,entres的打印结果如下:

参数表,对于entries这个集合里面的每个对象都有如下常用的参数:

  • entries下的对象

| 属性 | 说明 |
| - | - | - |
| boundingClientReact | 元素的空间信息,就是位置信息这些个 |
| intersctionRatio | 元素可见区域的占比 |
| isInterscting | 判断元素是否交叉,也就是元素在可视区是否可见了 |
| target | 目标节点,跟我们使用事件的event.target效果一样 |

  • options 一个配置对象

| 属性 | 说明 |
| - | - | - |
| root | 指定父元素,默认为视窗 |
| rootMargin | 触发交叉的偏移值,默认为"0px 0px 0px 0px"。(上左下右,正数为向外扩散,负数则向内收缩) |

  • IntersectionObserver的实例对象的常用方法

| 名称 | 说明 | 参数 |
| - | - | - | - |
| observe | 开始监听一个目标元素 | DOM节点 |
| unobserve | 停止监听一个目标元素 | DOM节点 |
| takeRecords | 返回所有监听的目标元素集合 | |
| disconnect | 停止监听所有元素 | |

封装一个支持多浏览器的懒加载方案

IntersectionObserve这个API用起来确实方便,但是还是有一部分浏览器不支持,所以需要封装判断下。

function getTop(elm){
  let tempTop = elm.offsetTop;
  //如果包裹img的父元素离顶部也有距离,那么也要把它加上
  while(elm = elm.offsetParent) { //注意这儿offsetParent返回的是元素不是数值,同时这儿是赋值语句
    tempTop += elm.offsetTop;
  }
  return tempTop;
}

function lazyLoad(imgs){
  //获取可视区域高度
  let viewHeight = document.documentElement.clientHeight;
  //获取滚动高度
  let scrollTopVal = document.documentElement.scrollTop || document.body.scrollTop;
  console.log(viewHeight, scrollTopVal)
  for(var i = 0; i < imgs.length; i++){
      if((viewHeight + scrollTopVal) > getTop(imgs[i])){
        imgs[i].src = imgs[i].getAttribute('data-src')
      }
  }
}

function useIntersectionObserve(imgList) {
  let obsever = new IntersectionObserver((entries) => {
    entries.forEach((item, index, arrSelf) => {
      console.log(entries)
      if(item.isIntersecting) {
        item.target.src = item.target.dataset.src;
        obsever.unobserve(item.target);
      }
    });
  });

  imgList.forEach((item) => {
    obsever.observe(item);
  });
}


export default {
  // 这两个个方案要求img标签有默认的高度,不然当一个页面出来就展示图片的情况下会一下展示很多图片,
  // 所以在使用懒加载方案时给img加默认高度
  // 单独使用普通懒加载方案
  lazyloadDefault(selector) {
    let imgList = document.querySelectorAll(selector);

    lazyLoad(imgList);
    window.onscroll = function() {
      lazyLoad(imgList);
    }
  },
  // 单独使用IntersectionObserve
  lazyLoadIntersectionObserver(selector) {
    let imgList = document.querySelectorAll(selector);

    let obsever = new IntersectionObserver((entries) => {
      entries.forEach((item, index, arrSelf) => {
        console.log(entries)
        if(item.isIntersecting) {
          item.target.src = item.target.dataset.src;
          obsever.unobserve(item.target);
        }
      });
    });

    imgList.forEach((item) => {
      obsever.observe(item);
    });
  },
  // 多浏览器适配方案
  lazyLoadPolyfill(selector) {
    let imgList = document.querySelectorAll(selector);

    if(!window.IntersectionObserver) {
      lazyLoad(imgList);
      window.onscroll = function() {
        lazyLoad(imgList);
      }
    }else {
      useIntersectionObserve(imgList)
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions