Progressive Web App,簡稱 PWA,是提升 Web App 的體驗的一種新方法,能給用戶原生應用的體驗。
目錄:
1、PWA 基本介紹
PWA 全稱 Progressive Web App,即漸進式 WEB 應用
2、PWA 核心技術揭祕
- Web app manifest
- Service worker
- Promise / async / await
- Fetch api
- Cache storage
- 常見的緩存策略
- notification
- test
3、PWA 項目實戰
內容:
1.PWA 基本介紹
- PWA:漸進式 Web 應用,MDN 地址:https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
- Progressive Web App,簡稱 PWA,是提升 Web App 的體驗的一種新方法,能給用戶原生應用的體驗。
- PWA 運用現代的 Web APP 以及傳統的漸進式增強策略來創建跨平臺 Web 應用程序。
- PWA 能做到原生應用的體驗不是特指某一項技術,而是經過應用一些新技術進行改造
- 只要你擁有一個 Web App,那麼 PWA 的旅程就開始了
- 現在 Vue 和 React 的腳手架中都已經集成了 PWA 功能
2.PWA 的優勢
- 漸進式
適用於所有瀏覽器,因爲它是以漸進式增強作爲宗旨開發的。 - 流暢
能夠藉助 Service Worker 在離線或者網絡較差的情況下進行正常訪問。 - 可安裝
用戶可以添加常用的 webApp 到桌面,免去去應用商店下載的麻煩 - 原生體驗
可以和 app 一樣,擁有首屏加載動畫,可以隱藏地址欄等沉浸式體驗。 - 黏性
通過推送離線通知等,可以讓用戶迴流。
3.PWA 核心技術揭祕
1.Web App manifest:應用程序清單
-
基本介紹
Web App manifest 可以讓網站安全安裝到設備的主屏幕,而不需要用戶
通過應用商店下載。
Web App manifest:在一個 JSON 文本文件中提供有關應用程序的信息
(如名稱、作者、圖標和描述)
傳統的 Web App 入口:1、網址 2、書籤、收藏夾 3、直接搜索Web App manifest:
- 可以添加到桌面,有唯一的圖標和名單
- 有啓動時界面,避免生硬的過渡
- 隱藏瀏覽器相關的 UI,比如地址欄等
-
使用步驟
- 在項目根目錄下創建一個 manifest.json 文件。
- 在 index.html 中引入 manifest.json
- 在 manifest.json 文件中提供常見的配置。
- 需要在 https 協議或者http://localhost 下訪問項目
-
常見配置:
- name:用於指定應用的名稱,用戶安裝橫幅提示的名稱,和啓動動畫裏的文字。
- short_name:應用的端名稱,用於主屏幕顯示
- start_url:指定用戶從設備啓動應用程序時加載的 URL,可以是絕對路徑和相對路徑
- icons:用於指定可在各種環境中用做應用程序圖標的圖像對象數組,144*144
- background_color:用戶指定啓動動畫的背景色
- theme_color:用於指定應用程序的主題顏色
- display:用於指定 app 的顯示模式
- fullscreen:全屏模式,所有可用的顯示區域都被使用,並且不現實狀態欄
- standalone 讓這個應用看起來像一個獨立的應用程序,包括具有不同的窗口,在應用程序啓動器中擁有自己的圖標等。
- minimal-ui 該應用程序將看起來像一個獨立的應用程序,但會有瀏覽器地址欄。
2. Service Worker
-
基本介紹
-
一個標準的 PWA 程序,必須包含 3 個部分
manifest.json
service worker
https 服務器或者http://localhost -
W3C 組織早在 2014 年 5 月就提出過 Service Worker 這樣一個 html5 API,主要用來做持久的離線緩存。
-
前端有很多性能優化的手段:CDN、CSS Sprite、文件的合併壓縮、異步加載、資源緩存等等,這
些手段都是用來做性能優化的,但是如果斷網了,會發生什麼? -
service worker 允許 web 應用在網絡環境比較差或者是離線環境下依然可以使用
-
service worker 可以極大的提升 web app 的用戶體驗
-
service worker 是一個獨立的 worker 線程,獨立於當前網頁鏈接,是一種特殊的 web worker
-
Web Worker 是臨時的,每次做的事情的結果還不能被持久存下來,如果下次有同樣的複雜操作,還得
費時間的重來一遍 -
一旦被 install,就永遠存在,除非手動 unregister
-
用到的時候可以直接喚醒,不用的時候自動休眠
-
可編程攔截代理請求和返回,緩存文件,緩存的文件可以被網頁進程取到(包括網絡離線狀態)
-
離線內容開發者可控
-
必須在 Https 環境下才能工作
-
異步實現,內部大都是通過 Promise 實現
-
-
使用步驟
- 在 window.onload 中註冊 service worker,防止與其他資源競爭
- 在 navigator 對象中內置了 serviceWorker 屬性
- serviceWorker 在老版本的瀏覽器中不支持,需要進行瀏覽器兼容 if(‘serviceWordker’ in navigator){}
- 註冊 service worker navigator.serviceWorker.register(‘./sw.js’),返回一個 promise 對象
3. Promise
-
基本使用
-
Promise 是異步編程的一種解決方案,比傳統的解決方案—回調函數和事件—更合理更強大
-
Promise 可以鏈式的進行異步請求,解決了回調地獄的問題
-
Promise 常用的靜態方法
1. Promise.resolve()返回一個解析過帶着給定值的 Promise 對象,如果返回值 2. 是一個 Promise 對象,則直接返回這個 Promise 對象 3. Promise.reject()靜態函數 Promise.reject 返回一個被拒絕的 Promise 對象 4. Promise.all()返回一個 Promise 實例,等所有 promise 對象都成功了,纔會成功 5. Promise.race()競速,只要有一個 promise 對象成功或者失敗了,結果就是成功或者失敗
-
4. async/await
- ES2017 標準引入 async 函數,使得異步操作變得更加方便
- async 用於修飾一個函數,async function fn(){},await 函數會返回一個 promise 對象
- await 只能出現在 async 函數中,await 後面跟一個 promise 對象,用於獲取 promise 對象成功的結果,
如果不是 promise 對象,直接返回值 - await 後面的 promise 如果沒有成功,那麼會拋出異常,需要使用 try.catch 語法
5. fetch api
- Fetch API 提供了一個 Javascript 接口,用於訪問和操縱 Http 管道的部分,例如請求和響應
- 在 service worker,如果想要發送請求,無法使用 XMLHttpRequest,必須使用 fetch api
- Fetch.api 是基於 promise 實現的
- request 是一個二進制數據流,需要調用 json()方法可以轉換 json
- config 常見參數
- body:用於設置請求體
- headers:用於設置請求頭
- method:用於設置請求方式
6. cache storage
- cacheStorage 接口表示 Cache 對象的存儲,配合 service worker 來實現資源的緩存
- caches api 類似數據庫的操作
- caches.open(cacheName).then(function(catch){}):用於打開緩存,返回一個匹配 cacheName 的 cache 對象的 promise,類似於鏈接數據庫
- caches.keys()返回一個 promise 對象,包括所有的緩存 key
- caches.delete(key)根據 key 刪除對應的緩存(數據庫)
- cache 對象常用方法(單挑數據的操作)
- cache 接口爲緩存的 Request/response 提供存儲機制
- cache.put(req,res)把請求當成可以,並且把對應的響應存儲起來
- cache.add(url)根據 url 發起請求,並且把響應結果存儲起來
- cache.addAll(urls)抓去一個 url 數組,並且把結果都存儲起來
- cache.match(req)獲取 req 對應的 response
7. 常見的緩存策略
Cache only
Network only
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ThkrMxuj-1579502717128)(https://m.360buyimg.com/img/jfs/t1/94162/24/7057/446668/5df89f4eE4447cca5/dfbb6edcaa343ffd.pngg)]
Cache,falling back to Network
Network,falling back to Cache
Cache & Network race
8. notification api
- Notification API 的通知接口用於向用戶配置和顯示桌面通知
- Notification.permission 可以獲取當前用戶的授權情況
- default:默認的,未授權
- Denied:拒絕的,如果拒絕了,無法再次請求授權,也無法彈窗提醒
- Granted:授權的,可以彈窗提醒
- 通過 Notification.requestPermission()可以請求用戶的授權
- 通過 new Notification(‘title’,{body:’’,icon:’’})可以顯示通知
- 在授權通過的情況下,可以在 service worker 中顯示通知 self.registration.showNotification(‘您好’,{body:’msg’})
3.PWA 項目實戰
代碼展示:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="manifest" href="./manifest.json" />
<title>PWA</title>
<script>
window.onload = function() {
//serviceWorker
// if ("serviceWorker" in navigator) {
// navigator.serviceWorker
// .register("./sw.js")
// .then(registration => {
// console.log("registration", registration);
// })
// .catch(err => {
// console.log(err);
// });
// }
//添加通知功能
if (Notification.permission === "default") {
Notification.requestPermission().then(result => {
console.log(result);
});
}
if (!window.navigator.onLine) {
new Notification("提示", { body: "當前沒有網絡" });
}
if (window.navigator.onLine) {
new Notification("提示", { body: "當前有網絡" });
}
};
</script>
</head>
<body>
Hello PWA
</body>
</html>
mainfest.json
{
"name": "PWA例子PWA例子",
"short_name": "PWA",
"start_url": ".",
"display": "minimal-ui",
"background_color": "#f8f8f8",
"theme_color": "#ff0000",
"description": "PWA例子",
"icons": [
{
"src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
"sizes": "168x168",
"type": "image/png"
},
{
"src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
sw.js
// //緩存內容
// self.addEventListener('install', event => {
// console.log('install', event);
// //跳過等待,直接進入activate
// //self.skipWaiting()等待結果,才進入activate
// event.waitUntil(self.skipWaiting());
// });
// //主要清除舊的緩存
// self.addEventListener('activate', event => {
// //跳過等待,直接進入active
// console.log('activate', event);
// //表示service worker激活後,立即獲取控制權
// event.waitUntil(self.clients.claim());
// });
// //fetch事件會在請求發送的時候觸發
// self.addEventListener('fetch', event => {
// //跳過等待,直接進入active
// self.skipWaiting('fetch', event);
// });
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
const CACHE_NAME = "cache";
//緩存內容
self.addEventListener("install", async event => {
// //開啓一個cache
// const cache = await caches.open(CACHE_NAME);
// //cache對象就可以存儲的資源
// //等待cache把所有資源存儲起來
// cache.addAll(['./index.html', './manifest.json', '/']);
// await self.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(["/", "/manifest.json", "/index.html"]);
})
);
});
//主要清除舊的緩存
self.addEventListener("activate", async () => {
const keys = await caches.keys();
keys.forEach(key => {
if (key !== CACHE_NAME) {
caches.delete(key);
}
});
await self.clients.claim();
});
//fetch事件會在請求發送的時候觸發
//判斷資源是否能夠請求成功,如果請求成功,就響應成功的結果,如果斷網,就讀取caches緩存
self.addEventListener("fetch", event => {
const req = event.request;
const url = new URL(req.url);
// if(url.origin !== self.origin){
// return
// }
event.respondWith(cacheFirst(req));
// if(req.url.includes('./api')){
// event.respondWith(networkFirst(req))
// }else{
// event.respondWith(cacheFirst(req))
// }
});
//緩存優先
async function cacheFirst(req) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(req);
//如果從緩存中得到
if (cached) {
return cached;
} else {
const fresh = await fetch(req);
return fresh;
}
}
//網絡請求優先,如果我們獲取到了數據,應該往緩存中存一份
async function networkFirst(req) {
const cache = await caches.open(CACHE_NAME);
try {
const fresh = await fetch(req);
cache.put(req, fresh.clone());
return fresh;
} catch (e) {
const cached = await cache.match(req);
return cached;
}
}