当网速慢的时候,你是否有在网上看女神照片时,它是慢慢加载出整张图片的,每当要往下滚动时,它又会慢慢的加载出下一张女神的照片,这时候,作为程序员的你不应该抱怨太慢了,而是要边欣赏女神,边欣赏同僚们的杰作......
一、浏览器HTML文档的解析与渲染
- 当你打开一个网页时,浏览器会把它的HTML文档下载下来,然后构建DOM树,它反映了文档的结构。
- 接着它会将css文件下载,然后解析css样式表,构建CSSOM(CSS)对象树,它描述了页面的样式规则。
- 使用DOM树与CSSOM树构建渲染树,此时会应用样式规则,并且过滤掉不可见的元素。
在下载之前还会进行解析URL、启动DNS查询、建立TCP连接、发送HTTP/HTTPS请求等操作,这里我们先不谈,而是注重于页面渲染和体验懒加载的性能优化。
现在我们打开待会要分享的图片懒加载实例,看看浏览器做的一些事。
我们可以看到和上述步骤一致,但是,由于JS是单线程的,而浏览器是多线程的,它在解析下载文档和构建渲染树时,如果遇到了img、link、script......它会启动新的下载线程,将所有要下载的内容全部下载到位,这时候,如果下载内容过多,就会影响页面渲染的过程,从而影响用户的体验。
那么,有必要同时并发那么多的图片吗?当然是没有必要的,我们只需要尽快的渲染出页面,也就是先下载用户第一眼能看到的屏幕中的图片,至于下面的图片,完全可以等用户向下滚动时,在进行加载,那么,如何实现呢?
接下来进入今天的实战——图片懒加载
二、图片懒加载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./common.css">
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
</head>
<body>
<img src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg">
<img src="https://img.36krcdn.com/20190905/v2_1567641293753_img_png">
<img src="https://img.36krcdn.com/20190905/v2_1567640518658_img_png">
<img src="https://img.36krcdn.com/20190905/v2_1567642423719_img_000">
<img src="https://img.36krcdn.com/20190905/v2_1567642425030_img_000">
<img src="https://img.36krcdn.com/20190905/v2_1567642425101_img_000">
<img src="https://img.36krcdn.com/20190905/v2_1567642425061_img_000">
<img src="https://img.36krcdn.com/20190904/v2_1567591358070_img_jpg">
<img src="https://img.36krcdn.com/20190905/v2_1567641974410_img_000">
<img src="https://img.36krcdn.com/20190905/v2_1567641974454_img_000">
</body>
</html>
- 在body中放了十张图片,如果不做任何处理的话,浏览器会将十张图片全部下载,并且它会尝试并行下载这些资源以加快加载速度,但是,为了避免网络拥塞和服务器过载,游览器对并发的下载数量是有一定限制的。
我们一起来看一下浏览器的下载内容。
可以看见,它将下面用户未滚到的地方的图片也下载了,页面简单,图片数量少时,这样的作法并不会有太大的区别,但是,企业级开发的网站往往都比较复杂,资源种类也很多,所以,这样高并发的下载是不可行的,
- 这里我们介绍图片的懒加载技术来解决这个问题。
const imgs = document.getElementsByTagName('img');
const num = imgs.length;
let n = 0
document.addEventListener("DOMContentLoaded",()=>{
loadImage()
})
-
- 首先获取DOM元素,并在DOM完全加载完毕后执行loadImage函数。document.addEventListener是一个方法,用于向指定的DOM元素添加事件监听器,DOMContentLoaded是事件名称,表示DOM内容加载完成。
function loadImage(){
console.log('你好');
//是否在可视区?
let screenHeight = document.documentElement.clientHeight; //一屏的高度
//滚动条
//不同浏览器的兼容问题
let scrollTop = document.documentElement.scrollTop
|| document.body.scrollTop; //滚动条的偏移量
for(let i = 0;i<num; i++){
if(imgs[i].offsetTop < screenHeight + scrollTop){
//数据属性
imgs[i].src = imgs[i].dataset.src
n = i+1
if(n === num){
// console.log('所有图片加载完毕');
window.removeEventListener('scroll',loadImage)
}
}
}
}
-
- 定义一个loadImage方法,然后将img标签添加上data-src的属性,用于修改img的src属性,当图片进入可视区时,将data-src的值赋给src,
imgs[i].src = imgs[i].dataset.src。
- 定义一个loadImage方法,然后将img标签添加上data-src的属性,用于修改img的src属性,当图片进入可视区时,将data-src的值赋给src,
-
- 判断是否在可视区:获取屏幕高度
screenHeight,计算滚动条的偏移量scrollTop,这里考虑兼容性,浏览器能识别并执行document.documentElement.scrollTop的话,就不用考虑document.body.scrollTop,否则,使用后者。
- 判断是否在可视区:获取屏幕高度
-
- 加载图片:遍历每张图片,并记录已经加载的数量n,判断图片offsetTop(相对父元素的顶部边缘距离)是否小于(屏幕高度+滚动条的偏移量),true则为在可视区,设置相应图片的src,进行加载,所以图片加载完成后,移除loadImage方法。
让我们在看一下打开页面的下载
只下载了前三张要显示的图片。
但是,当我们看动态效果的时候,它似乎每动一点点,还没有到下一张图片时,它也会触发loadImage函数,打印你好。
很显然,这里触发的太灵敏了,以至于短短几秒触发了上百次。这里我们直接用引入的js中的_.throttle(loadImage,200),来设置节流,限制它的执行频率。
......
if(n === num){
// console.log('所有图片加载完毕');
window.removeEventListener('scroll',throttleLayLoad)
}
}
}
}
const throttleLayLoad = _.throttle(loadImage,200)
window.addEventListener('scroll',throttleLayLoad)
到这,我想咱们就简单的理清了浏览器干了的一些操作,以及图片懒加载的实现,今天的分享就到这啦,最后附上上述代码中简单的common.css文件内容。
*{
margin: 0;
padding: 0;
}
body{
background-color: gray;
}
img{
display: block;
margin-bottom:50px ;
width: 400px;
height: 400px;
}