前段時間遇到了一個比較有意思的GIF圖相關的問題,下面我先簡單說一下遇到的問題,而後再說一下是怎麼作資源處理的。前端
頁面中有不少個相同的GIF,能夠手動觸發播放其中一個。(這裏播放的方案就不說了,直接說一下播放過程當中遇到的問題)算法
簡單來講,預期是這樣的:
數組
實際是這樣的:
promise
問題總結:觸發其中一個播放的時候,其餘相同的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... } );