在互联网时代,用户对网页加载速度的要求日益严苛,每一秒的延迟都可能导致用户的流失。因此,性能优化成为了前端开发中的关键一环。图片作为网页中不可或缺的元素,往往占据了大部分的加载时间。为了平衡用户体验与内容丰富度,图片懒加载技术应运而生,它通过仅在必要时加载可视区域内的图片,有效提升了页面的初始加载速度,确保了用户能够更快地看到页面内容。
浏览器的工作机制与挑战
当浏览器开始加载一个网页时,它会构建DOM树来表示HTML结构,并创建渲染树来计算每个节点的具体样式。这个过程中,CSS文件的下载和解析对于构建渲染树至关重要。随后,结合DOM树和渲染树,页面的内容逐渐展现给用户。然而,对于包含大量图片的网页,如果所有图片同时开始下载,不仅会占用大量的网络带宽,还可能因并发数限制导致某些资源下载被阻塞,这被称为“网络拥塞”。为解决这个问题便出现了懒加载的概念。
懒加载的原理和实现
核心思想:打开页面时,仅让出现在首屏(视窗)的图片加载。
为实现懒加载,这里我们涉及到一个新的属性--数据属性。HTML元素中的数据属性,正式称为自定义数据属性(custom data attributes),是HTML5引入的一个特性,允许开发者在HTML元素上嵌入自定义的数据属性。这些属性以data-为前缀(如本次使用的data-src),用于存储与元素相关的私有信息(可通过例如dataset.src的方式进行使用),以便通过脚本(通常是JavaScript)来访问和操作。数据属性为非标准的属性提供了一个标准的添加方式,使得网页的标记更加语义化且易于维护。
实现过程
使用data-src替换src:传统的图片加载是通过<img src="image.jpg">来实现的,浏览器遇到这个标签时会立即发起图片资源的请求。而使用data-src时,图片标签变为<img data-src="image.jpg">,这意味着浏览器不会立即加载图片。
监听滚动事件:通过JavaScript监听窗口的滚动事件,判断图片是否处于或接近可视区域。这里可以利用screenHeight(视口高度)、scrollTop(页面滚动的距离)和offsetTop(图片距离最顶端的高度)等属性来计算。
如何判断在可视区域呢?
- 获取
screenHeight(视口高度)。
let screenHeight=document.documentElement.clientHeight;//一屏的高度
- 获取
scrollTop(页面滚动的距离)。
这里又是为何使用||来获取页面滚动距离呢?
因为考虑到浏览器兼容性的问题,在许多标准浏览器中,滚动条是附加在html元素上的,而在一些较旧或非标准的浏览器中,滚动条是附加在body元素上的。当可以获取到document.documentElement.scrollTop时,||左边表达式则为true,scrollTop的值就是左边表达式的值;当无法获取时,||左边表达式则为false,scrollTop的值就是右边表达式的值。
let scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
- 判断图片位置
根据下图可直观的看出,当图片距离顶端高度小于页面滚动距离与视窗高度之和时,图片进入视窗。
imgs[i].offsetTop<screenHeight+scrollTop
实现代码
其中添加了一个对DOMContentLoaded的事件监听,监听文档加载完成(DOMContentLoaded)事件执行一次loadImage(),实现刚进入页面时便加载完了视窗内的图片。
- 为何要添加这个事件?
因为当script出现在body上面时,会造成阻塞,html文件还没有加载,还不存在DOM树,loadImage()依旧无法执行,仍无法达到我们想要的结果。
<typle>
*{
margin: 0;
padding: 0;
}
body{
background-color: gray;
}
img{
display: block;
margin-bottom: 50px;
width: 800px;
height: 500px;
}
</typle>
<body>
<img data-src="https://img2.baidu.com/it/u=4162318305,1708423708&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" alt=""/>
<img data-src="https://img2.baidu.com/it/u=3593918601,3769474535&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"alt="">
<img data-src="https://img1.baidu.com/it/u=3381975780,2603842320&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"alt="">
<img data-src="https://img0.baidu.com/it/u=2862517077,376485844&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"alt="">
<img data-src="https://img2.baidu.com/it/u=725300263,4108720449&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"alt="">
<img data-src="https://img1.baidu.com/it/u=1748779186,2331724121&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"alt="">
<img data-src="https://img1.baidu.com/it/u=519702429,836914917&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=207"alt="">
<img data-src="https://img0.baidu.com/it/u=1367551702,3513497199&fm=253&fmt=auto&app=138&f=JPEG?w=813&h=500"alt="">
<img data-src="https://img1.baidu.com/it/u=2985101164,3623470960&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"alt="">
<img data-src="https://img1.baidu.com/it/u=3534825847,3840530522&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=313"alt="">
<img data-src="https://img2.baidu.com/it/u=994524234,3841382986&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"alt="">
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
const imgs=document.querySelectorAll('img');
const num=imgs.length;
let n=0;
document.addEventListener('DOMContentLoaded',()=>{
loadImage();
})
function loadImage(){
console.log("开始加载");
//是否可视区
let screenHeight=document.documentElement.clientHeight;//一屏的高度
//滚动条
//不同浏览器的兼容性问题
let scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
for(let i=0;i<num;i++){
// console.log(imgs[i].offsetTop);
if(imgs[i].offsetTop<screenHeight+scrollTop){
//数据属性
// console.log(imgs[i].dataset.src);
imgs[i].src=imgs[i].dataset.src
n = i + 1;
if (n == num) {
console.log('所有图片已加载');
window.removeEventListener('scroll', loadImage);
}
}
}
}
window.addEventListener('scroll',loadImage)
</script>
</body>
性能优化--节流和防抖
节流
概念:节流保证一个函数在一定时间内最多执行一次。这意味着,即使事件被非常快速地连续触发,节流函数也会确保该函数在指定的时间间隔内只执行一次。它适用于那些需要限制操作频率但又希望保留一定响应性的场景。
本次示例中,我们可以通过判断当图片加载完后对loadImage进行节流,设置每500ms执行一次。
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
const imgs = document.querySelectorAll('img');
const num = imgs.length;
let n = 0;
document.addEventListener('DOMContentLoaded', () => {
loadImage();
})
function loadImage() {
console.log("开始加载");
//是否可视区
let screenHeight = document.documentElement.clientHeight;//一屏的高度
//滚动条
//不同浏览器的兼容性问题
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < num; i++) {
// console.log(imgs[i].offsetTop);
if (imgs[i].offsetTop < screenHeight + scrollTop) {
//数据属性
// console.log(imgs[i].dataset.src);
imgs[i].src = imgs[i].dataset.src
// imgs[i].src=imgs[i].getAttribute('data-src');
n = i + 1;
if (n == num) {
console.log('所有图片已加载');
window.removeEventListener('scroll', throttleLsyLoad);
}
}
}
}
// 节流 ---- 一段时间执行一次
const throttleLayLoad=_.throttle(loadImage,500);
window.addEventListener('scroll', throttleLayLoad);
</script>
防抖
概念:防抖确保一个函数在最后一次触发后的一段时间内才执行。如果在延迟期内再次触发该函数,则清除之前的计时器并重新开始计时。这样,只有当用户停止操作一段时间后,函数才会执行,常用于避免因连续快速的操作导致的重复执行。
在本文实例中,就是当用户停止滚动时超过500ms后,loadImage进行执行,一次性加载完需要加载的图片。
<script>
const imgs = document.querySelectorAll('img');
const num = imgs.length;
let n = 0;
document.addEventListener('DOMContentLoaded', () => {
loadImage();
})
function loadImage() {
console.log("开始加载");
//是否可视区
let screenHeight = document.documentElement.clientHeight;//一屏的高度
//滚动条
//不同浏览器的兼容性问题
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < num; i++) {
// console.log(imgs[i].offsetTop);
if (imgs[i].offsetTop < screenHeight + scrollTop) {
//数据属性
// console.log(imgs[i].dataset.src);
imgs[i].src = imgs[i].dataset.src
// imgs[i].src=imgs[i].getAttribute('data-src');
n = i + 1;
if (n == num) {
console.log('所有图片已加载');
window.removeEventListener('scroll', bounceLoadImage);
}
}
}
}
// 防抖---- 最后一次
function debounce(foo) {
let timer = null;
return function () {
if (timer) clearInterval(timer);
timer = setTimeout(() => loadImage(), 500);
}
}
const bounceLoadImage = debounce(loadImage);
window.addEventListener('scroll', bounceLoadImage)
</script>
总结
实施图片懒加载不仅能显著提升网页的首屏加载速度,增强用户体验,还能在一定程度上节省服务器带宽资源。然而,开发者在应用此技术时也需注意权衡利弊,比如过度依赖JavaScript可能导致页面在禁用脚本的环境下无法正常显示图片,或是对于SEO的潜在影响。因此,在追求高性能的同时,也要确保网页的可访问性和搜索引擎友好性。