Preload圖片預加載(jQuery插件)

背景

我們在做頁面的時候,從用戶體驗的角度出發,肯定是希望用戶以最快的速度看到完整的頁面信息,但在實際情況中經常會遇到些問題。

比如受網速影響,頁面加載素材的時間比較長,頁面會出現短時間的錯亂或者閃屏;

或者頁面素材比較多、比較大,需要一定的加載時間,特別有時候的活動頁面,我們一般會把首屏做的更多多樣化或者傳達比較豐富的氛圍的時候,我們的首屏素材上都會比較大,有時候也會給元素加上動畫來傳達信息,比如我之前做的WeGame新春活動頁就是這樣的情況;

一般我們的處理方案是在頁面素材加載完成之前顯示一個有趣(不那麼枯燥)的loading頁,那麼從重構的角度出發,我們這個素材加載狀態該怎麼配合loading頁做處理呢,於是想到了可以監測一下瀏覽器加載圖片的狀態,並同時響應處理,比如加載完時隱藏loading頁,每加載完一張圖片就更新加載的百分比進度條等。

當然,值得注意的是,我們應該儘量的讓用戶等待loading頁的時間變短,因爲不要忘了,loading頁也只是向後兼容處理的方案,它本身不是必須存在的。

方案分析

場景:除了首屏loading頁加載必須的圖片之外(無序加載),可能還有其他情景比如漫畫網站,需要加載完前一張圖片再加載下一張(有序加載)

原理:通過image 的 onload 事件判斷瀏覽器加載圖片的狀態。

實現:一般我們會這樣處理:

 var img = new Image();
 img.src = "logo.jpg";
 img.onload = function () {
    alert("image is loaded");
 };

但是這樣處理有點問題,首先我們來認識兩點:

1.一旦Image對象設置了src值,瀏覽器就會向服務器發起請求,並緩存返回的圖片。

2.瀏覽器請求是異步的。也就是說這段代碼會遍歷數組,每張圖片幾乎同時發起請求,並不需要等待服務器返回結果後順序發起請求。就是說,js不會傻傻地在原地等待圖片的加載,而是繼續讀代碼,直到圖片加載完成,觸發onload事件,js纔會回來執行onload裏面的內容。

基於以上兩點,如果我們先賦值了圖片地址,瀏覽器已經加載完成的時候,onload綁定事件還沒有賦值上去,這樣就造成了一定的誤差,所以改進如下:

 var img = new Image();
 img.onload = function () {
    alert("image is loaded");
 };
 img.src = "logo.jpg";

先綁定事件,在賦值圖片地址,確保瀏覽器發起請求前,圖片已經綁定了onload事件。

Preload插件實現

GitHub地址:https://github.com/xiangshuo1992/preload

/**
 * 圖片預加載插件Preload
 * 
 * @param array imgs  預加載的圖片地址數組列表
 * @param Object options  配置參數
 */

(function ($) {
    function Preload(imgs, options) {
        this.imgs = (typeof imgs === 'string') ? [imgs] : imgs;
        this.options = {
            order: false, //默認值false,代表無序加載
            minTimer: 0, //完成加載的最少時間,單位ms,默認爲0,一般展示類型的loading動畫會需要設置
            each: null, //單張圖片加載完執行的方法,一般是修改進度狀態
            end: null //所有圖片加載完執行的方法,一般是隱藏loading頁
        };
        this.timer = Date.now();
        this.init(options);
    };
    //插件初始化
    Preload.prototype.init = function (options) {
        //配置參數合併
        this.options = $.extend(this.options, options);
        if (this.options.order) {
            this.ordered(); //有序加載
        } else {
            this.unordered(); //無序加載
        }
    };
    // 有序加載
    Preload.prototype.ordered = function () {
        var that = this,
            imgs = this.imgs,
            len = imgs.length,
            count = 0,
            options = this.options;
        load();

        function load() {
            var img = new Image();
            $(img).on('load error', function () {
                options.each && options.each(count);
                if (count >= len-1) {
                    //所有圖片加載完畢,檢查是否滿足最小loading時間
                    var timerCount = Date.now() - that.timer;
                    if (timerCount < options.minTimer) {
                        var timeout = options.minTimer - timerCount;
                        setTimeout(function () {
                            options.end && options.end();
                        }, timeout);
                    }else{
                        options.end && options.end();
                    }
                } else {
                    load();
                }
                count++

            });
            // onload函數要寫在改變src前,這樣確保了onload函數一定會被調用

            img.src = imgs[count];
        }
    };
    // 無序加載
    Preload.prototype.unordered = function () {
        var that = this,
            imgs = this.imgs,
            len = imgs.length,
            count = 0,
            options = this.options;
        for (var i = 0; i < len; i++) {
            var img = new Image();
            $(img).on('load error', function () {
                options.each && options.each(count);
                if (count >= len-1) {
                    //所有圖片加載完畢,檢查是否滿足最小loading時間
                    var timerCount = Date.now() - that.timer;
                    if (timerCount < options.minTimer) {
                        var timeout = options.minTimer - timerCount;
                        setTimeout(function () {
                            options.end && options.end();
                        }, timeout);
                    }else{
                        options.end && options.end();
                    }
                }
                count++;
            })
            img.src = imgs[i];
        }
    };
    //擴展到jQuery對象上
    $.extend($,{
        preload: function (imgs, options) {
            new Preload(imgs, options);
        }
    });
})(jQuery);

