# 疫情被迫在家总结js知识图片延迟加载的几种方法

417 阅读5分钟

郑州今年7月份的洪水,8月份的疫情,12月份公司搬家离职,过了元旦的又是疫情!过的着实不易!这不现在公司内网,在家不让连公司电脑,所以不能在家写公司的代码!让在家学习是否会有工资都是未知!顺便总结些知识点,

1.图片延迟加载的意义

图片延时加载,是真实项目中的一个非常重要的性能优化手段。如果不做图片的延时加载,那也页面渲染的时候,同时也要把图片资源请求回来,进行渲染,这样会阻碍页面的渲染进度,导致首次加载页面的速度很慢,延迟加载一方面可以提要页面的加载速度,另一方面可以减少没必要的网络消耗

**实现:**在没有加载真实的图片之前,图片区域用默认的背景图(背景色)占位即可

**首屏:**先加载首屏其他数据,等待其他数据加载完成,再去加载真实图片

其他屏:滚动到对应的区域(一露面,出来一半,完全出现在屏幕中)再去加载真实图片

2.图片延迟加载方案一

在浏览器滚动条滚动(页面滚动)的过程中,等待图片完全出现在可视窗口内的时候,我们加载真实的图片,“这是非首屏图片

​ 的处理步骤”-> 计算出盒子底边距离body顶部的距离A,和浏览器底边距离body 的距离B 做比较,如果A<=B的时候我们去加载图片即可

WechatIMG174

WechatIMG174

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    * {
        margin0;
    }

    .imageLazyBox {
        width236px;
        height420px;
        backgroundurl(./images/loading.gif) no-repeat center center #eee;
        background-size100px 100px;
        margin1000px 0px;

    }

    .imageLazyBox img {
        width100%;
        height100%;
        /* 开始图片隐藏:因为在ie 浏览器中,如果图片src 是空的,或者加载图片是错误的,图片不隐藏 ,会显示一个X 
        效果,很难看,所以图片没有加载之前还是让他隐藏比较好
        办法一: display:none;这种办法加载完成真实的图片后,还需让他display:blok;这样触发dom的回流重汇,性能消耗比较大
        办法二 :opacity:0 ;transition:opacity: .3s; ->推荐方案,一方面加载真实图片后,我们只需要设置opacity:1;
        这样一方面不会引发dom的回流重汇,一方面可以css3实现出渐现的效果
        */
        opacity0;
        transition: opacity 1s;
        /* display: none; */
    }
</style>

<body>
    <div class="imageLazyBox">
        <img src="" alt="" lazy-image="./images/img1.jpeg">
    </div>
</body>


</html>
<script>
   // 图片延迟加载方法的处理
    function imageLazyFun(imageLazyBox) {
        let imageItem = imageLazyBox.querySelector('img'),
            lazy_image = imageItem.getAttribute('lazy-image')
        imageItem.src = lazy_image
        imageItem.onload = function () {
            imageItem.style.opacity = 1
        }
        imageItem.removeAttribute('lazy-image');
        imageItem.isLoad = true;

    }
    
    let imageLazyBox = document.querySelector(".imageLazyBox"),
    imageItem = imageLazyBox.querySelector('img'),
        HTML = document.documentElement;
    // 获取一个元素距离body顶部的距离
    function offset(element) {
        let l = element.offsetLeft,
            t = element.offsetTop,
            p = element.offsetParent;
        while (p && p.tagName !== 'body') {
            if (!/MSIE 8/.test(navigator.userAgent)) {
                l += p.clientLeft;
                t += p.clientTop;
            }
            l += p.offsetLeft;
            t += p.offsetTop;
            p = p.offsetParent;
        }
        return {
            top: t,
            left: l
        }
    }
  // 截流函数
   let throttle = function throttle(fn, wait) {
        let timeout = null// 计时器变量
            result = null,  // 上次执行的结果
            previous = 0//上次执行的时间 
        return function anymouse(...args) {
            let now = new Date,
                context = this;
            let remaining = wait - (now - previous);
            // 看下次执行的时间到没有时间到了清除上次的内容执行
            // console.log("Shiji",remaining);
            if (remaining <= 0) {
                clearTimeout(timeout);
                previous = now;
                result = fn.apply(context, args);
                // 首次执行
            } else if (!timeout) {
                timeout = setTimeout(() => {
                    previous = new Date;
                    result = fn.apply(context, args)
                }, wait)
            }
            // console.log(result)
            return result
        }
    }
    // 执行频率过高 做函数的截流
    window.onscroll = throttle(function () {
        console.log("我做了截流处理")
        if (imageItem.isLoadreturn
        let A = offset(imageLazyBox).top + imageLazyBox.offsetHeight,
            B = HTML.clientHeight + HTML.scrollTop;
        if (A <= B) {
            console.log("ok")
            imageLazyFun(imageLazyBox);
        }
    },500</script>

3.图片延迟加载方案二 利用 getBoundingClientRect()

getBoundingClientRect用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性。

WechatIMG182.jpeg

上面代码不东只需要把判断条件换了就行啦

  window.onscroll = throttle(function () {
        console.log("我做了截流处理")
        if (imageItem.isLoadreturn
        // let A = offset(imageLazyBox).top + imageLazyBox.offsetHeight,
        //     B = HTML.clientHeight + HTML.scrollTop;
        let A = imageLazyBox.getBoundingClientRect().bottom;
        B = HTML.clientHeight 
        if (A <= B) {
            console.log("ok")
            imageLazyFun(imageLazyBox);
        }
    },500

4.图片延迟加载方案三基于 IntersectionObserver来实现延迟加载

概念

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

这个是MDN上官方概念,贴出来显得专业

重点:这里监听目标元素与其祖先或视窗交叉状态的手段**,其实就是观察一个元素是否在视窗可见。**

API

var io = new IntersectionObserver(callback, options)

其实就是一个简单的构造函数。

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

options

root

用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素

threshold

用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]

const options = {
    rootnull,
    threshold: [00.51]
}
var io = new IntersectionObserver(callback, options)
io.observe(document.querySelector('img'))

rootMargin

用来扩大或者缩小视窗的的大小,使用css的定义方法,10px 10px 30px 20px表示top、right、bottom 和 left的值

const options = {
    root: document.querySelector('.box'),
    threshold: [00.51],
    rootMargin: '30px 100px 20px'
}

为了方便了解我们看图

insert.webp

首先我们来看下图上的问题,蓝线是什么呢?他就是咱们定义的root元素,我们添加了rootMargin属性,将视窗的增大了,虚线就是现在的视窗,所以元素现在也就在视窗里面了。

由此可见,root元素只有在rootMargin为空的时候才是绝对的视窗。

说了简单的options,接下来我们看下callback

callback

callback函数会触发两次,元素进入视窗(开始可见时)和元素离开视窗(开始不可见时)都会触发

var io = new IntersectionObserver((entries)=>{
    console.log(entries)
})

io.observe($0) // 观察的dom 

运行结果如下

result.webp

我们可以看到callback函数有个entries参数,它是个IntersectionObserverEntry对象数组,接下来我们重点说下IntersectionObserverEntry对象

IntersectionObserverEntry

IntersectionObserverEntry提供观察元素的信息,有七个属性。

boundingClientRect 目标元素的矩形信息获取的结果基于getBoundingClientRect()获取的结果 intersectionRatio 相交区域和目标元素的比例值 intersectionRect/boundingClientRect 不可见时小于等于0 intersectionRect 目标元素和视窗(根)相交的矩形信息 可以称为相交区域 isIntersecting 目标元素当前是否可见 Boolean值 可见为true rootBounds 根元素的矩形信息,没有指定根元素就是当前视窗的矩形信息 target 观察的目标元素 time 返回一个记录从IntersectionObserver的时间到交叉被触发的时间的时间戳

重点:intersectionRatioisIntersecting是用来判断元素是否可见的,押题咯.

IntersectionObserver触发时机

默认:第一次监听完dom触发一次,元素刚开始出现在页面中触发一次,元素完全消失触发一次

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    * {
        margin0;
    }

    .imageLazyBox {
        width236px;
        height420px;
        backgroundurl(./images/loading.gif) no-repeat center center #eee;
        background-size100px 100px;
        margin1000px 0px;

    }

    .imageLazyBox img {
        width100%;
        height100%;
        /* 开始图片隐藏:因为在ie 浏览器中,如果图片src 是空的,或者加载图片是错误的,图片不隐藏 ,会显示一个X 
        效果,很难看,所以图片没有加载之前还是让他隐藏比较好
        办法一: display:none;这种办法加载完成真实的图片后,还需让他display:blok;这样触发dom的回流重汇,性能消耗比较大
        办法二 :opacity:0 ;transition:opacity: .3s; ->推荐方案,一方面加载真实图片后,我们只需要设置opacity:1;
        这样一方面不会引发dom的回流重汇,一方面可以css3实现出渐现的效果
        */
        opacity0;
        transition: opacity 1s;
        /* display: none; */
    }
</style>
<body>
    <div class="imageLazyBox">
        <img src="" alt="" lazy-image="./images/img1.jpeg">
    </div>
</body>
</html>
<script>

    function imageLazyFun(imageLazyBox) {
        let imageItem = imageLazyBox.querySelector('img'),
            lazy_image = imageItem.getAttribute('lazy-image')
        imageItem.src = lazy_image
        imageItem.onload = function () {
            imageItem.style.opacity = 1
        }
        imageItem.removeAttribute('lazy-image');
        imageItem.isLoad = true;
    }

    let imageLazyBox = document.querySelector(".imageLazyBox");
    let ob = new IntersectionObserver(changes => {
        // 我们监听的dom元素与可视窗口的交叉信息
        let item = changes[0];
        target = item.target;
        // 符合条件:盒子已经完全出现在视口中
        if (item.isIntersecting) {
            // 符合条件:盒子已经完全出现在视口中
            imageLazyFun(target);
            // 处理过一次延迟加载,以后这个元素出现无需要再次监听处理
            ob.unobserve(target)

        }
    });
    // 监听dom元素
    ob.observe(imageLazyBox)
    // 解除监听
    // ob.unobserve(imageLazyBox)



</script>

5.图片延时加载方案四个,未来方法不兼容

loading="lazy" 浏览器兼容的比较少

浏览器兼容性查询网址


<div class="imageLazyBox">
        <img src="./images/img1.jpeg"  loading="lazy" >
    </div>

6. 几种图片延迟加载方法优缺点的对比

getBoundingClientRect 相对比第一种方法计算较少,但是getBoundingClientRect 兼容性不及第一种方法移动端适用,这两种当发都用了 window.onscroll 方法,需要自己写截流函数优化性能

IntersectionObserver相对于使用window.onscroll方法,更方便性能更优化,不用自己节流处理