图片懒加载的原理是没有在可视区域的图片暂时不加载图片,等进入可视区域后在加载图片,这样可以减少初始页面加载的图片数量而提升页面加载速度。 图片懒加载在提升页面加载速度的同时也会伴随用户看其他未展示的图片时会有等待时间;图片加载显示会伴有布局抖动等问题。
方案
图片懒加载的关键是:判断一个元素是否在可视区域。
img的loading属性设为“lazy”
HTMLImageElement 的 loading 属性为一个字符串,它的值会提示 用户代理 告诉浏览器不在可视视口内的图片该如何加载。这样一来,通过推迟图片加载仅让其在需要的时候加载而非页面初始载入时立刻加载,优化了页面的载入。
lazy 告诉用户代理推迟图片加载直到浏览器认为其需要立即加载时才去加载。例如,如果用户正在往下滚动页面,值为 lazy 会导致图片仅在马上要出现在 可视视口中时开始加载。
使用方法
1 2
| <img src="xxx.jpg" loading="lazy" />
|
缺点
虽然整个方案简单性能好,但问题也是最多的,所以很少使用这种方案。
- 前面提到图片懒加载用户看其他未展示的图片时会有等待时间,一般会设置一个默认图片,这种方案不能设置默认图片
- 图片的加载数量和图片的布局、可视区域尺寸有关,难以控制
- 图片的加载顺序也难以控制
该方案能粗略的实现图片懒加载基本功能。
通过offsetTop来计算是否在可视区域内
可视区域高度是 document.documentElement.clientHeight ,而可视区域的位置是在滚动条滚动位置 scrollTop 到 scrollTop+document.documentElement.clientHeight之间。因此通过 image.offsetTop <= document.documentElement.clientHeight + document.documentElement.scrollTop 判断图片是否可以在可视区域内。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function lazyload() { var lazyImages = document.querySelectorAll(".lazyload"); lazyImages.forEach(function (image) { if ( image.offsetTop <= document.documentElement.clientHeight + document.documentElement.scrollTop) { image.src = image.getAttribute("data-src"); } }); } window.onscroll = function () { lazyload(); };
|
1 2
| <img src="./default.gif" class="lazyload" data-src="./photo-1.jpg" />
|
采用getBoundingClientRect
原理都是一样的,只不过是判断元素出现在可视范围内的方式不同。
getBoundingClientRect用于获取其相对于视口的位置,这种方式更容易理解。
该api返回值是一个 DOMRect对象,拥有left, top, right, bottom, x, y, width, 和 height属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function isInViewPort(element) { const viewWidth = window.innerWidth || document.documentElement.clientWidth; const viewHeight = window.innerHeight || document.documentElement.clientHeight; const { top, right, bottom, left, } = element.getBoundingClientRect(); return ( top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight ); }
|
Intersection Observer
IntersectionObserver的作用是监听目标元素与祖先元素是否有相交(默认是浏览器的视口),如果相交就触发事件。使用IntersectionObserver完成懒加载,优势在于我们不用再去计算元素距离判断图片是否已经出现在视口了,当图片出现在视口内时它会自动触发回调,完成我们回调内部的业务逻辑。
使用步骤主要分为两步:创建观察者和传入被观察者
创建观察者
1 2 3 4 5 6 7 8 9 10 11
| const options = { threshold: 1.0, root:document.querySelector('#scrollArea') }; const callback = (entries, observer) => { ....} const observer = new IntersectionObserver(callback, options);
|
通过new IntersectionObserver创建了观察者 observer,传入的参数 callback 在重叠比例超过 threshold 时会被执行`
关于callback回调函数常用属性如下:
1 2 3 4 5 6 7 8 9 10 11 12
| const callback = function(entries, observer) { entries.forEach(entry => { entry.time; entry.rootBounds; entry.boundingClientRect; entry.intersectionRect; entry.intersectionRatio; entry.target; }); };
|
传入被观察者
通过 observer.observe(target) 这一行代码即可简单的注册被观察者
1 2 3
| const target = document.querySelector('.target'); observer.observe(target);
|
举例
假如我们需要实现图片加载前loading的效果的话,我们就可以给img的src设为loading图片的路径,data-src设为图片真实路径,在上述callback函数,将data-src的值赋给src即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #app { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
.img { height: 300px; margin: 10px; } </style> </head>
<body> <div id="app"> <img class="img" src="./loading.gif" data-src="https://p26-passport.byteacctimg.com/img/user-avatar/fd965033fba9d2b1b67d0dd6d1c80ad3~150x150.awebp" alt=""> <!-- ......此处省略一堆img --> </div> </body> <script> const config = { root:null, //监听元素的祖先元素dom对象,其边界盒将被视作视口。默认为浏览器视口 rootMargin:'0px 0px 0px 0px', // 距离视口多远触发回调函数 threshold:'0' // 目标元素与设置root元素相交的比例,若指定值为 1.0,则意味着整个元素都在可见范围内时才触发回调 } var observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { // isIntersecting为true说明出现在视口了 if (entry.isIntersecting) { entry.target.src = entry.target.dataset.src // 更改过地址的img 就取消监听 优化性能 observer.unobserve(entry.target) } }) },config );
const imgs = document.querySelectorAll('img') // 使用observer.observe监听所有的img imgs.forEach(img => { observer.observe(img) })
</script>
</html>
|
优点
Intersection Observer的优点在于不需要对事件进行监听,而上面两个方法都需要对类似于scroll事件进行监听,如果渲染列表很长,有可能会造成页面卡顿,因为scroll事件伴随了大量的计算,会造成资源方面的浪费