传统方案
监听scroll事件,判断图片是否在可视区域内(通过getBoundingClientRect获取图片位置信息)
缺点:scroll事件十分频繁,计算量大,容易造成性能问题,可以通过节流处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const viewHeight = window.innerHeight || document.documentElement.clientHeight const imgs = document.querySelectorAll('img') let threshold = 100 window.onload = lazyLoad document.addEventListener('scroll', function (e) { lazyLoad() }) function lazyLoad () { imgs.forEach(img => { let { top } = img.getBoundingClientRect() if (img.dataset.src && top - viewHeight < threshold) { img.src = img.dataset.src; img.removeAttribute('data-src') } }) }
|
源码
利用IntersectionObserver API实现的方案
关于IntersectionObserver的使用方法,可以参考阮一峰的文章IntersectionObserver API 使用教程
这种方案是去生成一个观察者,判断它观察的dom元素是否在视口之内
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
| let imgs = document.querySelectorAll('img') let imgsLen = imgs.length let count = 0 let eachRetryTimes = 3 // 每张图片可以重试三次 // 加载后的处理 function addOneSuccess (observer, target) { target.removeAttribute('data-src') target.removeAttribute('data-retry') observer.unobserve(target) if (count === imgsLen) { // 全都加载完毕,可以关闭观察器 observer.disconnect() } } // 目标元素的可见性变化时,就会调用观察器的回调函数callback。 // callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。 let observer = new IntersectionObserver((entries) => { entries.forEach(e => { // 如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。 // intersectionRatio表示目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0 if (e.intersectionRatio > 0 && e.target.dataset.src) { // 可见 e.target.src = e.target.dataset.src let retry = Number(e.target.dataset.retry || 0) e.target.setAttribute('data-retry', ++retry) e.target.onload = function () { addOneSuccess(observer, this) } e.target.onerror = function () { let retry = this.dataset.retry // 重试次数 if (Number(retry) === eachRetryTimes) { addOneSuccess(observer, this) } } } }) }) imgs.forEach(img => { observer.observe(img) }) imgs = null
|
源码
【参考】
IntersectionObserver API 使用教程