本文首發於公衆號:符合預期的CoyPan
作過運營活動需求的同窗都知道,通常一個運營活動中會用到不少的圖片資源。用戶訪問首頁時,都會看到一個loading態,表示頁面正在加載所需的全部圖片資源。像下面這樣:javascript
手動加載一個圖片的代碼也很簡單:java
var img = new Image(); img.onload = function(){ ... } img.src = '圖片地址';
之因此要提早加載全部的圖片,是爲了在後續的頁面中使用圖片時,不會由於須要加載圖片而產生耗時,致使體驗問題。本文所要討論的場景就是:怎麼樣作到在首頁加載圖片後,直接在後面的業務邏輯中直接使用提早加載好的圖片呢?答案就是:把圖片存下來。web
我能想到的這種場景下的緩存圖片方法有兩種:編程
作業務須要不斷的總結,思考。還能用什麼方法來實現圖片的緩存呢 ? 我嘗試了一下Service Worker,本文將介紹一下Service Worker在這種業務場景下的應用。canvas
本文只是輕輕嘗試了一下Service Worker,並未在線上項目中應用。
Service Worker是PWA的重要組成部分,其包含安裝、激活、等待、銷燬等四個生命週期。主要有如下的特性:瀏覽器
在本文所描述的業務場景中,主要是應用service worker的攔截代理請求和返回的功能。緩存
關於service worker的基礎,谷歌開發者網站上有詳細的介紹,這裏就不贅述了。服務器
地址在這裏:https://developers.google.com...網絡
須要注意的是,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劫持了頁面的請求,而且將圖片緩存到了瀏覽器的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...
service worker最適合的場景仍是資源離線化,用戶二次進入頁面時能夠達到資源秒加載,不會受網絡情況的影響。
本文從業務的角度出發,輕度探索了service worker在文章開頭給出的業務場景中的應用。後續會考慮在合適的業務場景中進行應用。