图片懒加载的三种解决方法

简介: 学习前端,三板斧,HTML、CSS、JavaScript。JavaScript 基础由 DOM、BOM、ECMAScript 组成,其中 ECMAScript 为规范语言,现在说的 ES6(ES2016~ES2022) 指的就是它,隔段时间就会发布,目前一年发布一次,以年份来说,现在是 ECMAScript 2022。

学习前端,三板斧,HTML、CSS、JavaScript。JavaScript 基础由 DOM、BOM、ECMAScript 组成,其中 ECMAScript 为规范语言,现在说的 ES6(ES2016~ES2022) 指的就是它,隔段时间就会发布,目前一年发布一次,以年份来说,现在是 ECMAScript 2022。BOM是什么,BOM是浏览器对象模型(Browser Object Model)。它有六大对象


  • document:DOM(对 BOM 包含了DOM,但是 DOM 重要,其地位和 BOM 一样)


  • event:事件对象


  • history:浏览器的历史记录


  • location:窗口的 url 地址栏信息


  • screen:显示设备的信息


  • navigator:浏览器的配置信息


DOM我们也很了解,文本对象模型,指操作HTML(超级文本标识语言)的API。DOM 会将文档解析为一个由节点和对象(包含属性和方法的对象)组件的结构集合


以前开发页面时,我们在 script 标签中,先获取节点(DOM Api),再操作 DOM ,所以以前是 《JavaScript 面向对象编程》,《JavaScript dom编程艺术》,但操作 DOM 的 API 太长,不易书写,JQuery 集大成,简化Api,统一了操作写法。如果展开,会有很多可以延伸,而我铺垫了这么多,就是想引出 document


这次我们要讲的 offset、scroll、client 就是出自“ document 家”。先配两张图来看看这三个到底是什么


image.png


image.png


client



client 指元素本身的可视内容。不包括 overflow 被折叠部分,不包括滚动条、border,包括 padding


有四属性:


  • clientHeight:对象可见的高度


  • clientWidth:对象可见宽度


  • clientTop:元素距离顶部的厚度,一般为0,因为滚动条不会出现在顶部


  • clientLeft:元素距离左侧的厚度,一般为0,因为滚动条不会出现在左侧


offset



offset 指偏移。包括这个元素在文档中占用的所有显示宽度,包括滚动条、padding、border,不包括 overflow 隐藏的部分


有五属性:


offsetHeight:该对象自身的绝对高度,


  • offsetHeight: = border-width * 2 + padding-top + height + padding-bottom


offsetWidth:该对象自身的绝对宽度


  • offsetWidth = border-width * 2 + padding-left + width + padding-right


offsetParent:返回一个对象的引用,字面意思,相对父元素的偏移


  • 如果当前元素的父元素没有 CSS 定位(position为absolute/relative),offsetParent 为 body


  • 如果当前元素的父元素有 CSS 定位(position为absolute/relative),offsetParent 取父级中最近的元素


offsetTop:相对版面或 offsetParent 属性指定父坐标的顶部距离


  • offsetTop = offsetParent 的 padding-top + 中间元素的 offsetHeight + 当前元素的 margin-top


offsetLeft:相对版面或 offsetParent 属性指定父坐标的左部距离


  • offsetLeft = offsetParent 的 padding-left + 中间元素的 offsetWidth + 当前元素的 margin-left


Scroll


Scroll 指滚动。包括这个元素没有显示出来的实际宽度,包括 padding,不包括滚动条、border


scrollHeight:获取对象的滚动高度,对象的实际高度


scrollWidth:获取对象的滚动宽度


scrollTop:当前元素与窗口最顶端的距离


scrollLeft:当前元素与窗口最左端的距离


其他



innerHeight 和 clientHeight 有什么区别


准确来说,clientHeight 是针对 body,innerHeight 是 window 的


document.body.clientHeight:网页可见区域高


window.innerHeight:可视窗口高度,不包括浏览器顶部工具栏


监听图片高度实现懒加载



通过图片的 offsetTop(偏移高度)和 window 的 innerHeight、scrollTop 判断图片是否位于可视区域


即很多图片,先显示视窗中的图片,没看见的先不展示,加快页面加载速度。当你向下滚,当后续图片的 offsetTop(偏移高度) 小于 innerHeight(视窗高度) + scrollTop(滚动高度) 时,意味着此图片已经出现在视窗中,将真正图片替换loading


关键代码在于


function lazyload() {
   let seeHeight = window.innerHeight;
   let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
   for (let i = n; i < img.length; i++) {
       if (img[i].offsetTop < seeHeight + scrollTop) { // 对比图片的偏移高度和屏幕高度+滚动高度
           if (img[i].getAttribute("src") === 'loading.gif') {
               img[i].src = img[i].getAttribute("data-src")
          }
           n = i + 1
      }
  }
}


效果如下:


image.png


Element.getBoundingClientRect



Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置


getBoundingClientRect 返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合,即与该元素相关的 CSS 边框集合


语法


domRect = element.getBoundingClientRect();


返回坐标、宽高、在视口中的位置


  • x
  • y
  • width
  • height
  • top
  • right
  • bottom
  • left


image.png


如果是标准盒子模型,元素的尺寸等于 width/height + padding + border-width 的总和。如果 box-sizing:border-box ,元素的尺寸等于 width/height


我们用这个 API 来获取每张图片的 top 值,如果 top 值小于可视区的高度就视为已经进入可视区,直接加载图片即可


function lazyload() {
   let seeHeight = document.documentElement.clientHeight
   for (let i = n; i < img.length; i++) {
       if (img[i].getBoundingClientRect().top < seeHeight) {
           if (img[i].getAttribute("src") === "loading.gif") {
               img[i].src= img[i].getAttribute("data-src")
          }
           n = i + 1
      }
  }
}


