图片懒加载的几种优秀的方式(1)
原生 JS 实现最简单的图片懒加载
懒加载
什么是懒加载
懒加载其实就是延迟加载
是一种对网页性能优化的方式
比如当访问一个页面的时候
优先显示可视区域的图片而不一次性加载所有图片
当需要显示的时候再发送图片请求
避免打开网页时加载过多资源。
为什么要把上面的话分开来写呢,因为上面的话相当简洁,每一句话都是一个操作或者是一个关键点,整体概括了整个图片懒加载的整个流程和优点。而我们会用到图片懒加载的情况就是图片过多的情况。
懒加载原理及实现原理
<img>
标签有一个属性是 src
,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有 src
属性,就不会发送请求。
我们先不给 <img>
设置 src
,把图片真正的URL放在另一个属性 data-src
中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到 src
中。
具体列子
HTML结构
-
<div class="container">
-
<div class="img-area">
-
<img class="my-photo" alt="loading" data-src="./img/img1.png">
-
</div>
-
<div class="img-area">
-
<img class="my-photo" alt="loading" data-src="./img/img2.png">
-
</div>
-
<div class="img-area">
-
<img class="my-photo" alt="loading" data-src="./img/img3.png">
-
</div>
-
<div class="img-area">
-
<img class="my-photo" alt="loading" data-src="./img/img4.png">
-
</div>
-
<div class="img-area">
-
<img class="my-photo" alt="loading" data-src="./img/img5.png">
-
</div>
-
</div>
仔细观察一下, <img>
标签此时是没有 src
属性的,只有 alt
和 data-src
属性。
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.
这个方法返回一个名为 ClientRect
的 DOMRect
对象,包含了 top
、 right
、 bottom
、 left
、 width
、 height
这些值。
MDN上有这样一张图:
top
↑
left← ClientRect
的 DOMRect
→ight
↓
bottom
假设 const bound=el.getBoundingClientRect();
来表示图片到可视区域顶部距离;(top)
并设 const clientHeight=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 => {// 将imgs转化为数组并且遍历
-
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) {// es6
-
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) {// 时间大雨500或者执行时间大约500就执行
-
fn.apply(context, args);
-
previous = now;// 更新时间
-
}
-
}
-
}
这里的 mustRun
就是调用函数的时间间隔,无论多么频繁的调用 fn
,只有 remaining>=mustRun
时 fn
才能被执行。
更新
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,完全不可见时小于等于0 |
target | 被观察的目标元素,是一个 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(ioes => {
-
const el = ioes.target;
-
const intersectionRatio = ioes.intersectionRatio;
-
if (intersectionRatio > 0 && intersectionRatio <= 1) {
-
loadImg(el);
-
}
-
el.onload = el.onerror = () => io.unobserve(el);
-
});
-
});
上面的几种图片懒加载的方法都很优秀,很值得学习,尤其是最后一种,长见识了。2017-08-28