专栏首页grain先森前端-原生JS实现最简单的图片懒加载

前端-原生JS实现最简单的图片懒加载

懒加载


什么是懒加载

懒加载其实就是延迟加载,是一种对网页性能优化的方式,比如当访问一个页面的时候,优先显示可视区域的图片而不一次性加载所有图片,当需要显示的时候再发送图片请求,避免打开网页时加载过多资源。

什么时候用懒加载

当页面中需要一次性载入很多图片的时候,往往都是需要用懒加载的。

懒加载原理

我们都知道HTML中的 <img>标签是代表文档中的一个图像。。说了个废话。。

<img>标签有一个属性是 src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有 src属性,就不会发送请求。

嗯?貌似这点可以利用一下?

我先不设置 src,需要的时候再设置?

nice,就是这样。

我们先不给 <img>设置 src,把图片真正的URL放在另一个属性 data-src中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到 src中。

实现


HTML结构

<div class="container">

 <div class="img-area">

   <img class="my-photo" alt="loading" src="./img/img1.png">

 </div>

 <div class="img-area">

   <img class="my-photo" alt="loading" src="./img/img2.png">

 </div>

 <div class="img-area">

   <img class="my-photo" alt="loading" src="./img/img3.png">

 </div>

 <div class="img-area">

   <img class="my-photo" alt="loading" src="./img/img4.png">

 </div>

 <div class="img-area">

   <img class="my-photo" alt="loading" src="./img/img5.png">

 </div>

</div>

仔细观察一下, <img>标签此时是没有 src属性的,只有 altdata-src属性。

alt 属性是一个必需的属性,它规定在图像无法显示时的替代文本。 data-* 全局属性:构成一类名称为自定义数据属性的属性,可以通过 HTMLElement.dataset来访问。

如何判断元素是否在可视区域

方法一

网上看到好多这种方法,稍微记录一下。

通过 document.documentElement.clientHeight获取屏幕可视窗口高度

通过 document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离

通过 element.offsetTop获取元素相对于文档顶部的距离

然后判断②-③<①是否成立,如果成立,元素就在可视区域内。

方法二(推荐)

通过 getBoundingClientRect()方法来获取元素的大小以及位置,MDN上是这样描述的:

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

这个方法返回一个名为 ClientRectDOMRect对象,包含了 toprightbottonleftwidthheight这些值。

MDN上有这样一张图:

可以看出返回的元素位置是相对于左上角而言的,而不是边距。

我们思考一下,什么情况下图片进入可视区域。

假设 constbound=el.getBoundingClientRect();来表示图片到可视区域顶部距离; 并设 constclientHeight=window.innerHeight;来表示可视区域的高度。

随着滚动条的向下滚动, bound.top会越来越小,也就是图片到可视区域顶部的距离越来越小,当 bound.top===clientHeight时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。

也就是说,在 bound.top<=clientHeight时,图片是在可视区域内的。

我们这样判断:

function isInSight(el) {

 const bound = el.getBoundingClientRect();

 const clientHeight = window.innerHeight;

 //如果只考虑向下滚动加载

 //const clientWidth = window.innerWeight;

 return bound.top <= clientHeight + 100;

}

这里有个+100是为了提前加载。

加载图片

页面打开时需要对所有图片进行检查,是否在可视区域内,如果是就加载。

function checkImgs() {

 const imgs = document.querySelectorAll('.my-photo');

 Array.from(imgs).forEach(el => {

   if (isInSight(el)) {

     loadImg(el);

   }

 })

}

function loadImg(el) {

 if (!el.src) {

   const source = el.dataset.src;

   el.src = source;

 }

}

这里应该是有一个优化的地方,设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。

函数节流

在类似于滚动条滚动等频繁的DOM操作时,总会提到“函数节流、函数去抖”。

所谓的函数节流,也就是让一个函数不要执行的太频繁,减少一些过快的调用来节流。

基本步骤:

获取第一次触发事件的时间戳

获取第二次触发事件的时间戳

时间差如果大于某个阈值就执行事件,然后重置第一个时间

function throttle(fn, mustRun = 500) {

 const timer = null;

 let previous = null;

 return function() {

   const now = new Date();

   const context = this;

   const args = arguments;

   if (!previous){

     previous = now;

   }

   const remaining = now - previous;

   if (mustRun && remaining >= mustRun) {

     fn.apply(context, args);

     previous = now;

   }

 }

}

这里的 mustRun就是调用函数的时间间隔,无论多么频繁的调用 fn,只有 remaining>=mustRunfn才能被执行。