有序加載和無序加載

無序加載:前面我們說了,瀏覽器請求是異步的,所以我們的資源請求有可能是同時在加載過程中。
我用chrome瀏覽器的開發者工具,查看了無序加載時,圖片的加載狀態如下:
這裏寫圖片描述
可以看到,會出現同時請求多張圖片的情況,也就是說當我們希望所有的網速集中在一張圖片上時,這個方案是不行的,於是就有了有序加載。

有序加載:前面我們是通過循環來實現無序加載的,我們要先實現圖片資源有序加載,就需要在當前圖片加載完成時纔去加載下一張圖片,這裏我們用函數回調實現,雖然整個資源的加載時間變長了,但是卻也保證了圖片是有序出現的,在上面說的漫畫網站的場景下非常適用。
我用chrome瀏覽器的開發者工具,查看了無序加載時,圖片的加載狀態如下:
這裏寫圖片描述

說明文檔

Application.js

//= require jquery
...
//= require preload

Configuration

Name Type Default Description
imgs Array [] 預加載的圖片地址列表
options Object {} 配置參數對象

Options

Name Type Default Description
order Boolean false 默認值false,代表無序加載
minTimer Number 0 完成加載的最少時間,單位ms,默認爲0,一般展示類型的loading動畫會需要設置
each Function null 單張圖片加載完執行的方法,一般會修改進度狀態
end Function null 所有圖片加載完執行的方法,一般會隱藏loading層

Examples

<style>
    html,
    body {
        width: 100%;
    }
    .loading {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        text-align: center;
        font-size: 30px;
        background-color: #000;
    }
</style>

<div class="box">
    <img src="img/brief_1.jpg" alt="picture" id="img" style="width: 720px;height:400px;">
</div>
<div class="loading">
    <p class="progress"></p>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="js/preload.js"></script>

<script>
var imgs = [
    'img/brief_1.jpg',
    'img/brief_2.jpg',
    'img/brief_3.jpg',
    'img/brief_4.jpg',
    'img/brief_5.jpg',
    'img/brief_6.jpg',
    'img/brief_7.jpg',
    'img/brief_8.jpg',
    'img/brief_9.jpg',
    'img/brief_10.jpg',
    'img/brief_11.jpg'
];
var index = 0,
    len = imgs.length,
    progress = $('.progress');

//圖片預加載
$.preload(imgs, {
    each: function (count) {
        progress.html(Math.round((count+1) / len * 100) + '%');
    },
    end: function () {
        $('.loading').hide();
    }
});
</script>

總結

上面我們分析和實現了一個看起來非常簡單,但是也包含了不少內容的圖片預加載插件,從業務場景、方案分析、代碼實現、說明文檔、測試使用、總結輸出這一系列的流程,我知道了,實現一個插件可簡單,可複雜,可輕量,可豐富,不管怎樣,任何的實現都是基於滿足自己和團隊的使用,合適的就是最好的。

最後,也希望能給大家提供一點幫助,如有建議,歡迎留言!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章