原文地址:图片懒加载踩坑javascript
对网页加载速度影响最大的就是图片,一张普通的图片可能会有好几 M 的大小,当图片不少时,网页的加载速度变得很缓慢。html
为了优化网页性能以及用户体验,咱们对图片进行懒加载
。vue
懒加载是一种对网页性能优化的方式,它的原理是优先加载在可视区域内的图片,而不一次性加载因此图片。当浏览器滚动,图片进入可视区时再去加载图片。经过设置图片的 src
属性来让浏览器发起图片的请求。当这个属性为空或者没有时,就不会发送请求。java
此文所涉及的懒加载皆是在垂直方向上的滚动加载,横向滚动暂不考虑。git
懒加载的实现主要是判断当前图片是否到了可视区域这一核心逻辑。咱们先来整理一下实现思路:github
img dom
。scroll
事件,对其进行事件监听。<div class="container">
<div class="img-area">
<img id="first" data-src="./img/ceng.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/data.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/huaji.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/liqi1.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/liqi2.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/steve-jobs.jpg" alt="">
</div>
</div>
复制代码
此时 img 标签是没有 src 属性的,咱们把真实的图片地址放在一个属性里,这里咱们使用 HTML5 的 data
属性,将真实地址放在自定义的 data-src
中。浏览器
这一逻辑有两种方法,听我娓娓道来。性能优化
方法一app
第一种方法咱们经过计算该图片距离 document
顶部的高度是否小于当前可视区域相对于 document 顶部高度来判断。dom
可视区域相对于 document 顶部高度的计算方法:
const clientHeight = document.documentElement.clientHeight; // 视口高度,也就是窗口的高度。
const scrollHeight = document.documentElement.scrollTop + clientHeight; // 滚动条偏移文档顶部的高度(也就是文档从顶部开始到可视区域被抹去的高度) + 视口高度
复制代码
画了一张图方便理解:
而后就是计算该图片距离文档顶部的高度。有两种方法,第一种方法是经过元素的 offsetTop
属性来计算。从上图咱们了解到元素的 offsetTop 属性是相对于一个 position
为 非 static
的祖先元素,也就是 child.offsetParent
。同时须要将祖先元素的 border
考虑在内,咱们经过child.offsetParent.clientTop
能够拿到边框厚度。
由此咱们获得元素距离文档顶部的高度的计算方法:
function getTop(el, initVal) {
let top = el.offsetTop + initVal;
if (el.offsetParent !== null) {
top += el.offsetParent.clientTop;
return getTop(el.offsetParent, top);
} else {
return top;
}
}
复制代码
这里的这个方法使用了 尾递归调用
。能够提升递归性能。固然这里也能够用循环来实现:
function getTop(el) {
let top = el.offsetTop;
var parent = el.offsetParent;
while(parent !== null) {
top += parent.offsetTop + parent.clientTop;
parent = parent.offsetParent;
}
return top;
}
复制代码
第二种方法是使用 element.getBoundingClientRect()
API 直接获得 top 值。
getBoundingClientRect 的返回值以下图:
var first = document.getElementById('first');
getTop(first, 0); // 130
console.log(first.getBoundingClientRect().top); // 130
复制代码
因而咱们获得判断方法:
function inSight(el) {
const clientHeight = document.documentElement.clientHeight;
const scrollHeight = document.documentElement.scrollTop + clientHeight;
// 方法一
return getTop(el, 0) < scrollHeight;
// 方法二
// return el.getBoundingClientRect().top < clientHeight;
}
复制代码
接下来就是对每一个图片进行判断和赋值。
function loadImg(el) {
if (!el.src) {
el.src = el.dataset.src;
}
}
function checkImgs() {
const imgs = document.getElementsByTagName('img');
Array.from(imgs).forEach(el => {
if (inSight(el)) {
loadImg(el);
}
})
console.log(count++);
}
复制代码
最后给 window 绑定 onscroll
事件以及 onload
事件:
window.addEventListener('scroll', checkImgs, false);
window.onload = checkImgs;
复制代码
咱们知道相似 scroll
或 resize
这样的事件浏览器可能在很短的时间内触发不少次,为了提升网页性能,咱们须要一个节流函数来控制函数的屡次触发,在一段时间内(如 500ms)只执行一次回调。
/** * 持续触发事件,每隔一段时间,只执行一次事件。 * @param fun 要执行的函数 * @param delay 延迟时间 * @param time 在 time 时间内必须执行一次 */
function throttle(fun, delay, time) {
var timeout;
var previous = +new Date();
return function () {
var now = +new Date();
var context = this;
var args = arguments;
clearTimeout(timeout);
if (now - previous >= time) {
fun.apply(context, args);
previous = now;
} else {
timeout = setTimeout(function () {
fun.apply(context, args);
}, delay);
}
}
}
window.addEventListener('scroll', throttle(checkImgs, 200, 1000), false);
复制代码
方法二
HTML5 有一个新的 IntersectionObserver
API,它能够自动观察元素是否可见。
主要用法:
var observer = new IntersectionObserver(callback, option);
// 开始观察
observer.observe(document.getElementById('first'));
// 中止观察
observer.unobserve(document.getElementById('first'));
// 关闭观察器
observer.disconnect();
复制代码
目标的可见性发生变化时就会调用观察器的 callback。
function callback(changes: IntersectionObserverEntry[]) {
console.log(changes[0])
}
// IntersectionObserverEntry
{
time: 29.499999713152647,
intersectionRatio: 1,
boundingClientRect: DOMRectReadOnly {
bottom: 144,
height: 4,
left: 289,
right: 293,
top: 140,
width: 4,
x: 289,
y: 140
},
intersectionRect: DOMRectReadOnly,
isIntersecting: true,
rootBounds: DOMRectReadOnly,
target: img#first
}
复制代码
详细释义:
使用 IntersectionObserver 实现图片懒加载:
function query(tag) {
return Array.from(document.getElementsByTagName(tag));
}
var observer = new IntersectionObserver(
(changes) => {
changes.forEach((change) => {
if (change.intersectionRatio > 0) {
var img = change.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
})
}
)
query('img').forEach((item) => {
observer.observe(item);
})
复制代码
完整代码见 github
完 :)