《PWA學習與實踐》系列文章已整理至gitbook - PWA學習手冊,文字內容已同步至learning-pwa-ebook。轉載請註明作者與出處。
本文是《PWA學習與實踐》系列的第七篇文章。
PWA作為時下最火熱的技術概念之一,對提升Web應用的安全、效能和體驗有著很大的意義,非常值得我們去了解與學習。對PWA感興趣的朋友歡迎關注《PWA學習與實踐》系列文章。
本文中的程式碼可以在learning-pwa的notification分支上找到(git clone
後注意切換到notification分支)。
1. 引言
在第五篇文章《Web中進行服務端訊息推送》中,我介紹瞭如何使用Push API進行服務端訊息推送。提到Push就不得不說與其聯絡緊密的另一個API——Notification API。它讓我們可以在“網站外”顯示訊息提示:
即使當你切換到其他Tab,也可以通過提醒互動來快速讓使用者回到你的網站;甚至當使用者離開當前網站,仍然可以收到系統的提醒訊息,並且可以通過訊息提醒快速開啟你的網站。
Notification的功能本身與Push並不耦合,你完全可以只使用Notification API或者Push API來構建Web App的某些功能。因此,本文會先介紹如何使用Notification API。然後,作為Notification的“黃金搭檔”,本文還會介紹如何組合使用Push & Notification(訊息推送與提醒)。
2. 使用Notification API
在這第二節裡,我們先來了解如何獨立使用Notification功能。相較於第五篇中的Push功能,Notification API更加簡潔易懂。
2.1. 獲取提醒許可權
首先,進行呼叫訊息提醒API需要獲得使用者的授權。
在呼叫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
可能的值為:
- denied:使用者拒絕了通知的顯示
- granted:使用者允許了通知的顯示
- default:因為不知道使用者的選擇,所以瀏覽器的行為與denied時相同
chrome中,可以在chrome://settings/content/notifications
裡進行通知的設定與管理。
2.2. 設定你的提醒內容
獲取使用者授權後,我們就可以通過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
中則包含了一些其他設定。
- body:提醒的內容
- icon:提醒的圖示
- actions:提醒可以包含一些自定義操作
- tag:相當於是ID,通過該ID標識可以操作特定的notification
- renotify:是否允許重複提醒,預設為false。當不允許重複提醒時,同一個tag的notification只會顯示一次
注意,由於不同瀏覽器中,對於
option
屬性的支援情況並不相同。部分屬性在一些瀏覽器中並不支援。
2.3. 捕獲使用者的點選
在上一部分中,我們已經為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中會有不同的輸出。
2.4. Service Worker與client通訊
到目前為止,我們已經可以順利得給使用者展示提醒,並且在使用者操作提醒後準確捕獲到使用者的操作。然而,還缺最重要的一步——針對不同的操作,觸發不同的互動。例如,
- 點選提醒本身會彈出書籍簡介;
- 點選“看一看”會給使用者展示本書的詳情;
- 點選“聯絡我”會嚮應用管理者發郵件等等。
這裡有個很重要的地方:我們在Service Worker中捕獲使用者操作,但是需要在client(這裡的client是指前端頁面的指令碼環境)中觸發相應操作(呼叫頁面方法/進行頁面跳轉…)。因此,這就需要讓Service Worker與client進行通訊。通訊包括下面兩個部分:
- 在Service Worker中使用Worker的
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);
});
})
);
});
複製程式碼
- 在client中監聽
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的結合將會幫助我們構築一個強大推送與提醒功能。下面就來看下它們的簡單結合。
3. 訊息推送與提醒
在第五篇《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中的訊息推送與提醒。
我們還可以將這個功能再豐富一些。由於使用者在關閉該網站時仍然可以收到提醒,因此加入一些更強大功能:
- 當使用者切換到其他Tab時,點選提醒會立刻回到網站的tab;
- 當使用者未開啟該網站時,點選提醒可以直接開啟網站。
// 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();
複製程式碼
4. MacOS Safari中的Web Notification
目前移動端瀏覽器普遍還不支援該特性。但是在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中找到。
5. 寫在最後
Web Notification是一個非常強大的API,尤其在和Push結合後,為WebApp帶來了類似Native的豐富能力。
本文中所有的程式碼示例均可以在learn-pwa/notification上找到。
如果你喜歡或想要了解更多的PWA相關知識,歡迎關注我,關注《PWA學習與實踐》系列文章。我會總結整理自己學習PWA過程的遇到的疑問與技術點,並通過實際程式碼和大家一起實踐。
到目前為止,我們已經學習了Manifest、離線快取、訊息推送、訊息提醒、Debug等一些基礎知識。在下一篇文章裡,我們會繼續瞭解與學習PWA中的一個重要功能——後臺同步。
《PWA學習與實踐》系列
- 第一篇:2018,開始你的PWA學習之旅
- 第二篇:10分鐘學會使用Manifest,讓你的WebApp更“Native”
- 第三篇:從今天起,讓你的WebApp離線可用
- 第四篇:TroubleShooting: 解決FireBase login驗證失敗問題
- 第五篇:與你的使用者保持聯絡: Web Push功能
- 第六篇:How to Debug? 在chrome中除錯你的PWA
- 第七篇:增強互動:使用Notification API來進行提醒(本文)
- 第八篇:使用Service Worker進行後臺資料同步
- 第九篇:PWA實踐中的問題與解決方案
- 第十篇:Resource Hint - 提升頁面載入效能與體驗
- 第十一篇:從PWA離線工具集workbox中學習各類離線策略(寫作中…)