数字营销 · Web开发 · 基础设施

网页图片延时加载技术

记录通过 JavaScript 监听页面滚动,在图片进入可视区域时再加载真实资源的网页图片延时加载实践。

本文写于 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"/>

其中:

space.jpg

是一张统一的占位图片。

而真正的图片地址:

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,图片就会开始加载。

一个实际场景

假设一个网页的结构是这样的:

网页顶部
├── 文字介绍
├── 文字介绍
├── 文字介绍
├── 图片 1
├── 图片 2
├── 图片 3
├── 图片 4
├── 图片 5
├── 图片 6
├── 图片 7
└── 图片 8

用户刚刚打开页面时,可能只能看到上面的文字介绍。

下面的 8 张图片并不在浏览器当前可视区域中。

由于并不是所有用户都会继续向下滚动页面,因此页面加载时完全没有必要立即下载这些图片。

这时候可以先让所有图片使用space.jpg 作为占位。

然后监听用户的滚动操作。

当用户向下滚动,某张图片即将出现在浏览器屏幕中时,再加载这张图片。

整个过程其实只需要完成两件事情:

  1. 编写一个加载真实图片的函数
  2. 在页面滚动时判断图片是否进入当前显示区域

加载图片函数

首先编写一个用于加载真实图片的函数:

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

欢迎分享你的看法,也欢迎补充不同的实践经验。