本文首發於公衆號:符合預期的CoyPanjavascript
作過運營活動需求的同窗都知道,通常一個運營活動中會用到不少的圖片資源。用戶訪問首頁時,都會看到一個loading態,表示頁面正在加載所需的全部圖片資源。像下面這樣:java
手動加載一個圖片的代碼也很簡單:web
var img = new Image();
img.onload = function(){ ... }
img.src = '圖片地址';
複製代碼
之因此要提早加載全部的圖片,是爲了在後續的頁面中使用圖片時,不會由於須要加載圖片而產生耗時,致使體驗問題。本文所要討論的場景就是:怎麼樣作到在首頁加載圖片後,直接在後面的業務邏輯中直接使用提早加載好的圖片呢?答案就是:把圖片存下來。編程
我能想到的這種場景下的緩存圖片方法有兩種:canvas
作業務須要不斷的總結,思考。還能用什麼方法來實現圖片的緩存呢 ? 我嘗試了一下Service Worker,本文將介紹一下Service Worker在這種業務場景下的應用。瀏覽器
本文只是輕輕嘗試了一下Service Worker,並未在線上項目中應用。緩存
Service Worker是PWA的重要組成部分,其包含安裝、激活、等待、銷燬等四個生命週期。主要有如下的特性:服務器
在本文所描述的業務場景中,主要是應用service worker的攔截代理請求和返回的功能。網絡
關於service worker的基礎,谷歌開發者網站上有詳細的介紹,這裏就不贅述了。異步
地址在這裏:https://developers.google.com/web/fundamentals/primers/service-workers/?hl=zh-cn
須要注意的是,service worker必定要謹慎使用,由於它過重要了,一旦註冊,站點的全部請求都會被控制。
結合文章開頭所描述的場景,咱們先來寫一些必要的業務函數。
// 加載一個圖片
function loadImage(imgUrl) {
return new Promise((resolve, reject)=>{
const img = new Image();
img.onload = function() {
resolve();
};
img.src = imgUrl;
});
}
// 加載一堆圖片
function loadImageList(imgList) {
return Promise.all(imgList.map(function (imgUrl) {
return loadImage(imgUrl);
}));
}
複製代碼
下面是service worker的代碼:
self.addEventListener('install', function (event) {
console.log('install');
});
self.addEventListener('fetch', function (evt) {
evt.respondWith(
caches.match(evt.request).then(function(response) {
if (response) {
return response;
}
const request = evt.request.clone();
return fetch(request).then(function (response) {
if (!response || response.status !== 200 || !response.headers.get('Content-type').match(/image/)) {
return response;
}
const responseClone = response.clone(); // 流數據須要克隆一份。注意事項②
caches.open('test-cache').then(function (cache) {
cache.put(evt.request, responseClone);
});
return response;
});
})
)
});
self.addEventListener('activate', function () {
console.log('activate');
clients.claim(); // 首次activate後,就控制頁面。注意事項①
});
複製代碼
註冊完service worker後,咱們就劫持了頁面的全部請求。每一次請求通過service worker時,都會判斷剛請求是否已有緩存,若是有緩存,就直接返回結果。沒有緩存時,纔會向服務器發起請求,而且將圖片請求的結果緩存起來。
在業務代碼中,咱們註冊並使用這個service worker的代碼以下:
// 須要加載的圖片列表
const imgArr = ['http://xxx.jpg', '...'];
// 註冊service worker
function registerServiceWorker() {
if ('serviceWorker' in navigator) {
return navigator.serviceWorker.register('http://localhost:8080/service.js');
} else {
// 沒有service的處理邏輯省略
}
}
registerServiceWorker().then(registration => { // 注意事項③
let serviceWorker;
if (registration.installing) {
console.log('registration.installing');
serviceWorker = registration.installing;
} else if (registration.waiting) {
console.log('registration.waiting');
serviceWorker = registration.waiting;
} else if (registration.active) {
console.log('registration.active');
serviceWorker = registration.active;
loadImageList(imgArr);
}
if (serviceWorker) {
serviceWorker.addEventListener('statechange', function (e) {
if(e.target.state === 'activated') {
// 首次註冊時
console.log('首次註冊sw時,sw激活');
loadImageList(imgArr);
}
});
}
}).catch(e => {
console.log(e);
});
複製代碼
注意事項:
正常狀況下,service worker剛註冊時,是不會控制頁面的,即沒法攔截到頁面的請求。須要用戶刷新頁面,再次訪問時,service worker纔會攔截頁面請求。這與咱們的需求場景不符合。咱們的需求是:用戶首次訪問請求圖片資源時,就須要對返回的圖片進行緩存。因此,須要在service worker進入activate狀態後,經過clients.claim()來得到頁面的控制權。不過,這種方式並不被提倡。
service worker攔截到請求後,咱們須要拷貝返回的數據流,才能存入緩存。
在業務代碼中,咱們每次都須要調用navigator.serviceWorker.register來拿到一個service worker。瀏覽器會判斷當前service worker的狀態,返回對應的對象。咱們須要保證在service worker準備無誤後,再發起圖片的請求。因爲server worker的自身邏輯須要必定的時間,因此咱們發起圖片請求的時間會被延後。
以我作的運營活動項目爲例,使用service worker以前,網絡請求是這樣的:
使用service-worker以後,網絡請求是這樣的:
能夠看到,咱們成功使用service worker劫持了頁面的請求,而且將圖片緩存到了瀏覽器的cache storage中。咱們來看一下瀏覽器的緩存。這裏的緩存都是http response。
另外這裏多說一句,可使用下面的代碼,來查看當前網站可使用的瀏覽器本地存儲空間
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(({usage, quota}) => {
console.log(`Using ${usage} out of ${quota} bytes.`);
});
}
複製代碼
在本文提到的場景中,咱們在用戶首次訪問頁面時,先註冊了service worker,而且使service worker當即控制頁面,而後再開始請求圖片。這種作法延後了圖片請求的發起時間,而且從上面的圖中能夠看到,經過service worker加載圖片的耗時比正常直接請求圖片耗時略長。這些因素致使首屏時間被延後了。另外,做爲運營活動頁,同一個用戶也不會在幾天內屢次訪問,所以service worker的【繞過網絡,當即響應請求】的特性並不能很好地發揮出來。所以,在本文的場景中,使用service worker來作緩存並非最佳實踐。
關於service worker作緩存的最佳實踐以及使用場景,能夠查看這篇文章:
https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading?hl=zh-cn
service worker最適合的場景仍是資源離線化,用戶二次進入頁面時能夠達到資源秒加載,不會受網絡情況的影響。
本文從業務的角度出發,輕度探索了service worker在文章開頭給出的業務場景中的應用。後續會考慮在合適的業務場景中進行應用。