该懒不一定是真懒💤[图片懒加载]
基本准备
当我们着手构建一个网页,展示一系列精心挑选的图片时,首先从HTML基础开始。想象一下,你的页面上散布着几个等待加载的图片元素。
* {
margin: 0;
padding: 0;
}
body {
background-color: gray;
}
img {
display: block;
margin-bottom: 50px;
width: 400px;
height: 400px;
}
<body>
<img data-src="https://img.36krcdn.com/20190905/v2_1567641293753_img_png">
<img data-src="https://img.36krcdn.com/20190905/v2_1567640518658_img_png">
<img data-src="https://img.36krcdn.com/20190905/v2_1567642423719_img_000">
<img data-src="https://img.36krcdn.com/20190905/v2_1567642425030_img_000">
<img data-src="https://img.36krcdn.com/20190905/v2_1567642425101_img_000">
<img data-src="https://img.36krcdn.com/20190905/v2_1567642425061_img_000">
<img data-src="https://img.36krcdn.com/20190904/v2_1567591358070_img_jpg">
<img data-src="https://img.36krcdn.com/20190905/v2_1567641974410_img_000">
<img data-src="https://img.36krcdn.com/20190905/v2_1567641974454_img_000">
</body>
思考浏览器会做什么?
当浏览器加载一个网页时,它会经历一系列复杂而有序的步骤来确保页面内容能够正确且高效地呈现给用户,先下载HTML,构建DOM树;接着应用CSS,生成渲染树;最后绘制页面。
-
初始化请求与HTML解析:
- 用户输入网址后,浏览器向服务器发送HTTP请求获取HTML文件。
- 下载HTML:一旦接收到HTML数据,浏览器开始解析这些标记语言,将其转换成文档对象模型(DOM)树。这个树状结构在内存中构建,代表了页面的结构。
-
CSS解析与渲染树构建:
- 同时,浏览器会查找页面中的
<link rel="stylesheet">
标签并下载CSS文件。 - 下载完成后,浏览器解析CSS规则,并结合DOM树,构造出渲染树(Render Tree) 。渲染树只包含渲染页面所需的样式信息和可见元素,忽略诸如
display: none
的元素。
- 同时,浏览器会查找页面中的
-
布局与绘制:
- 渲染树加上计算好的CSS样式后,浏览器进行布局,决定每个节点在屏幕上的确切位置和尺寸。
- 最后是绘制阶段,浏览器将渲染树的各个节点转化为像素,最终展现在屏幕上。
-
图片与外部资源处理:
- 在解析HTML过程中,浏览器遇到如
<img>
标签时,会立即启动新线程并发请求图片资源,不会等待整个HTML解析完成。 - 类似地,
<script>
、外部<link>
等也会触发独立的下载任务,这体现了浏览器的多线程下载能力,提高了资源加载效率。
- 在解析HTML过程中,浏览器遇到如
对于<img>
标签,一旦浏览器遇到,就会立即发起图片资源的请求。然而,如果图片数量众多,这样的做法显然不够高效。如果直接将图片URL放在src
属性中,浏览器会立即开始下载所有图片
这样会怎么样呢?就相当于开车上高速,同一时间一堆车冲进去,就会造成堵车,浏览器也会堵。
如何解决堵塞?
所以就要想想,真的有必要同时并发那么多图片吗?当然并发是很有必要的,总不能一个个去加载吧,只是需要有个限度,那这个限度是多少呢 如果不这样把图片都放进页面让它加载出来,那怎么才能看到所要的也页面呢?
对于页面的体验感,作为前端开发者是十分重要的。性能优化,尽快的显示页面就是天职。如果页面内的内容并不多,那就要当用户打开页面的时候就已经全部渲染完成,但是内容很多的时候,其实并没有必要把页面中的内容全一股脑都请求过来。当有时候网络比较卡,我们下拉页面就可能看到下面的内容全是空白,并没有加载出来,也就是说原本只是请求了这一个页面的内容,当刷到其他“地方”的时候,才会在这之前把相应的内容请求好渲染过来。这里就要介绍图片懒加载了
图片懒加载
它的核心思想是:仅在图片即将进入用户视线时才开始加载。这意味着,只有当用户滚动到某个图片附近,浏览器才开始下载该图片,大大减少了初次页面加载的时间和流量消耗。
我们手动控制图片的加载
<img data-price="20$" data-src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg">
第一次实现
如果给了img src 标签属性 会立马下载,在普通标签前加上data-使其成为自定义数据属性。也可以再加上其他数据属性,dataset一个元素可以有多个数据属性
<script>
// 获取页面中所有的<img>元素
const imgs = document.getElementsByTagName('img');
// 计算图片的数量
const num = imgs.length;
// 定义加载图片的函数
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) {
// 如果在视口中,则设置图片的src属性为原本的data-src值,开始加载图片
imgs[i].src = imgs[i].getAttribute('data-src');
// console.log(imgs[i].dataset.src, imgs[i].dataset.price);
}
}
}
// 给窗口添加滚动事件监听器,当用户滚动页面时会触发loadImage函数
window.addEventListener('scroll', loadImage);
</script>
实现2.0
进一步优化,已经加载过的不需要再去判断是否加载了,加上条件判断。
<script>
const imgs = document.getElementsByTagName('img')
const num = imgs.length
let n = 0
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].getAttribute('data-src')
// console.log(imgs[i].dataset.src,imgs[i].dataset.price);
n = i + 1
if (n === num) {
console.log('所有图片加载完成');
// 加载完最后的图片时移除函数
window.removeEventListener('scroll',loadImage)
}
}
}
}
// window.addEventListener('scroll',loadImage)
const throttleLayLoad = _.throttle(loadImage,350)
window.addEventListener('scroll',throttleLayLoad)
</script>
实现3.0
再进一步优化,当把script放到body的前面,这时候DOM树并没有建立,先执行script的内容才会接着执行后面,意味着script标签会造成阻塞。
<script>
const imgs = document.getElementsByTagName('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++) {
if (imgs[i].offsetTop < screenHeight + scrollTop) {
imgs[i].src = imgs[i].getAttribute('data-src')
// console.log(imgs[i].dataset.src,imgs[i].dataset.price);
n = i + 1
if (n === num) {
console.log('所有图片加载完成');
window.removeEventListener('scroll',throttleLayLoad)
}
}
}
}
window.addEventListener('scroll',loadImage)
</script>
实现4.0
接着进行优化,每次滑动的时候,判断滚动距离的函数会一直持续触发,太敏感。但其实没人会拉动页面只为了移动一点点滚动条。也就是说,当滑动停止的时候再判断触发函数就足够了,我们需要减少触发的次数。这就得提到防抖和节流。防抖和节流下次详细介绍。
来到booycdn的官网,www.bootcdn.cn/lodash.js/ ,复制链接来用<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
。
const imgs = document.getElementsByTagName('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++) {
if (imgs[i].offsetTop < screenHeight + scrollTop) {
imgs[i].src = imgs[i].getAttribute('data-src')
// console.log(imgs[i].dataset.src,imgs[i].dataset.price);
n = i + 1
if (n === num) {
console.log('所有图片加载完成');
window.removeEventListener('scroll',throttleLayLoad)
}
}
}
}
// window.addEventListener('scroll',loadImage)
// 使用Lodash的throttle函数限制加载检查的频率
const throttleLayLoad = _.throttle(loadImage,350)
window.addEventListener('scroll',throttleLayLoad)
</script>
总结
通过上述深入浅出的探讨与实践,我们不仅理解了图片懒加载技术的底层逻辑,也掌握了其实现步骤与优化策略。这一技术不仅极大地提升了网页的加载速度,优化了资源的使用效率,更是从前端层面为用户营造了更加流畅、响应迅速的浏览体验。希望看完对你有帮助,一起加油!✌️
转载自:https://juejin.cn/post/7382891971897770038