一般用户打开网页时,整个网页的内容将被下载而且呈如今一个页面中,虽然容许浏览器缓存页面,可是不能保证用户查看全部下载的的内容,例如一个照片墙应用,可能用户仅仅查看第一个图片以后离开,结果就是白白浪费了内存和带宽。所以咱们须要当用户须要访问页面的一部分时才去加载内容,而不是一看是就去加载所有内容。node
当有人向网页(图像,视频)等资源,资源引用一个小的占位符,当用户浏览网页,实际的资源被浏览器缓存,而且当资源在屏幕上可见时替换占位符,例如,若是用户加载网页并当即离开网页,则除了网页的顶部以外没有任何内容被加载。 api
以加载图片为例子,咱们须要将img
标签中设置一个data-src
属性,它指向的是实际上咱们须要加载的图像,而img
的src
指向一张默认的图片,若是为空的话也会向服务器发送请求。浏览器
<img src="default.jpg" data-src="www.example.com/1.jpg"> 复制代码
以后当用户访问的可视区域的img
元素时,将src
得值替换为data-src
指向的实际资源加载的图像缓存
具体代码bash
const lazy = (el) => { let scrTop = getTop(); let windowHeight = document.documentElement.clientHeight; function getTop(){ return document.documentElement.scrollTop || document.body.scrollTop; } function getOffset(node){ return node.getBoundingClientRect().top + scrTop; } function inView(node){ // 设立阈值 const threshold = 0; const viewTop = scrTop; const viewBot = viewTop + windowHeight; const nodeTop = getOffset(node); const nodeBot = nodeTop + node.offsetHeight; const offset = (threshold / 100) * windowHeight; console.log((nodeBot >= viewTop - offset), (nodeTop <= viewBot + offset)) return (nodeBot >= viewTop - offset) && (nodeTop <= viewBot + offset) } function check(node){ let el = document.querySelector(node); let images = [...el.querySelectorAll('img')]; images.forEach(img => { if(inView(img)){ img.src = img.dataset.src; } }) } check(el); } window.onscroll = function(){ lazy('.foo'); } 复制代码
经过上面例子的实现,咱们要实现懒加载都须要去监听scroll事件,尽管咱们能够经过函数节流的方式来阻止高频率的执行函数,可是咱们仍是须要去计算scrollTop
,offsetHeight
等属性,有没有简单的不须要计算这些属性的方式呢,答案是有的---IntersectionObserver服务器
根据MDN:markdown
IntersectionObserver API为开发者提供了一种能够异步监听目标元素与其祖先或视窗(viewport)处于交叉状态的方式。祖先元素与视窗(viewport)被称为根(root)。app
简单来讲就是观察一个元素和另外一个元素是否重叠。框架
IntersectionObserver初始化的过程当中提供了三个主要元素的配置:异步
root
指向的是浏览器的视口,但实际上能够是任意的DOM元素,要注意的是:root
在这种状况下,要观察元素的必选要在root表明的Dom元素内部
margin
CSS相似,好比rootMargin: '50px 20px 10px 40px'
(top, right, bottom, left)
intersectionObserver
咱们想要得配置,咱们只须要将咱们得config
对象和咱们的回调函数一块儿传递到Observer构造函数中const config = { root: null, rootMargin: '0px', threshold: 0.5 } let observer = new IntersectionObserver(fucntion(entries){ // ... }, config) 复制代码
如今咱们须要去给IntersectionObserver
实际观察的元素
const img = document.querySelector('image'); observer.observe(img); 复制代码
关于这个实际观察的元素须要注意几点
root
表明的DOM元素中IntersectionObserver
一次只能接受一个观察元素,不支持批量观察。这意味着若是你须要观察几个元素(好比说一个页面上的几个图像),你必须遍历全部元素并分别观察它们中的每个const images = document.querySelecttorAll('img'); images.forEach(image => { observer.observe(image) }) 复制代码
IntersectionObserver回调函数
new IntersectionObserver(function(entries, self)) 复制代码
在entries
咱们获得咱们的回调函数做为Array是特殊类型的:IntersectionObserverEntry
首先IntersectionObserverEntry
含有三个不一样的矩形的信息
root
+ rootMargin
)'的矩形IntersectionObserverEntry
还提供了isIntersecting
,这是一个方便的属性,返回观察元素是否与捕获框架相交, 另外,IntersectionObserverEntry
提供了利于计算的遍历属性intersctionRatio
:返回intersectionRect 与 boundingClientRect 的比例值.
target
则返回要观察的元素 好了简单介绍完,让咱们回到正题,用这个IntersectionObserver
来实现代化的懒加载方式吧
const images = document.querySelectorAll('[data-src]') const config = { rootMargin: '0px', threshold: 0 }; let observer = new IntersectionObserver((entries, self)=>{ entries.forEach(entry => { if(entry.isIntersecting){ // 加载图像 preloadImage(entry.target); // 解除观察 self.unobserve(entry.target) } }) }, config) images.forEach(image => { observer.observe(image); }); function preloadImage(img) { const src = img.dataset.src if (!src) { return; } img.src = src; } 复制代码
相比于以前懒加载的方式是否是更加简洁,并且只有当观察元素和捕捉框架交叉或重叠时,才会触发回掉函数(加载页面时也会触发回调函数,不过咱们能够用isIntersecting
来进行判断是否和观察元素相交)
参考:
Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver