前端圖片資源優化實踐

寫在前面

前段時間遇到了一個比較有意思的GIF圖相關的問題,下面我先簡單說一下遇到的問題,而後再說一下是怎麼作資源處理的。前端

問題

頁面中有不少個相同的GIF,能夠手動觸發播放其中一個。(這裏播放的方案就不說了,直接說一下播放過程當中遇到的問題)算法

簡單來講,預期是這樣的:
image數組

實際是這樣的
imagepromise

問題總結:觸發其中一個播放的時候,其餘相同的GIF動畫也會被一塊兒被影響。

分析

先看一下這些GIF圖的資源結構:

圖片資源結構

能夠看到,這些GIF用的都是相同的資源路徑。因此在第一個資源回來的時候,會在瀏覽器造成一個圖片緩存,後續其餘GIF資源用的都是這個緩存。因此觸發其中一個GIF圖播放的時候,至關於播放了存在緩存中的GIF圖。這個時候,其餘的引用了這個緩存資源的地方也就造成了一塊兒播放的效果。瀏覽器

分析總結:這些gif圖使用了相同的瀏覽器緩存。

初級方案

基礎實現

方案核心:分化圖片源
這個方案是將源文件複製成N多份,而後每一個前端的引用映射一個CDN源文件,從圖片源上將這些相同的圖片給獨立開。
舉個栗子,原圖的名字是「demo.gif」,而後複製「demo.gif」,生成「demo001.gif」、「demo002.gif」、「demo003.gif」... 。而後前端使用圖片的時候,第一張圖用「demo001.gif」、第二張用「demo002.gif」、第三張用「demo003.gif」... 。依此類推...緩存

資源結構以下:

方案一的資源結構

預加載

const baseUrl = 'http://www.abc.com/'
// 這裏包層數組是爲了拓展其餘圖片 
const imgArr = [{len: 15, nameFrag: 'demo_00'}]
let imgData = [];
// imgData 最後變成一個 url 列表
// 如:['http://www.abc.com/demo_001.gif','http://www.abc.com/demo_001.gif' ... ]
imgArr.forEach((imgConfig) => {
    // new Array兼容性有問題 能夠考慮換成for循環
    const arr = new Array(imgConfig.len).fill('').map((item,index) => `${baseUrl}${imgConfig.nameFrag}${index+1}.gif`);
    imgData = imgData.concat(arr)
})

// 拿到一個promise 列表 ,最後用 promise.all 處理全部預加載
const promiseAll = imgData.map(function (item, index) {
    return new Promise(function (resolve, reject) {
      const img = new Image();
      img.onload = function () {
        img.onload = null;
        resolve(img);
      };
      img.error = function () {
        reject('圖片加載失敗');
      };
      img.src = item;
    });
 });
 
Promise.all(promiseAll).then(
    function () {
        do something...
    },
    function (err) {
        do something...
    }
);

缺點:

一、資源更新成本高動畫

舉個例子,你的網頁上原來有 15 張 「點贊.gif」 ,UI同窗忽然哪天來靈感了,她要把「點贊.gif」替換成「瘋狂點贊.gif」。url

那這個時候麻煩就來了,你要生成15張新的「瘋狂點贊.gif」,分別是「瘋狂點贊001.gif」、「瘋狂點贊002.gif」、「瘋狂點贊003.gif」... 。而後再把原來的15張「點贊.gif」替換調。而後還要前端的引用的圖片名一個個改掉。spa

15張可能還能接受,可是若是再來15張「 點踩.gif」,或者其餘更多相同gif呢?那麻煩就大了,這個時候就是經典的吐槽UI時間...

二、浪費存儲(cdn)資源code

這個很好理解,前端每一個引用的圖片源都是要存起來的嘛,那實際只須要存 1 張的圖片,這個方案須要存15張,至關於資源消耗是原來的N倍。

仍是缺點1相同的問題,簡單場景下沒有問題,可是場景越複雜,存儲資源的浪費越大。

升級方案

基礎實現

方案核心:分化瀏覽器緩存
前面說的方案一是生成多個圖片源,讓前端引用和圖片源一對一映射關係。那這個方案的核心就是生成多個瀏覽器緩存,讓前端引用和瀏覽器緩存造成一對一映射關係。
具體就是,在每一個圖片請求上加一個固定的參數編號,至關於給瀏覽器緩存打上一個編號。
好比:
原圖片路徑是:「http://www.abc.com/demo.gif
新圖片路徑就是:
http://www.abc.com/demo.gif?001
http://www.abc.com/demo.gif?002
http://www.abc.com/demo.gif?003
......

圖片資源結構以下:

方案二資源結構

這個方案額外須要作的一件事情就是,在實現的時候維護一個參數映射關係,維護的形式能夠有不少,這裏提供兩種簡單建議:

// 第一種
// 直接將參數編號列出來
// 每用到一個用到一個,則index++
cosnt base_url = 'http://www.baidu.com/'
const imgMap = {
    "demo.gif": {
        index: 0,
        list:['001','002','003'......]
    }
}
const url_001 = `${base_url}demo.gif?${imgMap['demo.gif'].list[imgMap['demo.gif'].index]}`
imgMap['demo.gif'].index++
const url_001 = `${base_url}demo.gif?${imgMap['demo.gif'].list[imgMap['demo.gif'].index]}`
imgMap['demo.gif'].index++
.....

// 第二種 
// 不用存list,根據index實時計算就行
// 須要保證 圖片引用的時候 和 預加載的時候 的算法一致就行
// 這裏個代碼就不列舉了

預加載

預加載和方案一的沒什麼大的區別,只是在生成url的時候,稍微改一下就行

const arr = new Array(imgConfig.len).fill('').map((item,index) => `${baseUrl}${imgConfig.nameFrag}.gif?${index+1}`);

所有代碼:

const baseUrl = 'http://www.abc.com/'
// 這裏包層數組是爲了拓展其餘圖片 
const imgArr = [{len: 15, nameFrag: 'demo'}]
let imgData = [];
// imgData 最後變成一個 url 列表
// 如:['http://www.abc.com/demo_001.gif','http://www.abc.com/demo_001.gif' ... ]
imgArr.forEach((imgConfig) => {
    // new Array兼容性有問題 能夠考慮換成for循環
    const arr = new Array(imgConfig.len).fill('').map((item,index) => `${baseUrl}${imgConfig.nameFrag}.gif?${index+1}`);
    imgData = imgData.concat(arr)
})

// 拿到一個promise 列表 ,最後用 promise.all 處理全部預加載
const promiseAll = imgData.map(function (item, index) {
    return new Promise(function (resolve, reject) {
      const img = new Image();
      img.onload = function () {
        img.onload = null;
        resolve(img);
      };
      img.error = function () {
        reject('圖片加載失敗');
      };
      img.src = item;
    });
 });
 
Promise.all(promiseAll).then(
    function () {
        do something...
    },
    function (err) {
        do something...
    }
);
相關文章
相關標籤/搜索