《PWA學習與實踐》系列文章已整理至 gitbook - PWA學習手冊,文字內容已同步至 learning-pwa-ebook。轉載請註明做者與出處。
本文是《PWA學習與實踐》系列的第七篇文章。javascript
PWA做爲時下最火熱的技術概念之一,對提高Web應用的安全、性能和體驗有着很大的意義,很是值得咱們去了解與學習。對PWA感興趣的朋友歡迎關注《PWA學習與實踐》系列文章。前端
本文中的代碼能夠在learning-pwa的notification分支上找到(git clone
後注意切換到notification分支)。java
在第五篇文章《Web中進行服務端消息推送》中,我介紹瞭如何使用Push API進行服務端消息推送。提到Push就不得不說與其聯繫緊密的另外一個API——Notification API。它讓咱們能夠在「網站外」顯示消息提示:git
即便當你切換到其餘Tab,也能夠經過提醒交互來快速讓用戶回到你的網站;甚至當用戶離開當前網站,仍然能夠收到系統的提醒消息,而且能夠經過消息提醒快速打開你的網站。github
Notification的功能自己與Push並不耦合,你徹底能夠只使用Notification API或者Push API來構建Web App的某些功能。所以,本文會先介紹如何使用Notification API。而後,做爲Notification的「黃金搭檔」,本文還會介紹如何組合使用Push & Notification(消息推送與提醒)。chrome
在這第二節裏,咱們先來了解如何獨立使用Notification功能。相較於第五篇中的Push功能,Notification API更加簡潔易懂。json
首先,進行調用消息提醒API須要得到用戶的受權。promise
在調用Notification相關API以前,須要先使用Notification
對象上的靜態方法Notification.requestPermission()
來獲取受權。因爲Notification.requestPermission()
在某些版本瀏覽器中會接收一個回調函數(Notification.requestPermission(callback)
)做爲參數,而在另外一些瀏覽器版本中會返回一個promise,所以將該方法進行包裝,統一爲promise調用:瀏覽器
// index.js function askPermission() { return new Promise(function (resolve, reject) { var permissionResult = Notification.requestPermission(function (result) { resolve(result); }); if (permissionResult) { permissionResult.then(resolve, reject); } }).then(function (permissionResult) { if (permissionResult !== 'granted') { throw new Error('We weren\'t granted permission.'); } }); } registerServiceWorker('./sw.js').then(function (registration) { return Promise.all([ registration, askPermission() ]) })
咱們建立了一個askPermission()
方法來統一Notification.requestPermission()
的調用形式,並在Service Worker註冊完成後調用該方法。調用Notification.requestPermission()
獲取的permissionResult
可能的值爲:緩存
chrome中,能夠在chrome://settings/content/notifications
裏進行通知的設置與管理。
獲取用戶受權後,咱們就能夠經過registration.showNotification()
方法進行消息提醒了。
當咱們註冊完Service Worker後,then
方法的回調函數會接收一個registration
參數,經過調用其上的showNotification()
方法便可觸發提醒:
// index.js registerServiceWorker('./sw.js').then(function (registration) { return Promise.all([ registration, askPermission() ]) }).then(function (result) { var registration = result[0]; /* ===== 添加提醒功能 ====== */ document.querySelector('#js-notification-btn').addEventListener('click', function () { var title = 'PWA即學即用'; var options = { body: '邀請你一塊兒學習', icon: '/img/icons/book-128.png', actions: [{ action: 'show-book', title: '去看看' }, { action: 'contact-me', title: '聯繫我' }], tag: 'pwa-starter', renotify: true }; registration.showNotification(title, options); }); /* ======================= */ })
上面這段代碼爲頁面上的button添加了一個click事件監聽:當點擊後,調用registration.showNotification()
方法來顯示消息提醒,該方法接收兩個參數:title
與option
。title
用來設置該提醒的主標題,option
中則包含了一些其餘設置。
注意,因爲不一樣瀏覽器中,對於
option
屬性的支持狀況並不相同。部分屬性在一些瀏覽器中並不支持。
在上一部分中,咱們已經爲Web App添加了提醒功能。點擊頁面中的「提醒」按鈕,系統就會彈出提醒框,並展現相關提醒消息。
然而更多的時候,咱們並不只僅但願只展現有限的信息,更但願能引導用戶進行交互。例如推薦一本新書,讓用戶點擊閱讀或購買。在上一部分咱們設置的提醒框中,包含了「去看看」和「聯繫我」兩個按鈕選項,那麼怎麼作才能捕獲用戶的點擊操做,而且知道用戶點擊了哪一個呢?這一小節,就會告訴你如何實現。
還記的上一部分裏咱們定義的actions麼?
… actions: [{ action: 'show-book', title: '去看看' }, { action: 'contact-me', title: '聯繫我' }] …
爲了可以響應用戶對於提醒框的點擊事件,咱們須要在Service Worker中監聽notificationclick
事件。在該事件的回調函數中咱們能夠獲取點擊的相關信息:
// sw.js self.addEventListener('notificationclick', function (e) { var action = e.action; console.log(`action tag: ${e.notification.tag}`, `action: ${action}`); switch (action) { case 'show-book': console.log('show-book'); break; case 'contact-me': console.log('contact-me'); break; default: console.log(`未處理的action: ${e.action}`); action = 'default'; break; } e.notification.close(); });
e.action
獲取的值,就是咱們在showNotification()
中定義的actions裏的action。所以,經過e.action
就能夠知道用戶點擊了哪個操做選項。注意,當用戶點擊提醒自己時,也會觸發notificationclick
,可是不包含任何action值,因此在代碼中將其置於default默認操做中。
如今試一下,咱們就能夠捕獲用戶對於不一樣選項的點擊了。點擊後在Console中會有不一樣的輸出。
到目前爲止,咱們已經能夠順利得給用戶展現提醒,而且在用戶操做提醒後準確捕獲到用戶的操做。然而,還缺最重要的一步——針對不一樣的操做,觸發不一樣的交互。例如,
這裏有個很重要的地方:咱們在Service Worker中捕獲用戶操做,可是須要在client(這裏的client是指前端頁面的腳本環境)中觸發相應操做(調用頁面方法/進行頁面跳轉…)。所以,這就須要讓Service Worker與client進行通訊。通訊包括下面兩個部分:
postMessage()
方法來通知client:// sw.js self.addEventListener('notificationclick', function (e) { …… // 略去上一節內容 e.waitUntil( // 獲取全部clients self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { return; } clients.forEach(function (client) { // 使用postMessage進行通訊 client.postMessage(action); }); }) ); });
message
事件,判斷data
,進行不一樣的操做:// index.js navigator.serviceWorker.addEventListener('message', function (e) { var action = e.data; console.log(`receive post-message from sw, action is '${e.data}'`); switch (action) { case 'show-book': location.href = 'https://book.douban.com/subject/20515024/'; break; case 'contact-me': location.href = 'mailto:someone@sample.com'; break; default: document.querySelector('.panel').classList.add('show'); break; } });
當用戶點擊提醒後,咱們在notificationclick
監聽中,將action經過postMessage()
通訊給client;而後在client中監聽message
事件,基於action(e.data
)來進行不一樣的操做(跳轉到圖書詳情頁/發送郵件/顯示簡介面板)。
至此,一個比較簡單與完整的消息提醒(Notification)功能就完成了。
然而目前的消息提醒還存在必定的侷限性。例如,只有在用戶訪問網站期間纔能有機會觸發提醒。正如本文一開始所說,Push & Notification的結合將會幫助咱們構築一個強大推送與提醒功能。下面就來看下它們的簡單結合。
在第五篇《Web中進行服務端消息推送》最後,咱們經過監聽push
事件來處理服務端推送:
// sw.js self.addEventListener('push', function (e) { var data = e.data; if (e.data) { data = data.json(); console.log('push的數據爲:', data); self.registration.showNotification(data.text); } else { console.log('push沒有任何數據'); } });
簡單修改以上代碼,與咱們本文中的提醒功能相結合:
// sw.js self.addEventListener('push', function (e) { var data = e.data; if (e.data) { data = data.json(); console.log('push的數據爲:', data); var title = 'PWA即學即用'; var options = { body: data, icon: '/img/icons/book-128.png', image: '/img/icons/book-521.png', // no effect actions: [{ action: 'show-book', title: '去看看' }, { action: 'contact-me', title: '聯繫我' }], tag: 'pwa-starter', renotify: true }; self.registration.showNotification(title, options); } else { console.log('push沒有任何數據'); } });
使用Push來向用戶推送信息,並在Service Worker中直接調用Notification API來展現該信息的提醒框。這樣,即便是在用戶關閉該Web App時,依然能夠收到提醒,相似於Native中的消息推送與提醒。
咱們還能夠將這個功能再豐富一些。因爲用戶在關閉該網站時仍然能夠收到提醒,所以加入一些更強大功能:
// sw.js self.addEventListener('notificationclick', function (e) { var action = e.action; console.log(`action tag: ${e.notification.tag}`, `action: ${action}`); switch (action) { case 'show-book': console.log('show-book'); break; case 'contact-me': console.log('contact-me'); break; default: console.log(`未處理的action: ${e.action}`); action = 'default'; break; } e.notification.close(); e.waitUntil( // 獲取全部clients self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { // 當不存在client時,打開該網站 self.clients.openWindow && self.clients.openWindow('http://127.0.0.1:8085'); return; } // 切換到該站點的tab clients[0].focus && clients[0].focus(); clients.forEach(function (client) { // 使用postMessage進行通訊 client.postMessage(action); }); }) ); });
注意這兩行代碼,第一行會在網站關閉時打開該網站,第二行會在存在tab時自動切換到網站的tab。
self.clients.openWindow && self.clients.openWindow('http://127.0.0.1:8085'); clients[0].focus && clients[0].focus();
目前移動端瀏覽器廣泛還不支持該特性。可是在Mac OS上的safari裏面是支持該特性的,不過其調用方式與上文代碼有些不太同樣。在safari中使用Web Notification不是調用registration.showNotification()
方法,而是須要建立一個Notification對象。
// index.js …… document.querySelector('#js-notification-btn').addEventListener('click', function () { var title = 'PWA即學即用'; var options = { body: '邀請你一塊兒學習', icon: '/img/icons/book-128.png', actions: [{ action: 'show-book', title: '去看看' }, { action: 'contact-me', title: '聯繫我' }], tag: 'pwa-starter', renotify: true }; // registration.showNotification(title, options); // 使用Notification構造函數建立提醒框 // 而非registration.showNotification()方法 var notification = new Notification(title, options); }); ……
Notification對象繼承自EventTarget接口,所以在safari中須要經過添加click事件的監聽來觸發提醒框的交互操做:
// index.js notification.addEventListener('click', function (e) { document.querySelector('.panel').classList.add('show'); });
該功能示例能夠在learn-pwa/notify4safari中找到。
Web Notification是一個很是強大的API,尤爲在和Push結合後,爲WebApp帶來了相似Native的豐富能力。
本文中全部的代碼示例都可以在learn-pwa/notification上找到。
若是你喜歡或想要了解更多的PWA相關知識,歡迎關注我,關注《PWA學習與實踐》系列文章。我會總結整理本身學習PWA過程的遇到的疑問與技術點,並經過實際代碼和你們一塊兒實踐。
到目前爲止,咱們已經學習了Manifest、離線緩存、消息推送、消息提醒、Debug等一些基礎知識。在下一篇文章裏,咱們會繼續瞭解與學習PWA中的一個重要功能——後臺同步。