实验


页面打开时

可以看出此时仅仅是加载了img1和img2,其它的img都没发送请求,看看此时的浏览器

第一张图片是完整的呈现了,第二张图片刚进入可视区域,后面的就看不到了~

页面滚动时

当我向下滚动,此时浏览器是这样

此时第二张图片完全显示了,而第三张图片显示了一点点,这时候我们看看请求情况

img3的请求发出来,而后面的请求还是没发出~

全部载入时

当滚动条滚到最底下时,全部请求都应该是发出的,如图

更新


方法三 IntersectionObserver

经大佬提醒,发现了这个方法

先附上链接:

jjc大大:

https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10

阮一峰大大:

http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

API Sketch for Intersection Observers:

https://github.com/WICG/IntersectionObserver

IntersectionObserver可以自动观察元素是否在视口内。

var io = new IntersectionObserver(callback, option);

// 开始观察

io.observe(document.getElementById('example'));

// 停止观察

io.unobserve(element);

// 关闭观察器

io.disconnect();

callback的参数是一个数组,每个数组都是一个 IntersectionObserverEntry对象,包括以下属性:

属性描述time可见性发生变化的时间,单位为毫秒rootBounds与getBoundingClientRect()方法的返回值一样boundingClientRect目标元素的矩形区域的信息intersectionRect目标元素与视口(或根元素)的交叉区域的信息intersectionRatio目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0target被观察的目标元素,是一个 DOM 节点对象

我们需要用到 intersectionRatio来判断是否在可视区域内,当 intersectionRatio>0&&intersectionRatio<=1即在可视区域内。

代码

function checkImgs() {

 const imgs = Array.from(document.querySelectorAll(".my-photo"));

 imgs.forEach(item => io.observe(item));

}

function loadImg(el) {

 if (!el.src) {

   const source = el.dataset.src;

   el.src = source;

 }

}

const io = new IntersectionObserver(ioes => {

 ioes.forEach(ioe => {

   const el = ioe.target;

   const intersectionRatio = ioe.intersectionRatio;

   if (intersectionRatio > 0 && intersectionRatio <= 1) {

     loadImg(el);

   }

   el.onload = el.onerror = () => io.unobserve(el);

 });

});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 前端- CSS 的空格处理

    上面是一行 HTML 代码,文字的前部、内部和后部各有两个空格。为了便于识别,这里使用半圆形符号◡表示空格。

    grain先森
  • JavaScript 模块化

    随着前端js代码复杂度的提高,JavaScript模块化这个概念便被提出来,前端社区也不断地实现前端模块化,直到es6对其进行了规范,下面就介绍JavaScri...

    grain先森
  • h5页面在不同iOS设备上的问题总结

    在做文章评论的功能时,会遇到很多兼容性的问题,在不同机型上的表现也很不一致,总结了以下这些问题。

    grain先森
  • 想用小程序新能力为 App 引流?且慢!想要用上它可没那么简单

    首先,最引人瞩目的是「小程序唤起 App」的功能,它代表小程序与 App 间再添打通通道;其次,公开课上承诺的「标题栏开放自定义」能力,也在此次新能力开放范围中...

    知晓君
  • JDK8新特性之Stream流

    是什么是Stream流 java.util.stream.Stream Stream流和传统的IO流,它们都叫流,却是两个完全不一样的概念和东西。 流可以简单的...

    Java技术栈
  • Java8-对List转换Map、分组、求和、过滤

    在java8之后我们list转map再也不用循环put到map了,我们用lambda表达式,使用stream可以一行代码解决,下面我来简单介绍list转map的...

    胖虎
  • SQLAlchemy - Column详解

    过滤是数据  提取的一个很重要的功能,以下对一些常用的过滤条件进行详解,并且这些过滤条件都是只能通过filter方法实现的:

    stys35
  • 美欲重夺超算霸主:18亿美元投资新E级超算计划2022年完成

    精彩回顾 2018 新智元产业跃迁 AI 技术峰会圆满结束,点击链接回顾大会盛况: 爱奇艺 http://www.iqiyi.com/l_19rr3aqz3z...

    新智元
  • 深度学习应用大盘点

    当首次介绍深度学习时,我们认为它是一个要比机器学习更好的分类器。或者,我们亦理解成大脑神经计算。 第一种理解大大低估了深度学习构建应用的种类,而后者又高估了它的...

    CSDN技术头条
  • JPG、PNG、SVG 和 WEBP 格式图片的业务场景对比

    Leophen

扫码关注云+社区

领取腾讯云代金券