本文写于 2013 年,文中记录的是当时通过 JavaScript 和滚动事件实现图片延时加载的实践方式。现代浏览器已经提供了原生图片懒加载等方案,请结合文章发布时间阅读。
当一个网页中包含大量图片时,图片往往会成为影响网站加载速度的重要因素。
特别是一些图片展示较多的网站,如果页面打开时一次性加载所有图片,不仅会增加用户等待时间,还会带来更多不必要的网络请求和服务器资源消耗。
其实很多图片并不是用户打开页面后立即需要看到的。
例如,一个页面上方是文字内容,而下方有多张容量比较大的图片。用户打开网页时,第一屏可能根本看不到这些图片,甚至有些用户浏览完上面的内容之后就直接离开了页面。
如果在页面加载时就把所有图片全部下载下来,显然会造成一定程度的资源浪费。
因此,我想到一个比较简单的办法:
用户看到图片之前,不加载真正的图片;等图片即将进入浏览器可视区域时,再开始加载。
这也就是网页图片延时加载的基本思路。
图片为什么会自动加载?
浏览器在解析 HTML 文档时,遇到图片标签:
<img src="image.jpg"/>
会根据 src 属性向服务器请求对应的图片。
例如,一个页面中有:
<img src="1.jpg"/>
<img src="2.jpg"/>
<img src="3.jpg"/>
那么浏览器就会分别请求这些图片资源。
如果一个页面有几十张甚至上百张图片,页面打开时也会产生大量图片请求。
但问题是,其中很多图片可能根本不在当前屏幕中。
使用占位图片
图片延时加载的思路,就是先不把真实图片地址放到 src 属性中。
例如原本的图片:
<img src="reallyimg.jpg"/>
可以先改成:
<img src="space.jpg"data="reallyimg.jpg"/>
其中:
是一张统一的占位图片。
而真正的图片地址:
则暂时保存在另外一个属性中。
这样,如果页面中有 10 张需要延时加载的图片:
<img src="space.jpg"data="1.jpg"/>
<img src="space.jpg"data="2.jpg"/>
<img src="space.jpg"data="3.jpg"/>
浏览器实际开始时只需要加载占位图片,而不会立即下载所有真实的大图片。
等到需要显示时,再通过 JavaScript 把真实地址赋值给src,图片就会开始加载。
一个实际场景
假设一个网页的结构是这样的:
用户刚刚打开页面时,可能只能看到上面的文字介绍。
下面的 8 张图片并不在浏览器当前可视区域中。
由于并不是所有用户都会继续向下滚动页面,因此页面加载时完全没有必要立即下载这些图片。
这时候可以先让所有图片使用space.jpg 作为占位。
然后监听用户的滚动操作。
当用户向下滚动,某张图片即将出现在浏览器屏幕中时,再加载这张图片。
整个过程其实只需要完成两件事情:
- 编写一个加载真实图片的函数
- 在页面滚动时判断图片是否进入当前显示区域
加载图片函数
首先编写一个用于加载真实图片的函数:
function loadimg(obj){
var imgsrc = obj.getAttribute("data");
if(imgsrc){
obj.src =imgsrc;
obj.removeAttribute("data");
}else{
return false;
}
}函数的逻辑很简单。
首先获取图片中保存的真实地址:
var imgsrc=obj.getAttribute("data");如果存在真实图片地址,就将它重新赋值给:obj.src 也就是:obj.src =imgsrc;
浏览器发现 src 属性发生变化后,就会开始请求真正的图片。
加载完成之后,再删除原来的data属性。
这样下一次继续滚动页面时,这张已经加载过的图片就不会重复处理。
判断图片是否进入可视区域
接下来监听页面的滚动事件:
window.onscroll =function(){
var bodyheight=document.body.clientHeight;
var imgobj=document.getElementsByTagName("ul")[0].getElementsByTagName("img");
var scrollTop=document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
for(vari=0; i<imgobj.length; i++){
if(imgobj[i].offsetTop -scrollTop<bodyheight){
loadimg(imgobj[i]);
}
}
}这里主要获取几个数据。
首先获取当前浏览器窗口的高度:
var bodyheight=document.body.clientHeight;
然后获取页面已经向下滚动的距离:
var scrollTop=document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
再获取页面中的图片:
var imgobj=document.getElementsByTagName("ul")[0].getElementsByTagName("img");最后遍历所有图片:
for(vari=0; i<imgobj.length; i++)
判断图片当前位置减去页面已经滚动的距离后,是否进入当前浏览器的显示范围。
如果图片已经出现在可视区域:loadimg(imgobj[i]);就调用前面编写的函数加载真实图片。
完整示例
完整代码如下:
<!DOCTYPE HTML>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>图片延时加载技术</title>
<style>
* {
margin: 0;
padding: 0;
}
img {
border: none;
}
ul,
li {
list-style: none;
}
.space {
width: 900px;
height: 700px;
background: #ccc;
margin: auto;
text-align: center;
line-height: 700px;
color: white;
font-size: 80px;
}
ul {
text-align: center;
margin: 10px auto;
width: 900px;
}
ul li {
float: left;
margin: 10px;
}
ul li img {
width: 200px;
}
</style>
<script>
function loadimg(obj){
var imgsrc = obj.getAttribute("data");
if(imgsrc){
obj.src = imgsrc;
obj.removeAttribute("data");
}else{
return false;
}
}
window.onscroll = function(){
var bodyheight = document.body.clientHeight;
var imgobj = document
.getElementsByTagName("ul")[0]
.getElementsByTagName("img");
var scrollTop =
document.documentElement.scrollTop ||
window.pageYOffset ||
document.body.scrollTop;
for(var i = 0; i < imgobj.length; i++){
if(imgobj[i].offsetTop - scrollTop < bodyheight){
loadimg(imgobj[i]);
}
}
}
</script>
</head>
<body>
<div class="space">
这里是其它内容
</div>
<ul>
<li><img src="space.jpg" data="1.jpg" /></li>
<li><img src="space.jpg" data="2.jpg" /></li>
<li><img src="space.jpg" data="3.jpg" /></li>
<li><img src="space.jpg" data="4.jpg" /></li>
<li><img src="space.jpg" data="5.jpg" /></li>
<li><img src="space.jpg" data="6.jpg" /></li>
<li><img src="space.jpg" data="7.jpg" /></li>
<li><img src="space.jpg" data="8.jpg" /></li>
</ul>
</body>
</html>
还可以继续完善
上面的代码只是一个比较基础的实现方式。
实际使用时,还可以增加一些更友好的处理,例如:
- 图片加载过程中显示加载状态
- 图片加载失败后显示提示
- 图片加载失败后自动重新尝试
- 图片即将进入屏幕前提前加载
- 防止滚动事件频繁触发造成重复计算
不过从基本原理来看,图片延时加载的核心并不复杂:
先使用占位内容,真正需要显示时,再请求实际资源。
对于图片比较多的网页来说,这样可以避免一次性加载大量暂时不需要的图片,从而减少初始页面加载的资源消耗。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。