效果如下:

image.gifimage.png


通过 IntersectionObserver 实现懒加载



IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)


IntersectionObserver 可以不用监听 scroll 事件,做到元素一可见便调用回调,在回调里面我们来判断元素是否可见


if (IntersectionObserver) {
   let lazyImageObserver = new IntersectionObserver((entries, observer) => {
       entries.forEach((entry, index) => {
           let lazyImage = entry.target;
           // 如果元素可见
           if (entry.intersectionRatio > 0) {
               if (lazyImage.getAttribute("src") === 'loading.gif') {
                   lazyImage.src = lazyImage.getAttribute("data-src")
              }
               lazyImageObserver.unobserve(lazyImage)
          }
      })
  })
   for (let i = 0; i < img.length; i++) {
       lazyImageObserver.observe(img[i])
  }
}


上述代码表示,遍历所有的图片,对其进行观察原生是否可见,如果元素可见,就把真正图片替换loading


IntersectionObserver  可以自动“观察”元素是否可见,其本质是目标元素与视窗产生一个交叉去,所以这个 API 叫做“交叉观察器”


使用方式


let io = new IntersectionObserver(callback, option)


上面代码中, IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)


构造函数的返回值是一个观察器实例。实例的 observe 方法可以指定观察哪个 DOM 节点


// 开始观察
io.observe(document.getElementById("example"))
// 停止观察
io.unobserve(element)
// 关闭观察器
io.disconnect()


callback 参数


目标元素的可见性变化时,就会调用观察器的回调函数 callback


callback 一般会触发两次。一次时目标元素刚刚进入视窗(开始可见),另一次时完全离开视窗(开始不可见)


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


上面代码中,回调函数采用的是箭头函数的写法。callback 函数的参数(entries)是一个数组,每个成员都是一个 IntersectionObserverEntry 对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。


IntersectionObserverEntry 对象


IntersectionObserverEntry 对象提供目标元素的信息,一共有六个属性


  • time: 可见性发生变化的时间,单位毫秒


  • target:被观察的目标,是个 DOM 节点对象


  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect() 方法的返回值,如果没有根元素(即直接相对于视窗滚动),则返回null


  • boundingClientRect:目标元素的矩形区域的信息


  • intersectionRect:目标元素与视窗(或根元素)的交叉区域的信息


  • intersectionRatio:目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为1,完全不可见时小于等于 0  


如图所示:


image.png


兼容性如何


caniuse 兼容性报告目前支持率是 93.67%,但是iOS的支持度要在 iOS12.2 以上,如果是iPhoneX(2018.11)之后的手机都是支持的,如果是之前的,升级系统才支持,考虑到一些人是不会升级,所以这个兼容性还不支持大众化的场景,但它的能力和性能都非常的好


image.png


总结



面试的时候被问到懒加载,我那个时候没做过相关的准备,我说不知道,X虎的面试官会引导,其实引导才能测试出一个人真正的水平,但是那个时候我竟然连 scroll 都想不起来。现在回想起来,实在是准备的方向搞错了。


说到图片懒加载,有三种方法:


  • 监听图片高度


  • 技术要点:监听scroll,滚动的时候遍历所有的图片,如果图片的偏移高度小于屏幕高度+滑动高度,说明已经出现在视窗,就替换图片
  • 优点:兼容性好
  • 缺点:单纯使用 scroll 滑动来监听高度,会引发性能问题,所以要搭配节流


  • Element.getBoundingClientRect


  • 技术要点:与监听图片无太大区别,无非是把图片的偏移高度改成 getBoundingClientRect().top,对比每张图片的自身高度是否出现在视窗(视口)中,有就替换图片
  • 优点:兼容性好,代码相对监听图片高度少了一些
  • 缺点:也是使用 scroll 滑动来监听,会引发性能问题


  • 使用 IntersectionObserver Api


  • 技术要点:通过 IntersectionObserver Api 来实现,图片元素一可见就调用回调,在回调中判断元素是否可见
  • 优点:写起来方便,性能好
  • 缺点:兼容性适配iOS12.2以上,安卓5以上


附上线上 demo:


  • scroll


  • getBoundingClientRect


  • IntersectionObserver


参考资料



  • JavaScript学习总结(三)BOM和DOM详解


  • JS 中的offset、scroll、client总结


  • IntersectionObserver API 使用教程



相关文章
|
6月前
|
JavaScript
懒加载图片案例
懒加载图片案例
|
11月前
|
前端开发 UED
一个页面上有大量的图片,加载很慢,你有哪些方法优化这些图片的加载?
一个页面上有大量的图片,加载很慢,你有哪些方法优化这些图片的加载?
|
存储 前端开发 JavaScript
前端实现图片的懒加载
前端实现图片的懒加载
67 0
|
3月前
|
存储 JavaScript UED
图片懒加载
图片懒加载
|
3月前
|
JavaScript API
如何实现图片懒加载
如何实现图片懒加载
74 0
|
6月前
|
JavaScript 前端开发
如何实现网页的懒加载?图片懒加载的原理
如何实现网页的懒加载?图片懒加载的原理
48 0
|
11月前
|
存储
图片懒加载(二)
图片懒加载(二)
48 1
|
JavaScript 前端开发 UED
实现图片懒加载(及优化相关)
工作之余想要休闲一下(看-美-女-图),但是又觉得单纯休闲不利于自身进步,于是 ……
实现图片懒加载(及优化相关)
懒加载只能对图片进行懒加载吗?
懒加载只能对图片进行懒加载吗?