PWA 2再一次深度學(xué)習(xí)

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)
        });
    }
});

如何工作

  1. 首先我們需要在頁面的 JavaScript 主線程中使用 serviceWorkerContainer.register() 來注冊 Service Worker ,在注冊的過程中丁眼,瀏覽器會在后臺啟動嘗試 Service Worker 的安裝步驟筷凤。

  2. 如果注冊成功,Service Worker 在 ServiceWorkerGlobalScope 環(huán)境中運行苞七; 這是一個特殊的 worker context藐守,與主腳本的運行線程相獨立,同時也沒有訪問 DOM 的能力蹂风。

  3. 后臺開始安裝步驟卢厂, 通常在安裝的過程中需要緩存一些靜態(tài)資源。如果所有的資源成功緩存則安裝成功惠啄,如果有任何靜態(tài)資源緩存失敗則安裝失敗慎恒,在這里失敗的不要緊任内,會自動繼續(xù)安裝直到安裝成功,如果安裝不成功無法進(jìn)行下一步 — 激活 Service Worker融柬。

  4. 開始激活 Service Worker死嗦,必須要在 Service Worker 安裝成功之后,才能開始激活步驟粒氧,當(dāng) Service Worker 安裝完成后越除,會接收到一個激活事件(activate event)。激活事件的處理函數(shù)中外盯,主要操作是清理舊版本的 Service Worker 腳本中使用資源摘盆。

  5. 激活成功后 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)用至主屏幕

  1. 打開瀏覽器菜單饭庞,會看到添加到主屏幕的功能,用戶可以點擊該選項手動將 PWA 站點添加至主屏幕

  2. 彈出應(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 自己隨便找兩張圖片就可以了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末慌随,一起剝皮案震驚了整個濱河市芬沉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阁猜,老刑警劉巖丸逸,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剃袍,居然都是意外死亡黄刚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門民效,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憔维,“玉大人,你說我怎么就攤上這事畏邢∫蛋牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵舒萎,是天一觀的道長程储。 經(jīng)常有香客問我,道長逆甜,這世上最難降的妖魔是什么虱肄? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任致板,我火速辦了婚禮交煞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斟或。我一直安慰自己素征,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布萝挤。 她就那樣靜靜地躺著御毅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怜珍。 梳的紋絲不亂的頭發(fā)上端蛆,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音酥泛,去河邊找鬼今豆。 笑死嫌拣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呆躲。 我是一名探鬼主播异逐,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼插掂!你這毒婦竟也來了灰瞻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辅甥,失蹤者是張志新(化名)和其女友劉穎酝润,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肆氓,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡袍祖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谢揪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕉陋。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拨扶,靈堂內(nèi)的尸體忽然破棺而出凳鬓,到底是詐尸還是另有隱情,我是刑警寧澤患民,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布缩举,位于F島的核電站,受9級特大地震影響匹颤,放射性物質(zhì)發(fā)生泄漏仅孩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一印蓖、第九天 我趴在偏房一處隱蔽的房頂上張望辽慕。 院中可真熱鬧,春花似錦赦肃、人聲如沸溅蛉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽船侧。三九已至,卻和暖如春厅各,著一層夾襖步出監(jiān)牢的瞬間镜撩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工队塘, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留袁梗,地道東北人卫旱。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓棒假,卻偏偏與公主長得像佩脊,于是被迫代替她去往敵國和親密幔。 傳聞我的和親對象是個殘疾皇子鸳君,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容