图片一直是网络资源占用大户,对于一个前端有几百张图片的网站来说,如果首屏即加载所有图片(无论这些图片有没有被用户看到),那无疑是既浪费网络资源,又伤害用户体验的事。因此,图片懒加载,是提高前端性能的刚需所在。
目前,淘宝网、知乎等大流量网站都已经使用了图片滚动懒加载的方案——仅当图片滚入视窗,被用户看到的时候,才会去真正加载。
基本原理
图片滚动懒加载的原理非常简单:基于标签,在初次加载时,不把图片url放在src属性中,而是自定义一个属性,例如data-src。然后检测"scroll","resize"等窗体事件,判断图片是否进入了可视范围。如果进入,则将data-src的字段替换到src,此时浏览器会自动去加载对应图片资源。
Talking is cheap, show you the code
首先是不添加src的img标签,新增data-src用于放置图片url:
<img class&#61;"lazyImg" data-src&#61;"http://xxx.xxx.com"/>
然后&#xff0c;我们需要新增一个数组队列&#xff0c;来储存所有未加载的img节点&#xff1a;
var lazyImg&#61;[].slice.call(document.querySelectorAll(".lazyImg"));
为了方便&#xff0c;这里直接用querySelectorAll来获取所有img节点。注意因为NodeList是只读数组&#xff0c;因此需要将其转化为数组&#xff0c;方便之后的增删。在真实环境中&#xff0c;还需给每个成员添加其最近的可滚动祖先节点的引用&#xff0c;即el.parentNode。
最关键的部分来了&#xff0c;如何判断图片是否进入了可视区域&#xff0c;以及实现加载呢&#xff1f;
// 参数传入lazyImg数组
function loadImage(images){let scrollParent,src,el;for(let i &#61; 0;i
}
上面提到的scrollParent是带有scroll特性的祖先节点&#xff0c;具体实现&#xff1a;可使用getComputedStyle检查父节点是否设置了overflow(overflow-x,overflow-y)为"auto"或"scroll"&#xff0c;不断循环直到找到满足条件的祖先节点。
下面封装了判断是否在可视区域的函数&#xff1a;
const checkInView&#61;(el,scrollParent,offset)&#61;>{let scrollTop,scrollLeft,clientH,clientW;if(scrollParent &#61;&#61;&#61; window) {scrollTop&#61;document.documentElement.scrollTop||document.body.scrollTop;scrollLeft&#61;document.documentElement.scrollLeft||document.body.scrollLeft;clientH&#61;document.documentElement.clientHeight||document.body.clientHeight;clientW&#61;document.documentElement.clientWidth||document.body.clientWidth;}else {scrollTop &#61; scrollParent.scrollTop;scrollLeft&#61;scrollParent.scrollLeft;clientH &#61; scrollParent.clientHeight;clientW&#61;scrollParent.clientWidth;}while(el!&#61;scrollParent && el!&#61;null){let borderWidth&#61;parseInt(getStyle(el,"border-width"));offsetTop&#43;&#61;el.offsetTop&#43;borderWidth;offsetLeft&#43;&#61;el.offsetLeft&#43;borderWidth;el&#61;el.offsetParent;}//在这里判断是否滚入可视区域。offset为预留的预加载距离if(scrollTop&#43;clientH>el.offsetTop-offset && scrollLeft&#43;clientW>el.offsetLeft-offset){return true;}else return false;
}
最后再让各自的scrollParent监听"scroll"&#xff0c;"resize"等事件即可&#xff1a;
function initListener(el){let scrollParent&#61;getScrollParent(el);if(this.scrollParent.indexOf(scrollParent)<0){position &#61; getStyle(scrollParent, "position"); //若为window则返回nullif (position&#61;&#61;&#61;"" || position &#61;&#61;&#61; "static") scrollParent.style.position &#61; "relative"; //确保能检测到正确的offsetTop和offsetLeftthis.scrollParent.push(scrollParent); //数组用于保存已经监听的可滚动祖先节点this.eventsList.forEach((event)&#61;>{scrollParent.addEventListener(event,this.loadImage.bind(this));})}
}
以上便是实现图片懒加载的关键代码。
如果有动态添加的img标签&#xff0c;该怎么办呢&#xff1f;其实很简单&#xff0c;只需要将新增的img元素push进这个lazyImg数组队列中&#xff0c;然后调用InitListener即可。
完整实现
利用以上原理&#xff0c;我实现了一个基于vue2.x的图片懒加载的插件&#xff0c;完整代码可参考这里vue-lazyload-images。