探索图片懒加载的最佳实践及封装一个懒加载插件

这周研究了下目前图片懒加载的所有主流方式,分享下,末尾封装了一个兼容所有方式的图片懒加载插件

图片懒加载的意义:

1.首先它能提升用户的体验,试想一下,如果打开页面的时候就将页面上所有的图片全部获取加载,如果图片数量较大,对于用户来说简直就是灾难,会出现卡顿现象,影响用户体验。
2.有选择性地请求图片,这样能明显减少了服务器的压力和流量,也能够减小浏览器的负担。

原理:

1、将页面中的img标签src指向一张小图片或者src为空,
2、然后定义data-image属性(这个属性可以自定义命名,我常用data-image)属性指向真实的图片。
3、当载入页面时,先把可视区区域内的img标签的data-src属性值赋值给src。
4、然后监听滚动事件,加载用户即将看到的图片

怎么做:

先说结论,四种方式实现图片懒加载:从上到下,也是发展顺序的先后,可以划分为:

  1. 基于JS盒模型实现的懒加载方案(兼容性最好)
  2. 基于getBoundingClientRect的进阶方案(ie5以上都能支持)
  3. 终极方案:IntersectionObserver(ie不支持,性能最最好)
  4. 未来设想:img.loading=lazy(只有chrome76以上支持)

以下只演示核心逻辑:

基于JS盒模型实现的懒加载方案:

在这里插入图片描述

  // 加载条件:盒子底边距离BODY距离 < 浏览器底边距离BODY的距离
		    let winH = window.innerHeight;
            let B = lazyImageBox.offsetTop + lazyImageBox.offsetHeight,
                A = winH + document.documentElement.scrollTop;
            if (B <= A) {
    
    
               //达到条件,把该img标签data-image属性上的值赋值给src属性
                lazyImg(lazyImageBox);
            } 



基于getBoundingClientRect的进阶方案

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
在这里插入图片描述

		let winH = document.documentElement.clientHeight;
		let {
    
    
            bottom
        } = lazyImageBox.getBoundingClientRect();
        if (bottom <= winH) {
    
    
        //达到条件,把该img标签data-image属性上的值赋值给src属性
            lazyImg(lazyImageBox);
        }



基于IntersectionObserver的终极方案(性能最好)

这个IntersectionObserverAPI比较新,这里只说应用,详情看文档

	// 实现图片的延迟加载
    // IntersectionObserver 监听DOM对象,当DOM元素出现和离开视口的时候触发回调函数
        observer = new IntersectionObserver(changes => {
    
    
            changes.forEach(item => {
    
    
                let {
    
    
                //这个属性表示是否出现在视口中,ture/false
                    isIntersecting,
                //目标dom元素
                    target
                } = item;
                if (isIntersecting) {
    
    
                    lazyImg(target);
                    observer.unobserve(target);
                }
            });
        });


       let lazyImageBoxs = Array.from(document.querySelectorAll('img')) 
       lazyImageBoxs.forEach(lazyImageBox => {
    
    
            observer.observe(lazyImageBox);
        });
    



未来设想:img.loading=lazy

这种用法还在试验阶段,只有chrome76版本以上的浏览器才支持,实现图片懒加载就一句话,剩下的底层全部帮你做

<img src="xxx.png" alt=""  loading="lazy">

图片懒加载插件实现:

基于上述四种方案,封装了一个插件,内部通过能力测试,优先选择性能最佳的去实现图片懒加载

class ImgLazy {
    
    
				static winH = null
				static selector = null
				static targetImgDoms = []

				static init(selector) {
    
    
					this.selector = selector
					this.winH = document.documentElement.clientHeight;
					this.targetImgDoms = Array.from(document.querySelectorAll(selector))
					if("loading" in new Image()) {
    
    
						this.方案四()
						return
					} else if(typeof IntersectionObserver !== "undefined") {
    
    
						this.方案三()
						return
					}
						this.lazyFunc()

					// onscroll触发的频率太高了,滚动一下可能要被触发很多次,导致很多没必要的计算和处理,消耗性能=>我们需要降低onscroll的时候的触发频率 (节流)
					window.onscroll = this.throttle(this.lazyFunc, 500).bind(this)

				}

				static lazyFunc() {
    
    
					this.targetImgDoms.forEach(img => {
    
    
						// 已经处理过则不在处理
						let isLoad = img.getAttribute('isLoad');
						if(isLoad) return;
						//getBoundingClientRect判断是否存在
						if(!document.body.getBoundingClientRect) {
    
    
							console.log("方案一")
							this.方案一(img)
						} else {
    
    
							console.log("方案二")
							this.方案二(img)
						}

					});
				}

				static 方案一(img) {
    
    
					// 加载条件:盒子底边距离距离 < 浏览器底边距离BODY的距离
					let B = this.offset(img).top + img.offsetHeight,
						A = this.winH + document.documentElement.scrollTop;
					if(B <= A) {
    
    
						this.lazyImg(img);
					}
				}

				static 方案二(img) {
    
    
					let {
    
    
						bottom
					} = img.getBoundingClientRect();
					if(bottom <= this.winH) {
    
    
						this.lazyImg(img);
					}
				}

				static 方案三() {
    
    
					let observer = new IntersectionObserver(changes => {
    
    
						console.log(changes)
						changes.forEach(item => {
    
    
							let {
    
    
								isIntersecting,
								target
							} = item;
							if(isIntersecting) {
    
    
								this.lazyImg(target);
								observer.unobserve(target);
							}
						});
					});
					this.targetImgDoms.forEach(img => {
    
    
						observer.observe(img);
					});
				}

				static 方案四(img) {
    
    
					this.targetImgDoms.forEach(img => {
    
    
						img.setAttribute("loading", "lazy")
						this.lazyImg(img)
					});
				}

				static lazyImg(img) {
    
    
					let trueImg = img.getAttribute('data-image');
					img.src = trueImg;
					img.onload = function() {
    
    
						// 图片加载成功
					};
					img.removeAttribute('data-image');
					// 记录当前图片已经处理过了
					img.setAttribute('isLoad', 'true');
				}

				//工具方法:获取元素距离页面顶部的距离(兼容性处理)
				static offset(element) {
    
    
					let parent = element.offsetParent,
						top = element.offsetTop,
						left = element.offsetLeft;
					while(parent) {
    
    
						if(!/MSIE 8/.test(navigator.userAgent)) {
    
    
							left += parent.clientLeft;
							top += parent.clientTop;
						}
						left += parent.offsetLeft;
						top += parent.offsetTop;
						parent = parent.offsetParent;
					}
					return {
    
    
						top,
						left
					};
				}

				//工具方法:节流
				static throttle(func, wait = 500) {
    
    
					let timer = null,
						previous = 0; //记录上一次操作时间
					return function anonymous(...params) {
    
    
						let now = new Date(), //当前操作的时间
							remaining = wait - (now - previous);
						if(remaining <= 0) {
    
    
							// 两次间隔时间超过频率:把方法执行即可
							clearTimeout(timer);
							timer = null;
							previous = now;
							func.call(this, ...params);
						} else if(!timer) {
    
    
							// 两次间隔时间没有超过频率,说明还没有达到触发标准呢,设置定时器等待即可(还差多久等多久)
							timer = setTimeout(() => {
    
    
								clearTimeout(timer);
								timer = null;
								previous = new Date();
								func.call(this, ...params);
							}, remaining);
						}
					};
				}
			}

插件使用:

需要注意img图片一定要事先撑开,占有一定的空间

	<style type="text/css">
				div{
     
     
					width: 400px;
					height: 400px;
				}
				img{
     
     
					width: 100%;
					height: 100%;
				}
		</style>
	<body>
		<div><img class="lazy" src="" data-image="2.jpg" /></div>
		<div><img class="lazy" src="" data-image="images/1.jpg" /></div>
		<div><img class="lazy" src="" data-image="images/2.jpg" /></div>
		<div><img class="lazy" src="" data-image="images/3.jpg" /></div>
		。。。。。。
	</body>
<script>
ImgLazy.init("img.lazy")
</script>

效果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/fesfsefgs/article/details/110009040