項目是web App.需兼容ios, android.html
知乎有人提類似的問題: https://www.zhihu.com/questio...,可是沒有好的答案.前端
佔位圖天然是爲了體驗.vue
前端緩存圖片,也沒啥稀奇,大部分瀏覽器本身都作好的.android
可是考慮到img url是來自服務端redirect的302響應呢?ios
服務端之因此這麼設計,是考慮到圖片資源可能被其餘cdn分發,總之來自不一樣的存儲,可是考慮兼容這些存儲,須要統一url,就是下面的origin url的形式nginx
origin url: xxx/files/5: redirect to local: /storage/adsaf2qe12.jpg; or redirect to cdn: /xx.cdn.com/adsaf2qe12.jpg;
1.初次訪問 /files/22web
GET /files/705%22 HTTP/1.1 Host: xxx:8000 Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5 Connection: keep-alive User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Faraday Futu re Accept-Language: zh-cn Accept-Encoding: gzip, deflate
2.返回 302chrome
HTTP/1.1 302 Found Server: nginx Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive X-Powered-By: PHP/7.2.19 Cache-Control: no-cache Date: Mon, 12 Aug 2019 23:06:06 GMT Location: http://xxxx:8000/storage/2019/07/26/0650/eWNojunNteHaADwujoHeBPSucm1Ac1DlTENiroXL.jpeg Expires: Mon, 12 Aug 2019 23:06:05 GMT X-matching: api_begin X-matching: api_end
注意:axios
response body是1個頁面,就是圖片的展現,img的src實現裏應該只關心302和Location的url.
3.訪問真實地址/storage/2019/07/26/0650/xxx.jpegapi
GET /storage/2019/07/26/0650/eWNojunNteHaADwujoHeBPSucm1Ac1DlTENiroXL.jpeg HTTP/1.1 Host: 54.223.41.252:8000 Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5 Connection: keep-alive User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Faraday Future Accept-Language: zh-cn Referer: http://xx/news/79?lang=zh&follow=false Accept-Encoding: gzip, deflate
4.返回 200
HTTP/1.1 200 OK Server: nginx Date: Mon, 12 Aug 2019 23:06:06 GMT Content-Type: image/jpeg Content-Length: 331683 Last-Modified: Fri, 26 Jul 2019 06:50:25 GMT Connection: keep-alive ETag: "5d3aa2b1-50fa3" Expires: Wed, 11 Sep 2019 23:06:06 GMT Cache-Control: max-age=2592000 X-matching: img_begin X-matching: img_end Accept-Ranges: bytes
靜態資源由nginx返回,帶了cache控制的header,正常狀況就應該按http緩存的規則來了.
即使是重定位,對chrome這樣的強緩存策略的瀏覽器也沒撒問題.
問題就在於,咱們的項目是web app. 對於ios的WKWebview的img.src引起的訪問,在重定向的前提下是無視緩存的
必須本身來實現前端緩存機制.
首先,切換url來實現佔位圖的效果:
<img v-for="(image,index) in saved_images" :src="image.url" :class="layout(saved_images.length)" @click="clickImage(index)" style="object-fit: cover" @load="loadedImage(index)" >
vue的核心思路:
watch: { //watch images數據,裏面包含url 'images': { handler(newVal) { // console.log(this.images); let that = this; //not video if( !this.hasVideo) { //newVal is a array, save url; newVal.forEach(val => { // 緩存圖片的api,核心在這裏 that.apiImg.fetchImg(val.url,response=>{ // base64編碼 //response來自cache 或者第1次訪問的結果 val.url = response; }); that.image_urls.push(val.url); //place holder val.url = resources.placeHolder; }); } this.saved_images = newVal; }, immediate: true, // deep: true, }
前端緩存須要瞭解下 cookie->localstorage-->indexDB
.
我最終選擇了localforage庫,結合axios-cache-adapter
來作請求的緩存.
代碼核心功能:
1. 配置localforage,使用INDEXEDDB; 2. 配置axios-cache-adapter 3. 圖片轉儲base64 4. 調用localforage的setItem/getItem;
最終實現以下:
import {axios} from '../utils/http'; import localforage from 'localforage' import memoryDriver from 'localforage-memoryStorageDriver' import {setup} from 'axios-cache-adapter' import {setupCache} from 'axios-cache-adapter' // Register the custom `memoryDriver` to `localforage` localforage.defineDriver(memoryDriver) // Create `localforage` instance const forageStore = localforage.createInstance({ // List of drivers used driver: [ localforage.INDEXEDDB, localforage.LOCALSTORAGE, memoryDriver._driver ], size: 10000000, // Prefix all storage keys to prevent conflicts name: 'img-cache' }); const cache = setupCache({ //過時時間 maxAge: 15 * 60 * 1000, store: forageStore }); const api = axios.create( { adapter: cache.adapter } ); export default { fetchImg: async function (url, fn) { const config = { responseType: 'arraybuffer' }; //get from store await forageStore.getItem(url, (err, value) => { if (!err && value) { console.log(forageStore); console.log('already cached'); fn(value); } else { console.log('1st time') api.get(url, config,).then(async function (response) { //base64 format let bas64Url = "data:" + response.headers['content-type'] + ";base64," + btoa( new Uint8Array(response.data).reduce((data, byte) => data + String.fromCharCode(byte), '') ); //store await forageStore.setItem(url, bas64Url, () => { console.log('set item ok'); return fn(url); }); }).catch(function (reason) { console.log(reason); }) } }); }, }