JavaScript实现图片懒加载的2种方式(监听scroll滚动事件、IntersectionObserver接口)

当有人打开了你的网站,而你的网站恰好需要展示很多高质量、精美的图片、日常图片时
一张图片往往都会在500kb-2MB之间,如果有10张图片的话、大约会产生(取中间值1MB)10MB的网络宽带
如果没有使用使用图片懒加载的话,用户打开网站的一瞬间,所有的资源需要全部加载完成才能完整的打开网站
一个网站打开的速度超过5s的话,大部分人都会立刻关闭该网站

其中出现了三个问题
在用户没有看到的地方,就一股脑的加载全部图片

  1. 这会造成网络资源的浪费
  2. 增加服务器的压力
  3. 用户的体验感

这时候就需要使用到图片的懒加载,只有浏览器浏览到的地方才会加载图片,大大节省了网站的响应时间,服务器压力减少

监听scroll滚动事件

用户滚动到的地方,就会加载图片
既然是滚动,很多人都会想到用scroll滚动事件

  1. 此写法稍微优点复杂
    COPY
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    /**
    * 获取指定代码块的基本信息
    * @scrollTop 滚动条纵坐标位置
    * @scrollLeft 滚动条横坐标位置
    * @param {*} node 元素(标签)
    * @returns 当前元素(标签) top|right|bottom|left 距离顶部的距离
    *
    * nodePos: top|bottom 当前元素在可视区域的上方时获取到的是负数,反之正数
    * nodePos: right|left 当前元素距离视区域left的距离,right视区域距离
    *
    */
    function getNodePosition(node) {
    var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft,
    scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    var nodePos = node.getBoundingClientRect(); //获取元素(标签)的参数信息
    return {top:nodePos.top + scrollTop, right:nodePos.right + scrollLeft, bottom:nodePos.bottom + scrollTop, left:nodePos.left + scrollLeft }
    }

    /**
    * 图片懒加载
    * @selectNode 图片元素(标签)
    * @dataImg 图片的真实url地址
    */
    function imgLazyLoad(selectNode,dataImg){
    // 获取 指定的img
    var imgLazyLoad = document.querySelectorAll(selectNode)
    var timer,
    len = imgLazyLoad.length;

    // 加载
    function loading(){
    timer && clearTimeout(timer); // 清除延迟
    timer = setTimeout(function(){ // 设置延迟
    var scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
    winHeight = document.documentElement.clientHeight;
    // 遍历所有的img
    for(var i = 0;i < imgLazyLoad.length;i++){
    var nodePos = getNodePosition(imgLazyLoad[i]), // 调用getNodePosition()方法,返回元素的信息
    winTop = winHeight+scrollTop;
    if((nodePos.top > scrollTop && nodePos.top < winTop) || (nodePos.bottom > scrollTop && nodePos.bottom < winTop)){
    imgLazyLoad[i].src = imgLazyLoad[i].getAttribute(dataImg);
    }
    }
    },100);
    }
    if(!len) return; // 没有img元素则结束
    loading();
    document.addEventListener('scroll', function(){
    if(!len) return;
    else loading();
    })
    }
    imgLazyLoad("body img[data-img]","data-img")
  2. 可简写为
    COPY
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var body = document.querySelector("body")
    var img = document.querySelector("body img[data-img]")
    body.addEventListener('scroll',()=>{
    img.forEach(img=>{
    var imgTop = img.getBoundingClientRect().top
    var bodyTop = body.getBoundingClientRect().top
    // if(imgTop-bodyTop<=body.clientHeight){
    // var src = img.getAttribute("data-img")
    // img.setAttribute("src",src)
    // }
    // 可将上方if压缩为1行
    if(imgTop-bodyTop<=body.clientHeight) img.setAttribute("src",img.getAttribute("data-img"))
    })
    })

IntersectionObserver接口

在写图片懒加载的时候我上Github查找了一下,有个项目https://github.com/tuupola/lazyload使用到了IntersectionObserver接口,我当时还纳闷,这都没有滚动事件怎么实现的图片懒加载,于是我网上查了IntersectionObserver接口,发现它的效率比监听scroll滚动事件快,然后我研究了一下

IntersectionObserver是监听目标元素与其祖先或视窗交叉状态的手段,如果目标元素未定义(为null)的话默认是根据浏览器视图框可见范围

是否可见

COPY
1
var io = new IntersectionObserver(callback, options)

IntersectionObserver返回一个实例
callback当元素的可见性变化时候触发回调函数
options设置一些配置项(可选)

关于IntersectionObserver更多的知识可参考:https://www.jianshu.com/p/84a86e41eb2b

COPY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 图片懒加载
* @param {*} img 需要懒加载的img元素(标签)
* @param {*} attr 图片的真实url地址
*/
function ImgLazyLoad(img,attr){
var imgLazyLoad = document.querySelectorAll(img) // 获取全部需要加载的图片
function LazyLoad(target){
const io = new IntersectionObserver((entries,Observer)=>{
entries.forEach(entry=>{
if(entry.isIntersecting){
const img = entry.target
const src = img.getAttribute(attr)
img.setAttribute("src",src)
Observer.disconnect()
}
})
})
io.observe(target)
}
imgLazyLoad.forEach(LazyLoad)
}
ImgLazyLoad("body img[data-img]","data-img")
Authorship: Lete乐特
Article Link: https://blog.imlete.cn/article/JavaScript-Image-LazyLoad.html
Copyright: All posts on this blog are licensed under the CC BY-NC-SA 4.0 license unless otherwise stated. Please cite Lete乐特 's Blog !