pwa
Progressive Web App, 簡稱 PWA,是提升 Web App 的體驗的一種新方法许赃,能給用戶原生應(yīng)用的體驗
PWA 能做到原生應(yīng)用的體驗不是靠特指某一項技術(shù)篡石,而是經(jīng)過應(yīng)用一些新技術(shù)進(jìn)行改進(jìn)扛拨,在安全矮烹、性能和體驗三個方面都有很大提升麻养,PWA 本質(zhì)上是 Web App褐啡,借助一些新技術(shù)也具備了 Native App 的一些特性,兼具 Web App 和 Native App 的優(yōu)點鳖昌。
PWA 的主要特點包括下面三點:
- 可靠 - 即使在不穩(wěn)定的網(wǎng)絡(luò)環(huán)境下备畦,也能瞬間加載并展現(xiàn)
- 體驗 - 快速響應(yīng),并且有平滑的動畫響應(yīng)用戶的操作
- 粘性 - 像設(shè)備上的原生應(yīng)用许昨,具有沉浸式的用戶體驗懂盐,用戶可以添加到桌面
來源: https://lavas.baidu.com/pwa/README
離線和緩存
Service Worker
W3C 組織早在 2014 年 5 月就提出過 Service Worker 這樣的一個 HTML5 API ,主要用來做持久的離線緩存
Service Worker 有以下功能和特性:
一個獨立的 worker 線程糕档,獨立于當(dāng)前網(wǎng)頁進(jìn)程莉恼,有自己獨立的 worker context。
一旦被 install,就永遠(yuǎn)存在俐银,除非被手動 unregister
用到的時候可以直接喚醒尿背,不用的時候自動睡眠
可編程攔截代理請求和返回,緩存文件捶惜,緩存的文件可以被網(wǎng)頁進(jìn)程取到(包括網(wǎng)絡(luò)離線狀態(tài))
離線內(nèi)容開發(fā)者可控
能向客戶端推送消息
不能直接操作 DOM
必須在 HTTPS 環(huán)境下才能工作
異步實現(xiàn)田藐,內(nèi)部大都是通過 Promise 實現(xiàn)
使用 前提條件
Service Worker 出于安全性和其實現(xiàn)原理,在使用的時候有一定的前提條件:
由于 Service Worker 要求 HTTPS 的環(huán)境吱七,我們通澄牖矗可以借助于 github page 進(jìn)行學(xué)習(xí)調(diào)試。當(dāng)然一般瀏覽器允許調(diào)試 Service Worker 的時候 host 為 localhost 或者 127.0.0.1 也是 ok 的陪捷。
Service Worker 的緩存機(jī)制是依賴 Cache API 實現(xiàn)的
依賴 HTML5 fetch API
依賴 Promise 實現(xiàn)
注冊
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', {scope: '/'})
.then(function (registration) {
// 注冊成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function (err) {
// 注冊失敗:(
console.log('ServiceWorker registration failed: ', err);
});
});
}
register 方法的 scope 參數(shù)是可選的,用于指定你想讓 Service Worker 控制的內(nèi)容的子目錄诺擅。
Service Worker 線程將接收 scope 指定網(wǎng)域目錄上所有事項的 fetch 事件市袖,如果我們的 Service Worker 的 javaScript 文件在 /a/b/sw.js, 不傳 scope 值的情況下, scope 的值就是 /a/b烁涌。
scope 的值的意義在于苍碟,如果 scope 的值為 /a/b, 那么 Service Worker 線程只能捕獲到 path 為 /a/b 開頭的( /a/b/page1, /a/b/page2撮执,...)頁面的 fetch 事件微峰。通過 scope 的意義我們也能看出 Service Worker 不是服務(wù)單個頁面的,所以在 Service Worker 的 js 邏輯中全局變量需要慎用
安裝
- Service Worker 注冊成功之后呢抒钱,我們的瀏覽器中已經(jīng)有了一個屬于你自己 web App 的 worker context蜓肆,此時,瀏覽器就會馬不停蹄的嘗試為你的站點里面的頁面安裝并激活它谋币,并且在這里可以把靜態(tài)資源的緩存給辦了
// 監(jiān)聽 service worker 的 install 事件
this.addEventListener('install', function (event) {
// 如果監(jiān)聽到了 service worker 已經(jīng)安裝成功的話仗扬,就會調(diào)用 event.waitUntil 回調(diào)函數(shù)
event.waitUntil(
// 安裝成功后操作 CacheStorage 緩存,使用之前需要先通過 caches.open() 打開對應(yīng)緩存空間蕾额。
caches.open('my-test-cache-v1').then(function (cache) {
// 通過 cache 緩存對象的 addAll 方法添加 precache 緩存
return cache.addAll([
'/',
'/index.html',
'/main.css',
'/main.js',
'/image.jpg'
]);
})
);
});
- ExtendableEvent.waitUntil() 方法——這會確保 Service Worker 不會在 waitUntil() 里面的代碼執(zhí)行完畢之前安裝完成
- 在 waitUntil() 內(nèi)早芭,我們使用了 caches.open() 方法來創(chuàng)建了一個叫做 v1 的新的緩存,將會是我們的站點資源緩存的第一個版本诅蝶。它返回了一個創(chuàng)建緩存的 promise退个,當(dāng)它 resolved 的時候,我們接著會調(diào)用在創(chuàng)建的緩存實例(Cache API)上的一個方法 addAll()调炬,這個方法的參數(shù)是一個由一組相對于 origin 的 URL 組成的數(shù)組语盈,這些 URL 就是你想緩存的資源的列表
自定義請求響應(yīng)
- 每次任何被 Service Worker 控制的資源被請求到時,都會觸發(fā) fetch 事件缰泡,這些資源包括了指定的 scope 內(nèi)的 html 文檔黎烈,和這些 html 文檔內(nèi)引用的其他任何資源(比如 index.html 發(fā)起了一個跨域的請求來嵌入一個圖片,這個也會通過 Service Worker),這下 Service Worker 代理服務(wù)器的形象開始慢慢露出來了照棋,而這個代理服務(wù)器的鉤子就是憑借 scope 和 fetch 事件兩大利器就能把站點的請求管理的井井有條
this.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
// 來來來资溃,代理可以搞一些代理的事情
// 如果 Service Worker 有自己的返回,就直接返回烈炭,減少一次 http 請求
if (response) {
return response;
}
// 如果 service worker 沒有返回溶锭,那就得直接請求真實遠(yuǎn)程服務(wù)
var request = event.request.clone(); // 把原始請求拷過來
return fetch(request).then(function (httpRes) {
// http請求的返回已被抓到,可以處置了符隙。
// 請求失敗了趴捅,直接返回失敗的結(jié)果就好了。霹疫。
if (!httpRes || httpRes.status !== 200) {
return httpRes;
}
// 請求成功的話拱绑,將請求緩存起來。
var responseClone = httpRes.clone();
caches.open('my-test-cache-v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return httpRes;
});
})
);
});
我們可以在 install 的時候進(jìn)行靜態(tài)資源緩存丽蝎,也可以通過 fetch 事件處理回調(diào)來代理頁面請求從而實現(xiàn)動態(tài)資源緩存
兩種方式可以比較一下
on install 的優(yōu)點是第二次訪問即可離線猎拨,缺點是需要將需要緩存的 URL 在編譯時插入到腳本中,增加代碼量和降低可維護(hù)性屠阻;
on fetch 的優(yōu)點是無需更改編譯過程红省,也不會產(chǎn)生額外的流量,缺點是需要多一次訪問才能離線可用国觉。(第二次請求頁面才緩存)
自動更新所有頁面
- install 事件中執(zhí)行 self.skipWaiting() 方法跳過 waiting 狀態(tài)吧恃,然后會直接進(jìn)入 activate 階段。接著在 activate 事件發(fā)生時麻诀,通過執(zhí)行 self.clients.claim() 方法痕寓,更新所有客戶端上的 Service Worker
// 安裝階段跳過等待,直接進(jìn)入 active
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function (event) {
event.waitUntil(
Promise.all([
// 更新客戶端
self.clients.claim(),
// 清理舊版本
caches.keys().then(function (cacheList) {
return Promise.all(
cacheList.map(function (cacheName) {
if (cacheName !== 'my-test-cache-v1') {
return caches.delete(cacheName);
}
})
);
})
])
);
});
手動更新 Service Worker
- 其實在頁面中蝇闭,也可以手動借助 Registration.update() 更新厂抽。
var version = '1.0.1';
navigator.serviceWorker.register('/sw.js').then(function (reg) {
if (localStorage.getItem('sw_version') !== version) {
reg.update().then(function () {
localStorage.setItem('sw_version', version)
});
}
});
如何工作
首先我們需要在頁面的 JavaScript 主線程中使用 serviceWorkerContainer.register() 來注冊 Service Worker ,在注冊的過程中丁眼,瀏覽器會在后臺啟動嘗試 Service Worker 的安裝步驟筷凤。
如果注冊成功,Service Worker 在 ServiceWorkerGlobalScope 環(huán)境中運行苞七; 這是一個特殊的 worker context藐守,與主腳本的運行線程相獨立,同時也沒有訪問 DOM 的能力蹂风。
后臺開始安裝步驟卢厂, 通常在安裝的過程中需要緩存一些靜態(tài)資源。如果所有的資源成功緩存則安裝成功惠啄,如果有任何靜態(tài)資源緩存失敗則安裝失敗慎恒,在這里失敗的不要緊任内,會自動繼續(xù)安裝直到安裝成功,如果安裝不成功無法進(jìn)行下一步 — 激活 Service Worker融柬。
開始激活 Service Worker死嗦,必須要在 Service Worker 安裝成功之后,才能開始激活步驟粒氧,當(dāng) Service Worker 安裝完成后越除,會接收到一個激活事件(activate event)。激活事件的處理函數(shù)中外盯,主要操作是清理舊版本的 Service Worker 腳本中使用資源摘盆。
激活成功后 Service Worker 可以控制頁面了,但是只針對在成功注冊了 Service Worker 后打開的頁面饱苟。也就是說孩擂,頁面打開時有沒有 Service Worker,決定了接下來頁面的生命周期內(nèi)受不受 Service Worker 控制箱熬。所以类垦,只有當(dāng)頁面刷新后,之前不受 Service Worker 控制的頁面才有可能被控制起來
生命周期 https://lavas.baidu.com/pwa/offline-and-cache-loading/service-worker/service-worker-lifecycle
添加到主屏幕 manifest.json
- 允許將站點添加至主屏幕坦弟,是 PWA 提供的一項重要功能。雖然目前部分瀏覽器已經(jīng)支持向主屏幕添加網(wǎng)頁快捷方式以方便用戶快速打開站點官地,但是 PWA 添加到主屏幕的不僅僅是一個網(wǎng)頁快捷方式酿傍,它將提供更多的功能,讓 PWA 具有更加原生的體驗驱入。
manifest.json
{
"short_name": "短名稱",
"name": "這是一個完整名稱",
"icons": [
{
"src": "icon.png",
"type": "image/png",
"sizes": "48x48"
}
],
"start_url": "index.html"
}
index.html
<link rel="manifest" href="path-to-manifest/manifest.json">
<!-- safari 添加到主屏幕 圖標(biāo) -->
<link rel="apple-touch-icon-precomposed" href="icon.jpg">
詳細(xì)介紹 manifest.json 字段
- name: {string} 應(yīng)用名稱赤炒,用于安裝橫幅、啟動畫面顯示
- short_name: {string} 應(yīng)用短名稱亏较,用于主屏幕顯示
- icons: {Array.
<ImageObject>
} 應(yīng)用圖標(biāo)列表(瀏覽器會根據(jù)有效圖標(biāo)的 sizes 字段進(jìn)行選擇莺褒。首先尋找與顯示密度相匹配并且尺寸調(diào)整到 48dp 屏幕密度的圖標(biāo);如果未找到任何圖標(biāo)雪情,則會查找與設(shè)備特性匹配度最高的圖標(biāo)遵岩;如果匹配到的圖標(biāo)路徑錯誤,將會顯示瀏覽器默認(rèn) icon巡通。)- src {string} 圖標(biāo) url
- type {string=} 圖標(biāo)的 mime 類型尘执,非必填項,該字段可讓瀏覽器快速忽略掉不支持的圖標(biāo)類型
- sizes {string} 圖標(biāo)尺寸宴凉,格式為 width x height誊锭,寬高數(shù)值以 css 的 px 為單位。如果需要填寫多個尺寸弥锄,則使用空格進(jìn)行間隔丧靡,如"48x48 96x96 128x128"
- start_url: {string=} 應(yīng)用啟動地址
- scope: {string} 作用域
- background_color: {Color} css 色值
- display : {string} 顯示類型 屬性去指定 PWA 從主屏幕點擊啟動后的顯示類型
- fullscreen 應(yīng)用的顯示界面將占滿整個屏幕
- standalone 瀏覽器相關(guān) UI(如導(dǎo)航欄蟆沫、工具欄等)將會被隱藏
- minimal-ui 顯示形式與 standalone 類似,瀏覽器相關(guān) UI 會最小化為一個按鈕
- browser 瀏覽器模式温治,與普通網(wǎng)頁在瀏覽器中打開的顯示一致
- orientation: string 應(yīng)用顯示方向
- theme_color: {Color} css 色值 可以指定 PWA 的主題顏色
引導(dǎo)用戶添加應(yīng)用至主屏幕
打開瀏覽器菜單饭庞,會看到添加到主屏幕的功能,用戶可以點擊該選項手動將 PWA 站點添加至主屏幕
彈出應(yīng)用安裝橫幅 條件
- 站點部署 manifest.json罐盔,該文件需配置如下屬性
- short_name (用于主屏幕顯示)
- name (用于安裝橫幅顯示)
- icons (其中必須包含一個 mime 類型為 image/png 的圖標(biāo)聲明)
- start_url (應(yīng)用啟動地址)
- display (必須為 standalone 或 fullscreen)
- 站點注冊 Service Worker但绕。
- 站點支持 HTTPS 訪問。
- 站點在同一瀏覽器中被訪問至少兩次惶看,兩次訪問間隔至少為 5 分鐘捏顺。
注意這些事件接口仍處于 Working Draft 階段,僅有部分瀏覽器支持纬黎。這也就意味著幅骄,即使支持彈出安裝橫幅的瀏覽器,也不一定支持應(yīng)用安裝橫幅事件本今。想要進(jìn)行完整的功能體驗拆座,建議使用 Chrome Beta for Android 瀏覽器進(jìn)行測試。
消息推送介紹
- 目前整體支持度并不高冠息,在手機(jī)端更是只有安卓 Chrome57 支持挪凑。
授權(quán)
window.addEventListener('load', () => {
if (!('serviceWorker' in navigator)) {
// Service Worker isn't supported on this browser, disable or hide UI.
return;
}
if (!('PushManager' in window)) {
// Push isn't supported on this browser, disable or hide UI.
return;
}
let promiseChain = new Promise((resolve, reject) => {
const permissionPromise = Notification.requestPermission(result => {
resolve(result);
});
if (permissionPromise) {
permissionPromise.then(resolve);
}
})
.then(result => {
if (result === 'granted') {
execute();
}
else {
console.log('no permission');
}
});
});
- 值得注意的是,當(dāng)用戶允許或者拒絕授權(quán)后逛艰,后續(xù)都不會重復(fù)詢問躏碳。 想要更改這個設(shè)置,在 Chrome 地址欄左側(cè)網(wǎng)站信息中如下
注冊 service worker
function registerServiceWorker() {
return navigator.serviceWorker.register('service-worker.js')
.then(registration => {
console.log('Service worker successfully registered.');
return registration;
})
.catch(err => {
console.error('Unable to register service worker.', err);
});
}
使用 showNotification 方法彈出通知
function execute() {
registerServiceWorker().then(registration => {
registration.showNotification('Hello World!');
});
}
showNotification 參數(shù)
- title - 必填 字符串類型 表示通知的標(biāo)題
- options - 選填 對象類型 集合眾多配置項散怖,可用項如下:
{
// 視覺相關(guān)
"body": "<String>",
"icon": "<URL String>",
"image": "<URL String>",
"badge": "<URL String>",
"vibrate": "<Array of Integers>",
"sound": "<URL String>",
"dir": "<String of 'auto' | 'ltr' | 'rtl'>",
// 行為相關(guān)
"tag": "<String>",
"data": "<Anything>",
"requireInteraction": "<boolean>",
"renotify": "<Boolean>",
"silent": "<Boolean>",
// 視覺行為均會影響
"actions": "<Array of Strings>",
// 定時發(fā)送時間戳
"timestamp": "<Long>"
}
- 通知關(guān)閉事件
self.addEventListener('notificationclose', event => {
let dismissedNotification = event.notification;
let promiseChain = notificationCloseAnalytics();
event.waitUntil(promiseChain);
});
- 通知事件的數(shù)據(jù)傳遞
index.html
registration.showNotification('Notification With Data', {
body: 'This notification has data attached to it that is printed to the console when it\'s clicked.',
data: {
time: (new Date()).toString(),
message: 'Hello World!'
}
});
sw.js
self.addEventListener('notificationclick', event => {
const notificationData = event.notification.data;
console.log('The data notification had the following parameters:');
Object.keys(notificationData).forEach(key => {
console.log(` ${key}: ${notificationData[key]}`);
});
})
- 打開頁面
let examplePage = '/demos/notification-examples/example-page.html';
let promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);
- 向頁面發(fā)送信息
sw.js
clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage('向頁面發(fā)送的信息')
})
})
index.html
navigator.serviceWorker.addEventListener('message', event => {
console.log('Received a message from service worker: ', event.data);
});
post Meassage
http://craig-russell.co.uk/2016/01/29/service-worker-messaging.html#.XGZK3VUzaUl
為什么 pwa 沒有發(fā)展起來
https://baijiahao.baidu.com/s?id=1612919514973793701&wfr=spider&for=pc
后面是完整代碼
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello PWA</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="main.css">
<link rel="manifest" href="manifest.json">
<!-- safari 添加到主屏幕 圖標(biāo) -->
<link rel="apple-touch-icon-precomposed" href="icon.jpg">
</head>
<body>
<h3>Hello PWA</h3>
<p>dsjflskdjfl</p>
</body>
<script>
// 檢測瀏覽器是否支持SW
/* if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('./sw.js')
.then(function(registration) {
// 注冊成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function(err) {
// 注冊失敗:(
console.log('ServiceWorker registration failed: ', err);
});
});
} */
</script>
<script>
window.addEventListener('load', () => {
if (!('serviceWorker' in navigator)) {
// Service Worker isn't supported on this browser, disable or hide UI.
return;
}
if (!('PushManager' in window)) {
// Push isn't supported on this browser, disable or hide UI.
return;
}
let promiseChain = new Promise((resolve, reject) => {
// 在訂閱之前先獲取用戶授權(quán)菇绵,使用Notification.requestPermission
const permissionPromise = Notification.requestPermission(result => {
resolve(result);
});
if (permissionPromise) {
permissionPromise.then(resolve);
}
})
.then(result => {
// 授權(quán)通過
if (result === 'granted') {
execute();
} else {
console.log('no permission');
}
});
// 注冊 service worker ,獲取注冊對象
function registerServiceWorker() {
return navigator.serviceWorker.register('./sw.js')
.then(registration => {
console.log('Service worker successfully registered.');
return registration;
})
.catch(err => {
console.error('Unable to register service worker.', err);
});
}
// 使用 showNotification 方法彈出通知
function execute() {
registerServiceWorker().then(registration => {
registration.showNotification('短標(biāo)題4', {
body: "我是bodya,老長老長了",
icon: "./icon.jpg",
// badge: './icon.jpg', // 手機(jī)通知消息縮略圖
// dir:'auto',// 文字方向 auto|ltr|rtr
// vibrate:[],// 震動 以數(shù)組的形式進(jìn)行配置镇眷,其中的數(shù)字以2個為一組咬最,分別表示震動的毫秒數(shù),和不震動的毫秒數(shù)欠动,如此往復(fù)
// sound:'',// 聲音
image: "./image.jpg",
// timestamp: Date.parse('2019/02/15 15:22:00'), // 時間戳
tag: 'message-group-1', // 兩個相同ID的通知會被歸類到一起
renotify: true, // tag 一同使用的 替換通知時提示聲音或者震動
requireInteraction: true, // 顯式的讓通知一直顯示直到用戶交互,
data: {
time: (new Date()).toString(),
message: 'Hello World!'
},
actions: [{
action: 'coffee-action',
title: 'Coffee',
icon: './icon.jpg'
}, {
action: 'doughnut-action',
title: 'Doughnut',
icon: './icon.jpg'
}]
});
});
}
navigator.serviceWorker.addEventListener('message', function(event) {
console.log("Client 1 Received Message: " + event.data);
console.log("Client ports: " + event.ports);
// window.location.
// 如果 event.ports存在永乌,就可以通過端口傳遞數(shù)據(jù)到 sw.js中
// event.ports[0].postMessage("Client 1 Says 'Hello back!'");
});
});
</script>
</html>
main.css
h3{
color: #f00;
}
sw.js
var cacheStorageKey = 'test-pwa-4'
var cacheList = [
'/pwa',
'/pwa/index.html',
'/pwa/main.css',
'/pwa/manifest.json',
'/pwa/icon.jpg'
]
self.addEventListener('install', e => {
// 如果監(jiān)聽到了 service worker 已經(jīng)安裝成功的話,就會調(diào)用 event.waitUntil 回調(diào)函數(shù)
e.waitUntil(
// 安裝成功后操作 CacheStorage 緩存具伍,使用之前需要先通過 caches.open() 打開對應(yīng)緩存空間铆遭。
caches.open(cacheStorageKey)
// 通過 cache 緩存對象的 addAll 方法添加 precache 緩存
// .then(cache => cache.addAll(cacheList))
.then(cache => {
return cache.addAll(cacheList);
})
.then(() => self.skipWaiting())
)
})
self.addEventListener('fetch', function(e) {
console.log(e.request)
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response;
}
// 如果 service worker 沒有返回,那就得直接請求真實遠(yuǎn)程服務(wù)
var request = e.request.clone(); // 把原始請求拷過來
return fetch(request).then(function(httpRes) {
// http請求的返回已被抓到沿猜,可以處置了枚荣。
// 請求失敗了,直接返回失敗的結(jié)果就好了啼肩。橄妆。
if (!httpRes || httpRes.status !== 200) {
return httpRes;
}
// 請求成功的話衙伶,將請求緩存起來。
var responseClone = httpRes.clone();
caches.open(cacheStorageKey).then(function(cache) {
cache.put(e.request, responseClone);
});
return httpRes;
});
})
)
})
self.addEventListener('activate', function(e) {
e.waitUntil(
//獲取所有cache名稱
caches.keys().then(cacheNames => {
return Promise.all(
// 獲取所有不同于當(dāng)前版本名稱cache下的內(nèi)容
cacheNames.filter(cacheNames => {
return cacheNames !== cacheStorageKey
}).map(cacheNames => {
return caches.delete(cacheNames)
})
)
}).then(() => {
return self.clients.claim()
})
)
})
function doSome() {
console.log(33333334)
}
// 點擊通知
self.addEventListener('notificationclick', event => {
// 獲取傳遞data數(shù)據(jù)
const notificationData = event.notification.data;
console.log('The data notification had the following parameters:');
Object.keys(notificationData).forEach(key => {
console.log(` ${key}: ${notificationData[key]}`);
});
let clickedNotification = event.notification;
// 關(guān)閉通知
clickedNotification.close();
// 執(zhí)行某些異步操作害碾,等待它完成
let promiseChain = doSome();
event.waitUntil(promiseChain);
// 點擊按鈕
if (!event.action) {
// 沒有點擊在按鈕上
console.log('沒有點擊在按鈕上');
return;
}
switch (event.action) {
case 'coffee-action':
console.log('User \'s coffee.');
send_message_to_all_clients('666666666666666666')
break;
case 'doughnut-action':
console.log('User \'s doughnuts.');
// 再次彈窗
self.registration.showNotification('Had to show a notification.');
// 打開新頁面
clients.openWindow("https://taobao.com");
break;
default:
console.log(`Unknown action clicked: '${event.action}'`);
break;
}
});
function send_message_to_all_clients(msg) {
clients.matchAll().then(clients => {
clients.forEach(client => {
console.log(clients, client)
send_message_to_client(client, msg).then(m => console.log("SW Received Message: " + m));
})
})
}
function send_message_to_client(client, msg) {
return new Promise(function(resolve, reject) {
var msg_chan = new MessageChannel();
msg_chan.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// 發(fā)送給頁面操作dom元素 (把posrt2發(fā)送給index.html矢劲。方便回傳數(shù)據(jù))
client.postMessage("SW Says: '" + msg + "'", [msg_chan.port2]);
// client.postMessage('給客戶端發(fā)送數(shù)據(jù)'); //如果僅僅發(fā)送數(shù)據(jù),不要回傳數(shù)據(jù)這個就可以了
});
}
manifest.json
{
"name": "一個PWA示例",
"short_name": "PWA示例",
"start_url": "./index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#3eaf7c",
"icons": [{
"src": "./icon.jpg",
"sizes": "120x120",
"type": "image/jpg"
}]
}
icon.jpg(120*120) image.jpg 自己隨便找兩張圖片就可以了