美文网首页JavaScriptJS
图片加载之预先加载和按需加载

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

作者: 柏丘君 | 来源:发表于2017-05-23 22:09 被阅读156次

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

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

    预加载的网络请求.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 求值,只需在点击时对图片列表进行切片,然后加载切片中的图片即可。

    总结

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

    完。

    相关文章

      网友评论

        本文标题:图片加载之预先加载和按需加载

        本文链接:https://www.haomeiwen.com/subject/rkugxxtx.html