在大多数前端项目中,我们不会考虑图片的加载方式,因为站点的图片较少,不必考虑图片加载带来的性能和体验问题。
对于图片展示类的站点(如相册),就需要考虑图片的加载问题了,这类站点上图片数量较多,如果在打开页面时就加载所有的图片,势必会造成网页卡顿,影响用户体验。
具体说来,大多数网页的 JavaScript 代码都是在网页加载完成后执行的,如果网页迟迟不能加载完成,那么相应的 JavaScript 代码就迟迟得不到执行,极大的影响用户体验。
另外,对于多图展示类站点,更没有必要一次将所有图片加载完了,因为用户可能只想看网站中的某几张甚至某一张图片,基于这种情况,如果一次性将所有的图片全部加载,造成页面卡顿不说,还白白浪费了带宽。
两种加载方式
针对上面提到的两种情况,有两种加载可供选择:预先加载和按需加载。
预先加载适用于相册类站点,在用户浏览某张图片时,预先加载其他的图片并缓存下来,而不是在点击“下一张”的时候再加载,可以提升用户体验。
按需加载适用于多图展示类站点,一般和瀑布流布局结合使用,即先加载一部分图片,在页面滚动到某个位置时,再加载一部分图片。
下面将分别介绍这两种加载方式的实现。
预先加载
先来一个糙糙的布局:
基础布局.png<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图片加载</title>
<style>
div{
width: 260px;
height: 20px;
margin: 0 auto;
text-align: center;
}
div img{
float: left;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div>
<img src = "imgs/2.jpg" />
<button>上一张</button>
<button>下一张</button>
</div>
</body>
</html>
点击上一张时向上切换,点击下一张时向下切换。
我们在调试工具中将网速设置为 2G 网速,使效果更明显一些:
先来看看不预先加载的情况,我们将图片地址存放在列表中,点击按钮时进行图片切换:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图片加载</title>
<style>
div{
width: 260px;
height: 200px;
margin: 0 auto;
text-align: center;
}
div img{
float: left;
margin-bottom: 10px;
width: 260px;
height:180px;
}
</style>
</head>
<body>
<div>
<img src = "https://img.haomeiwen.com/i3831834/7132cb1f36ecc157?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 />
<button onClick="switchImg(-1)">上一张</button>
<button onClick="switchImg(1)">下一张</button>
</div>
</body>
<script>
const Img = document.getElementById("img");
let index = 0;
const imgLists = [
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d91d48aa89298d03513393f84102d4a2&imgtype=0&src=http%3A%2F%2Ftupian.enterdesk.com%2F2015%2Fgha%2F09%2F1002%2F09.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=7c1d66a82b6fee78f3aaa75b552b7bc3&imgtype=0&src=http%3A%2F%2Fh8.86.cc%2Fwalls%2F20160302%2F1440x900_051b44f7f94f775.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d6a1e3fc760b1bdb65b82a8b0ebf75ee&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201505%2F11%2F20150511190138_caRJu.thumb.700_0.jpeg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=759be9f601fe3bee072ad675b373bcfa&imgtype=0&src=http%3A%2F%2Fpic.58pic.com%2F58pic%2F14%2F78%2F98%2F43U58PIC5te_1024.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=f0c860a395d4ad55c44df35f07825658&imgtype=0&src=http%3A%2F%2Fh8.86.cc%2Fwalls%2F20160225%2F1024x768_5a3b74c1f9c118e.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=856e500e46077540c9590b53d7058a94&imgtype=0&src=http%3A%2F%2Fpptdown.pptbz.com%2Fpptbeijing%2F%25CE%25C4%25D2%25D5%25D0%25A1%25C7%25E5%25D0%25C2%25BE%25B2%25D2%25DD%25CA%25B1%25B9%25E2%25CA%25F7%25D6%25A6PPT%25B1%25B3%25BE%25B0%25CD%25BC%25C6%25AC.jpg",
];
function switchImg(flag){
index += flag;
if(index <= 0){
index = 0;
}else if(index >= imgLists.length - 1){
index = imgLists.length - 1;
}
Img.src = imgLists[index];
}
</script>
</html>
看下效果:
不进行预处理.gif在点击下一张时再加载图片,可以看到有明显的延迟。我们调整下脚本代码,实现预加载,再看下效果:
...
const Img = document.getElementById("img");
let index = 0;
const imgLists = [
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d91d48aa89298d03513393f84102d4a2&imgtype=0&src=http%3A%2F%2Ftupian.enterdesk.com%2F2015%2Fgha%2F09%2F1002%2F09.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=7c1d66a82b6fee78f3aaa75b552b7bc3&imgtype=0&src=http%3A%2F%2Fh8.86.cc%2Fwalls%2F20160302%2F1440x900_051b44f7f94f775.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d6a1e3fc760b1bdb65b82a8b0ebf75ee&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201505%2F11%2F20150511190138_caRJu.thumb.700_0.jpeg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=759be9f601fe3bee072ad675b373bcfa&imgtype=0&src=http%3A%2F%2Fpic.58pic.com%2F58pic%2F14%2F78%2F98%2F43U58PIC5te_1024.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=f0c860a395d4ad55c44df35f07825658&imgtype=0&src=http%3A%2F%2Fh8.86.cc%2Fwalls%2F20160225%2F1024x768_5a3b74c1f9c118e.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=856e500e46077540c9590b53d7058a94&imgtype=0&src=http%3A%2F%2Fpptdown.pptbz.com%2Fpptbeijing%2F%25CE%25C4%25D2%25D5%25D0%25A1%25C7%25E5%25D0%25C2%25BE%25B2%25D2%25DD%25CA%25B1%25B9%25E2%25CA%25F7%25D6%25A6PPT%25B1%25B3%25BE%25B0%25CD%25BC%25C6%25AC.jpg",
];
function switchImg(flag){
index += flag;
if(index <= 0){
index = 0;
}else if(index >= imgLists.length - 1){
index = imgLists.length - 1;
}
Img.src = imgLists[index];
}
// 标志变量
let curr = 1;
function loadImg(){
const tmpImage = new Image();
tmpImage.src = imgLists[curr];
tmpImage.onload = function(){
// 图片加载成功后标志标志变量自增1
curr ++;
// 如果图片未与加载完,则继续递归
if(curr < imgLists.length){
loadImg();
}
}
}
loadImg();
...
看下效果:
预先加载.gif由于在看第一张图片时进行了预加载,切换下一张时直接从缓存中读取,用户体验更好了有木有。当然除了递归,还有很多种方式实现预加载,比如循环、定时器等,这个就看大家的喜好了。
再来看下预加载图片的网络请求:
按需加载
说完图片的预加载后,再来看下图片的按需加载,首先还是布局:
...
<head>
<meta charset="UTF-8">
<title>图片加载</title>
<style>
div{
width: 600px;
height: 300px;
margin: 0 auto;
text-align: center;
overflow: auto;
border: 1px solid red;
}
div img{
margin-bottom: 10px;
width: 260px;
height:180px;
}
</style>
</head>
<body>
<div>
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
<img src="" alt="">
</div>
</body>
...
按需加载布局.png
要在拖动滚动条时实现按需加载,首先需要解决这两个问题:
- 如何知道在滚动过程中该加载哪个位置的图片
- 如何知道应该加载哪一个 src 地址
页面上的图片什么时候应该开始加载呢?当图片位于页面可视区及以上时,表示这些图片应该被加载了,而位于可视区以下的图片不用加载。
如何存放图片的真实 src 地址呢?将图片真实的 src 地址存放在图片的一个自定义属性上,当图片位于可视区及以上时,将图片的 src 地址替换为自定义属性。
修改一下布局:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图片加载</title>
<style>
div{
width: 600px;
height: 300px;
margin: 0 auto;
text-align: center;
overflow: auto;
border: 1px solid red;
}
div img{
margin-bottom: 10px;
width: 260px;
height:180px;
}
</style>
</head>
<body>
<div id = "box">
</div>
</body>
<script>
for(let i = 0; i < 10; i++){
box.innerHTML += (
`
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d91d48aa89298d03513393f84102d4a2&imgtype=0&src=http%3A%2F%2Ftupian.enterdesk.com%2F2015%2Fgha%2F09%2F1002%2F09.jpg&tmp=${Math.random()}" />
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=7c1d66a82b6fee78f3aaa75b552b7bc3&imgtype=0&src=http%3A%2F%2Fh8.86.cc%2Fwalls%2F20160302%2F1440x900_051b44f7f94f775.jpg&tmp=${Math.random()}" />
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d6a1e3fc760b1bdb65b82a8b0ebf75ee&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201505%2F11%2F20150511190138_caRJu.thumb.700_0.jpeg&tmp=${Math.random()}" />
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=856e500e46077540c9590b53d7058a94&imgtype=0&src=http%3A%2F%2Fpptdown.pptbz.com%2Fpptbeijing%2F%25CE%25C4%25D2%25D5%25D0%25A1%25C7%25E5%25D0%25C2%25BE%25B2%25D2%25DD%25CA%25B1%25B9%25E2%25CA%25F7%25D6%25A6PPT%25B1%25B3%25BE%25B0%25CD%25BC%25C6%25AC.jpg&tmp=${Math.random()}" />
`
);
}
</script>
</html>
我们在每个图片后面加一个随机数子串,用来控制浏览器缓存,保证获取每个图片都要重新进行请求。
准备一个获取当前元素到页面顶部距离的函数:
function getTop(ele){
let top = 0;
while(ele){
top += ele.offsetTop;
ele = ele.offsetParent;
}
return top;
}
修改 JavaScript 代码,实现页面滚动时按需加载图片:
...
<script>
const box = document.getElementById("box");
for(let i = 0; i < 10; i++){
box.innerHTML += (
`
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d91d48aa89298d03513393f84102d4a2&imgtype=0&src=http%3A%2F%2Ftupian.enterdesk.com%2F2015%2Fgha%2F09%2F1002%2F09.jpg&tmp=${Math.random()}" />
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=7c1d66a82b6fee78f3aaa75b552b7bc3&imgtype=0&src=http%3A%2F%2Fh8.86.cc%2Fwalls%2F20160302%2F1440x900_051b44f7f94f775.jpg&tmp=${Math.random()}" />
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=d6a1e3fc760b1bdb65b82a8b0ebf75ee&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201505%2F11%2F20150511190138_caRJu.thumb.700_0.jpeg&tmp=${Math.random()}" />
<img src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495552287513&di=856e500e46077540c9590b53d7058a94&imgtype=0&src=http%3A%2F%2Fpptdown.pptbz.com%2Fpptbeijing%2F%25CE%25C4%25D2%25D5%25D0%25A1%25C7%25E5%25D0%25C2%25BE%25B2%25D2%25DD%25CA%25B1%25B9%25E2%25CA%25F7%25D6%25A6PPT%25B1%25B3%25BE%25B0%25CD%25BC%25C6%25AC.jpg&tmp=${Math.random()}" />
`
);
}
// 获取元素到页面顶部的距离
function getTop(ele){
let top = 0;
while(ele){
top += ele.offsetTop;
ele = ele.offsetParent;
}
return top;
}
// 获取所有的 img 元素
let imgs = [...document.getElementsByTagName("img")];
// 加载图片的函数
function loadImg(){
const boxScrollTop = box.scrollTop;
const boxClientHeight = box.clientHeight;
// 遍历所有的图片
for(let i = 0; i < imgs.length; i ++){
// 如果该图片位于可视区及上方,就加载这个图片
if(getTop(imgs[i]) < boxScrollTop + boxClientHeight){
imgs[i].src = imgs[i].getAttribute("_src");
// 加载对应的 src 后将该当前对象从 imgs 中移除,提高性能
imgs.splice(i,1);
i --;
}
}
}
// 初始化
loadImg();
box.addEventListener("scroll",loadImg);
</script>
...
看下页面效果:
按需加载效果.gifOK,一个最基本的按需图片加载就完成了。
按需加载扩展
按需加载有很多种方式,上面提到的是其中一种实现:先在页面生成一定数量的图片,在页面滚动时根据每个判断每个图片距离页面顶部的距离,如果该距离小于页面的滚动距离加上可视区的距离,就加载该图片。
这种实现方式的特点是:首先获取所有的图片数量,并在容器中追加相同数目的 img 标签,而后在页面滚动时按需加载。
还有一种常见的实现方式:不预先生成所有的图片,而是先生成一定数量的图片,图片下方提供一个“点击加载更多”的按钮,点击时再加载一定数量的图片,这也是按需加载的一种方式,并且通过这种方式,不需要绑定 onscroll 事件以及对每个图片进行 getTop 求值,只需在点击时对图片列表进行切片,然后加载切片中的图片即可。
总结
本文我们总结了两种图片加载的方式:预先加载和按需加载。预先加载适合于相册类项目,按需加载适合于图片较多的图片展示类项目。
完。
网友评论