Skip to content

图片懒加载 #37

Description

@LumiereXyloto

方案一:clientHeight、scrollTop 和 offsetTop

首先给图片一个占位资源:

==注意,要设置图片的width,height才可以获取到offsetTop==

<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />

接着,通过监听 scroll 事件来判断图片是否到达视口:

let imgs = document.getElementsByTagName("img");
let num = imgs.length;
let count = 0;//计数器,从第一张图片开始计,避免每次都要去遍历所有图片

lazyload();//还未滑动时加载一波图片

window.addEventListener('scroll', lazyload);

function lazyload() {
  let viewHeight = document.documentElement.clientHeight;//视口高度
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;//滚动条的高度
  for(let i = count; i <num; i++) {
    // 如果滚动高度加视口高度大于元素距离顶部的高度,即元素现在已经出现在视口中
    if(imgs[i].offsetTop < scrollTop + viewHeight) {
      if(imgs[i].getAttribute("src") !== "default.jpg") continue;
      imgs[i].src = imgs[i].getAttribute("data-src");
      count ++;
    }
  }
}

然后来个节流函数优化,避免频繁触发

function throttle(fn, interval) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
            fn.apply(this, arguments);
            // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
            canRun = true;
        }, interval);
    };
}

window.addEventListener('scroll', throttle(lazyload, 200));

方案二:getBoundingClientRect()

function lazyload() {
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(imgs[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      if(imgs[i].getAttribute("src") !== "default.jpg") continue;
      imgs[i].src = imgs[i].getAttribute("data-src");
      count ++;
    }
  }
}

方案三:IntersectionObserver接口

IntersectionObserver接口可用于资源的预加载。监听、判断、节流一次搞定。但是兼容性不好,仅提供思路参考。

概念

IntersectionObserver接口(从属于Intersection Observer API)为开发者提供了一种可以异步==监听目标元素与其祖先或视窗(viewport)交叉状态==的手段。祖先元素与视窗(viewport)被称为根(root)。

交叉就是说明元素在视窗里面了

API:

var io = new IntersectionObserver(callback, options)

以上代码会返回一个IntersectionObserver实例,callback是当元素的可见性变化时候的回调函数,options是一些配置项(可选)。

callback函数有个entries参数,它是个IntersectionObserverEntry对象数组,包含了很多当前被observe的el的属性包括:

  • isIntersecting 目标元素当前是否可见 Boolean值
  • target 观察的目标元素

其他API:

io.observe(el)   开始观察,接受一个DOM节点对象
io.unobserve(el) 停止观察 接受一个element元素
io.disconnect()  关闭观察器

懒加载方案:

let imgs = document.getElementsByTagName("img");

const observer = new IntersectionObserver(entries => {
  //entries 是被观察的元素集合
  for(let i = 0, len = entries.length; i < len; i++) {
    let entry = entries[i];
    // 通过这个属性判断是否在视口中
    if(entry.isIntersecting) {
      const imgElement = entry.target;
      imgElement.src = imgElement.getAttribute("data-src");
      observer.unobserve(imgElement);
    }
  }
})
Array.from(imgs).forEach(item => observer.observe(item));

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions