Bootstrap

性能优化:图片懒加载

性能优化:图片懒加载

为什么需要图片懒加载

为什么需要图片懒加载?

我们请求一个带有n张图片的html文件实际上会发送n+1次请求,因为在浏览器解析html的时候遇到了src,就会请求src后面的内容。

如果资源文件很大时,容易阻塞渲染引起卡顿。或者就算加载完毕,用户也不一定会滚动屏幕浏览。所以在首次打开网站时,应尽量只加载首屏内容所包含的资源。

延迟加载的处理过程

首屏被展示的img标签,src属性值为URL,src属性被赋予一个URL后,会立刻向该URL发起资源请求。
还未加载的图片的src属性值均使用了相同的base64来占位,base64图片已经包含了图片的完全编码,可以直接拿来渲染,不用发起网络请求。
当页面发生滚动时,出现在视图内的图片Base64会被替换成真实的URL,进而发起资源请求。

原理
图片懒加载技术主要通过监听图片资源容器是否出现在视口区域内,来决定图片资源是否被加载。
那么实现图片懒加载技术的核心就是如何判断元素处于视口区域之内

传统实现方式

思路

  • 给目标图片一个占位图,这里使用base64。将真实的图片URL保存在自定义属性中,一般是data-src属性。
  • 监听与用户滚动相关的scroll 事件。
  • 在 scroll 事件处理程序中利用 Element.getBoundingClientRect() 方法判断图片是否出现在窗口中。
  • 如果出现将真实的图片链接赋给目标元素 src 属性。

首先获取class属性名为lazy的所有<img>标签,将标签存在数组中,当一个图片被加载后便将其移除数组,并删除类名lazy,当数组为空时,表示所有待延迟加载的图片均已加载完成,此时可以移除滚动事件。

  1. 如何表示需要被来加载的图片?
<img class="lazy" src="image.jpg" data-src="image-to-laod.jpg">
  • class属性,用于类加载器选取需要延迟加载处理的img标签
  • src属性,加载前的占位符图片,可用Base64图片或低分辨率的图片
  • data-src属性,自定义属性保存图片真实的URL外链接

首先获取class属性名为lazy的所有<img>标签,将标签存在数组中,当一个图片被加载后便将其移除数组,并删除类名lazy,当数组为空时,表示所有待延迟加载的图片均已加载完成,此时可以移除滚动事件。

inViewShow() {     
    let imageElements = [].slice.call(document.querySelectorAll('.lazy'))    
    for(let i = 0; i <  imageElements.length  ; i++) {         
        let imageElement = imageElements[i]        
        const rect = imageElement.getBoundingClientRect() // 出现在视野的时候加载图片         
        if(isElementInViewport(rect)) {             
            imageElement.src = imageElement.dataset.src // 移除掉已经显示的             
            imageElements.splice(i, 1)   ;            
            i-- ;        
        }
        if(imageElements.length==0){//移除事件监听
			document.removeEventListener....
		}     
    } 
}
  1. 如何判断图片是否出现在视窗中?–getBoundingClientRect()方法
    getBoundingClientRect()返回元素的大小以及相对于可视区域左上角的位置信息
    window.innerHeight/window.innerWidth 可视窗口的高度和宽度
function isElementInViewport (el) {
  const { top, height, left, width } = el.getBoundingClientRect();
  const w = window.innerWidth;
  const h = window.innerHeight;

  return (  //表示在窗口中

    top <= h &&
    (top + height) >= 0 &&
    left <= w &&
    (left + width) >= 0
  )
}
  1. 监听与用户滚动相关的scroll 事件
//节流
function throttle(callback,wait){
	let pre=0;
	//console.log(this);window
	//节流函数/真正的事件回调函数
	return function(...args){
		const now = Date.now();
		if(now-pre>wait){
			//callback()是window调用的,所以callback函数里的this是window,这里要修改指向事件源,
			//console.log('this2',this); //DOM
			callback.apply(this,args);
			pre = now;
		}
	}
}

document.addEventListener('scroll', throttle(inViewShow,200))

存在的问题:虽然能够实现图片懒加载,绑定和取消绑定操作都需要开发者去实现。需要自己手动去计算,并且会引起回流与重绘

IntersectionObserver API方式

IntersectionObserver API 介绍

IntersectionObserver接口提供了一种异步观察目标元素与祖先元素或顶级文档viewport有交集与无交集之间的变化。祖先元素与视窗viewport被称为根(root)。

IntersectionObserver API 自动检查目标元素的可见性。
语法:new IntersectionObserver(callback, option)

IntersectionObserver是浏览器原生提供的构造函数,IntersectionObserver支持两个参数:

  • callback是当被监听元素的可见性变化时,触发的回调函数。一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
    • callback的参数entries是一个数组,每个成员都是一个 IntersectionObserverEntry 对象,表示发生可了见性变化的对象。如果同时有两个被观察的对象的可见性发生变化,entries 数组就会有两个成员。
    • observer:被调用的IntersectionObserver实例
  • options是一个配置参数,可选。如果options未指定,observer实例默认使用文档视口作为root,并且没有margin,阈值为0%(意味着即使一像素的改变都会触发回调函数)
    • root:监听元素的祖先元素,其边界盒将被视作视口。
    • rootMargin:类似margin属性,在可视窗口中设置缓存区,当元素距离视窗下边界小于256px时,回调函数就会执行开始资源的请求加载
      rootMargin:0 0 256px 0
      
    • threshold:规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组0.0到1.0之间的数组。若指定值为0.0,则意味着监听元素即使与根有1像素交叉,此元素也会被视为可见. 若指定值为1.0,则意味着整个元素都在可见范围内时才算可见。

IntersectionObserverEntry对象描述了目标元素与root的交叉状态

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,与根出现相交区域改变的元素 ,是一个 DOM 节点对象
  • isIntersecting: 目标是否可见,返回bool值
  • rootBounds:根元素的矩形区域的信息,root.getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null
  • boundingClientRect:目标元素的矩形区域的信息,返回结果与element.getBoundingClientRect() 相同
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0

IntersectionObserver 实例方法

  • observe():向IntersectionObserver对象监听的目标集合添加一个元素。一个监听者有一组阈值和一个根, 但是可以监视多个目标元素,以查看这些目标元素可见区域的变化。
  • unobserve:命令IntersectionObserver停止对一个元素的观察。

懒加载实现

function lazyLoadWithObserver() {
	  let observer = new IntersectionObserver((entries, observe) => {
		entries.forEach(item => {//IntersectionObserverEntry对象
			//获取当前被观察的对象
            let target = item.target;
         	 if(item.isIntersecting && target.dataset.src){//元素是否可见
				 target.src = target.dataset.src;
				 // 删除data-src属性和lazy类
                 target.removeAttribute('data-src');
                 target.classList.remove("lazy")
                 //取消观察
                 observer.unobserve(target);
			 }
		})
	  })
}
//
let imgs = document.querySelectorAll('.lazy');
imgs.forEach(item => {
      // 遍历观察元素,添加观察元素
        observer.observe(item) //observer观察器的observe观察方法
})

img/iframe 的loading属性

loading属性

  • eager 立即加载,默认
  • lazy 延迟加载,只有鼠标滚动到该图片所在位置才会显示。
;