图片加载之预先加载和按需加载

在大多数前端项目中,我们不会考虑图片的加载方式,因为站点的图片较少,不必考虑图片加载带来的性能和体验问题。
对于图片展示类的站点(如相册),就需要考虑图片的加载问题了,这类站点上图片数量较多,如果在打开页面时就加载所有的图片,势必会造成网页卡顿,影响用户体验。
具体说来,大多数网页的 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 网速,使效果更明显一些:

设置网速.png

先来看看不预先加载的情况,我们将图片地址存放在列表中,点击按钮时进行图片切换:

<!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 = "http://upload-images.jianshu.io/upload_images/3831834-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

由于在看第一张图片时进行了预加载,切换下一张时直接从缓存中读取,用户体验更好了有木有。当然除了递归,还有很多种方式实现预加载,比如循环、定时器等,这个就看大家的喜好了。
再来看下预加载图片的网络请求:

预加载的网络请求.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>
...

看下页面效果:

按需加载效果.gif

OK,一个最基本的按需图片加载就完成了。

按需加载扩展

按需加载有很多种方式,上面提到的是其中一种实现:先在页面生成一定数量的图片,在页面滚动时根据每个判断每个图片距离页面顶部的距离,如果该距离小于页面的滚动距离加上可视区的距离,就加载该图片。
这种实现方式的特点是:首先获取所有的图片数量,并在容器中追加相同数目的 img 标签,而后在页面滚动时按需加载。
还有一种常见的实现方式:不预先生成所有的图片,而是先生成一定数量的图片,图片下方提供一个“点击加载更多”的按钮,点击时再加载一定数量的图片,这也是按需加载的一种方式,并且通过这种方式,不需要绑定 onscroll 事件以及对每个图片进行 getTop 求值,只需在点击时对图片列表进行切片,然后加载切片中的图片即可。

总结

本文我们总结了两种图片加载的方式:预先加载和按需加载。预先加载适合于相册类项目,按需加载适合于图片较多的图片展示类项目。

完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容