- 需求及分析
- img 标签访问重定向 url 的过程
- 占位图
- 带前端缓存的 img api
需求及分析
项目是 web App. 需兼容 ios, android.
- img 占位图显示
- url 被重定向的图片前端缓存
知乎有人提相似的问题: https://www.zhihu.com/questio…, 但是没有好的答案.
占位图自然是为了体验.
前端缓存图片, 也没啥稀奇,大部分浏览器自己都做好的.
但是考虑到 img url 是来自服务端 redirect 的 302 响应呢?
服务端之所以这么设计,是考虑到图片资源可能被其他 cdn 分发,总之来自不同的存储, 但是考虑兼容这些存储,需要统一 url, 就是下面的 origin url 的形式
origin url: xxx/files/5:
redirect to local: /storage/adsaf2qe12.jpg;
or redirect to cdn: /xx.cdn.com/adsaf2qe12.jpg;
img 标签访问重定向 url 的过程
1. 初次访问 /files/22
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. 返回 302
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
注意:
response body 是 1 个页面,就是图片的展示,img 的 src 实现里应该只关心 302 和 Location 的 url.
3. 访问真实地址 /storage/2019/07/26/0650/xxx.jpeg
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://54.223.41.252:8000/ff/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,
}
带前端缓存的 img api
前端缓存需要了解下 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);
})
}
});
},
}