原文地址:notification behaviourhtml
譯文地址:通知行爲git
譯者:任家樂github
到此爲止,咱們已經瀏覽了能夠改變通知樣式的選項,除了樣式,咱們還能夠經過一些選項來改變通知的行爲。chrome
默認狀況下,若是隻設置視覺相關選項,調用 showNotification()
會出現如下行爲:windows
在這一節中,咱們會探討如何單獨使用一些選項改變默認的通知行爲,這相對來講比較容易實施和利用。promise
當用戶點擊通知時,默認不會觸發任何事件,它並不會關閉或移除通知。瀏覽器
通知點擊事件的常見用法是調用它來關閉通知、同時執行一些其餘的邏輯(例如,打開一個窗口或對應用程序進行一些API調用)緩存
爲此,咱們須要在 service worker 中添加一個 「notificationclick」 事件監聽器。 這個事件將在點擊通知時被調用。異步
self.addEventListener('notificationclick', function(event) {
const clickedNotification = event.notification;
clickedNotification.close();
// 點擊通知後作些什麼
const promiseChain = doSomething();
event.waitUntil(promiseChain);
});
複製代碼
正如你在此示例中所看到的,被點擊的通知能夠經過 event.notification
參數來訪問。經過這個參數咱們能夠得到通知的屬性和方法,所以咱們可以調用通知的 close()
方法,同時執行一些額外的操做。
提示:在程序運行高峯期,你仍然須要調用 event.waitUntil()
保證 service worker 的持續運行。
相比於以前的普通點擊行爲,actions
的使用能夠提供給用戶更高級別的交互體驗。
在上一節中,咱們知道了如何調用 showNotification()
來定義 actions
:
const title = 'Actions Notification';
const options = {
actions: [
{
action: 'coffee-action',
title: 'Coffee',
icon: '/images/demos/action-1-128x128.png'
},
{
action: 'doughnut-action',
title: 'Doughnut',
icon: '/images/demos/action-2-128x128.png'
},
{
action: 'gramophone-action',
title: 'gramophone',
icon: '/images/demos/action-3-128x128.png'
},
{
action: 'atom-action',
title: 'Atom',
icon: '/images/demos/action-4-128x128.png'
}
]
};
const maxVisibleActions = Notification.maxActions;
if (maxVisibleActions < 4) {
options.body = `This notification will only display ` +
`${maxVisibleActions} actions.`;
} else {
options.body = `This notification can display up to ` +
`${maxVisibleActions} actions.`;
}
registration.showNotification(title, options);
複製代碼
若是用戶點擊了 action 按鈕,經過 notificationclick
回調中返回的 event.action
就能夠知道被點擊的按鈕是哪一個。
event.action
會包含全部選項中有關 action
的值的集合。在上面的例子中,event.action
的值則會是: 「coffee-action」、 「doughnut-action」、 「gramophone-action」 或 「atom-action」 的其中一個。
所以經過 event.action
,咱們能夠檢測到通知或 action 的點擊,代碼以下:
self.addEventListener('notificationclick', function(event) {
if (!event.action) {
// 正常的通知點擊事件
console.log('Notification Click.');
return;
}
switch (event.action) {
case 'coffee-action':
console.log('User ❤️️\'s coffee.');
break;
case 'doughnut-action':
console.log('User ❤️️\'s doughnuts.');
break;
case 'gramophone-action':
console.log('User ❤️️\'s music.');
break;
case 'atom-action':
console.log('User ❤️️\'s science.');
break;
default:
console.log(`Unknown action clicked: '${event.action}'`);
break;
}
});
複製代碼
tag 選項的本質是一個字符串類型的 ID,以此將通知 「分組」 在一塊兒,並提供了一種簡單的方法來向用戶顯示多個通知,這裏可能用示例來解釋最爲簡單:
讓咱們來展現一個通知,並給它標記一個 tag,例如 「message-group-1」。 咱們能夠按照以下代碼來展現這個通知:
const title = 'Notification 1 of 3';
const options = {
body: 'With \'tag\' of \'message-group-1\'',
tag: 'message-group-1'
};
registration.showNotification(title, options);
複製代碼
這會展現咱們定義好的第一個通知。
咱們再用一個新的 tag 「message-group-2」 來標記並展現第二個通知,代碼以下:
const title = 'Notification 2 of 3';
const options = {
body: 'With \'tag\' of \'message-group-2\'',
tag: 'message-group-2'
};
registration.showNotification(title, options);
複製代碼
這樣會展現給用戶第二個通知。
如今讓咱們展現第三個通知,但不新增 tag,而是重用咱們第一次定義的 tag 「message-group-1」。這樣操做會關閉以前的第一個通知並將其替換成新定義的通知。
const title = 'Notification 3 of 3';
const options = {
body: 'With \'tag\' of \'message-group-1\'',
tag: 'message-group-1'
};
registration.showNotification(title, options);
複製代碼
如今即便咱們連續 3 次調用 showNotification()
也只會展現 2 個通知。
tag
這個選項簡單來看就是一個用於信息分組的方式,所以在新通知與已有通知標記爲同一個tag時,當前被展現的全部舊通知將會被關閉。
使用 tag
有一個容易被忽略的小細節:當它替換了一個通知時,是沒有音效和震動提醒的。
此時 Renotify
選項就有了用武之地。
在寫此文時,這個選項大多數應用於移動設備。經過設置它,接收到新的通知時,系統會震動並播放系統音效。
某些場景下,你可能更但願替換通知時可以提醒到用戶,而不是默默地進行。聊天應用則是一個很好的例子。這種狀況你須要同時使用 tag
和 Renotify
選項。
const title = 'Notification 2 of 2';
const options = {
tag: 'renotify',
renotify: true
};
registration.showNotification(title, options);
TypeError: Failed to execute 'showNotification' on 'ServiceWorkerRegistration':
Notifications which set the renotify flag must specify a non-empty tag
複製代碼
注意: 若是你設置了 Renotify: true
但卻沒有設置標籤,會出現如下報錯信息:
類型錯誤:不可以在 「ServiceWorkerRegistration」 上執行 「showNotification」 方法:設置了 renotify 標識的通知必須聲明一個不爲空的標籤。(TypeError: Failed to execute 'showNotification' on 'ServiceWorkerRegistration':Notifications which set the renotify flag must specify a non-empty tag)
這一選項能夠阻止設備震動、音效以及屏幕亮起的默認行爲。若是你的通知不須要立馬讓用戶注意到,這個選項是最合適的。
const title = 'Silent Notification';
const options = {
silent: true
};
registration.showNotification(title, options);
複製代碼
注意: 若是同時設置了 silent 和 Renotify,silent 選項會取得更高的優先級。
桌面 chrome 瀏覽器會展現通知一段時間後將其隱藏,而安卓設備的 chrome 瀏覽器不會有這種行爲,通知會一直展現,直到用戶對其進行操做。
若是要強制讓通知持續展現直到用戶對其操做,須要添加 requireInteraction
選項,此選項會展現通知直到用戶消除或點擊它。
const title = 'Require Interaction Notification';
const options = {
body: 'With "requireInteraction: \'true\'".',
requireInteraction: true
};
registration.showNotification(title, options);
複製代碼
請謹慎使用這個選項,由於一直展現通知、並強制讓用戶停下手頭的事情來忽略通知可能會干擾到用戶。
在下一節中,咱們會瀏覽一些 web 上適用的用於管理通知的常見模式,以及如何執行一些常見的 actions
,例如在點擊通知時執行打開網頁的行爲。
譯文地址:經常使用的通知模式
譯者:任家樂
此篇咱們將會探索 Web 推送的一些經常使用模式,包括使用一些 service worker 提供的 API。
在上一篇中,咱們瞭解瞭如何監聽 notificationclick
事件。
除了 notificationclick
事件,咱們還能夠監聽 notificationclose
事件,它會在用戶忽略其中一個通知(例如,用戶點擊了關閉按鈕或劃掉了通知,而不是點擊了它)時被調用。
這個事件一般被用做數據分析,以此追蹤用戶與通知的互動狀況。
self.addEventListener('notificationclose', function(event) {
const dismissedNotification = event.notification;
const promiseChain = notificationCloseAnalytics();
event.waitUntil(promiseChain);
});
複製代碼
當收到推送的信息時,一般只須要獲取用戶點擊後的有用數據。例如,獲取用戶點擊通知時打開的頁面地址。
若是須要將推送事件中獲取的數據傳遞給通知,最簡單的方式就是在調用 showNotification()
時,給參數 options 對象添加一個 data
屬性,其值爲對象類型,例如如下所示:
const options = {
body: 'This notification has data attached to it that is printed ' +
'to the console when it\'s clicked.',
tag: 'data-notification',
data: {
time: new Date(Date.now()).toString(),
message: 'Hello, World!'
}
};
registration.showNotification('Notification with Data', options);
複製代碼
在點擊事件的回調內,能夠經過 event.notification.data
來獲取數據,例如:
const notificationData = event.notification.data;
console.log('');
console.log('The data notification had the following parameters:');
Object.keys(notificationData).forEach((key) => {
console.log(` ${key}: ${notificationData[key]}`);
});
console.log('');
複製代碼
對一個通知來講,打開指定地址的窗口/標籤頁能夠說是一種最多見的反饋,這個咱們能夠經過 clients.openWindow() 來實現。
在 notificationclick
事件中,咱們會運行相似下面的代碼來實現以上需求:
const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);
複製代碼
在下一節中,咱們會看下如何檢測用戶點擊通知後跳轉的頁面是否已被打開,若是已被打開,咱們能夠直接呼起已打開的標籤頁,而不是打開一個新的標籤頁。
若是可能,咱們應該呼起一個已打開的窗口,而不是在每次用戶點擊通知時都打開一個新的窗口。
在咱們探索如何實現以前,值得提醒的是你只可以在與通知同域名的頁面實現這個需求。由於咱們只能檢測咱們本身站點的頁面是否已被打開,這也避免了 開發者看到用戶正在瀏覽的全部站點。
再來看下以前的例子,咱們會對代碼稍做調整來檢測頁面 '/demos/notification-examples/example-page.html' 是否已經被打開。
const urlToOpen = new URL(examplePage, self.location.origin).href;
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true
})
.then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
});
event.waitUntil(promiseChain);
複製代碼
讓咱們逐步瀏覽下代碼。
首先,咱們將示例中目標頁面的地址傳遞給 URL API。這是我從 Jeff Posnick 那學到的一個巧妙的計策。 調用 new URL()
並傳入 location 對象,若是傳入的第一個參數是相對地址,則會返回頁面的絕對地址(例如,「/」 會變成 「https://站點域名」 )。
咱們將地址轉成了絕對地址則是爲了以後與窗口的地址做對比。
const urlToOpen = new URL(examplePage, self.location.origin).href;
複製代碼
以後,咱們會經過調用 matchAll()
獲得一系列 WindowClient
對象,包含了當前打開的標籤頁和窗口。(記住,這些標籤頁只是你域名下的頁面)
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true
})
複製代碼
matchAll
方法中傳入的 options 對象則告訴瀏覽器咱們只想獲取 「window」 類型的對象(例如,只查看標籤頁、窗口,不包含 web workers [瀏覽器的其餘工做線程])。 includeUncontrolled
屬性表示咱們只能獲取沒有被當前 service worker 控制的全部標籤頁(本域下),例如 service worker 正在運行當前代碼。通常來講,在調用 matchAll()
時,你一般會將 includeUncontrolled
設置爲 true。
咱們 以promiseChain
(promise 鏈式調用)的形式捕獲返回的 promise 對象,所以以後能夠將其傳 入event.waitUntil()
方法中以此保持咱們的 service worker 持續工做。
當上一步的 matchAll()
返回的 promise 對象已完成異步操做,咱們就能夠開始遍歷返回的 window 對象,並將這些對象的 URL 和想要打開的目標 URL 進行對比,若是發現有匹配的,則調用 matchingClient.focus()
方法,它會呼起匹配的窗口,引發用戶的注意。
若是沒有與之匹配的 URL,咱們則採用上一節的方式新開窗口打開地址。
.then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
});
複製代碼
注意: 咱們會返回 matchingClient.focus()
、clients.openWindow()
方法執行後返回的 promise 對象, 這樣 promise 對象就能夠組成咱們的 promise 調用鏈了。
咱們已經看到,給一個通知添加標籤後會致使用同一個標籤標識的已有通知被替代。
但經過使用通知相關的 API,你能夠更靈活地覆蓋展現通知。好比一個聊天應用,開發者可能更但願用新的通知來展現"你有 2 條未讀信息"等相似信息,而不是隻展現最新接收到的信息。
你能夠利用新的通知,或以其餘方式操做當前已有通知,使用 registration.getNotifications()API 可以得到到你 APP 中全部當前展現的通知。
讓咱們看看如何使用這個 API 去實現剛說的聊天應用的例子。
在聊天應用中,咱們假設每一個通知都有一些包含用戶名的數據。
咱們要作的第一件事就是在全部已打開的通知中找到帶有具體用戶名的用戶。首先調用 registration.getNotifications()
方法,以後遍歷其結果檢測 notification.data
中是否有具體用戶名。
const promiseChain = registration.getNotifications()
.then(notifications => {
let currentNotification;
for(let i = 0; i < notifications.length; i++) {
if (notifications[i].data &&
notifications[i].data.userName === userName) {
currentNotification = notifications[i];
}
}
return currentNotification;
})
複製代碼
下一步就是用新的通知來替換上一步中得到的通知。
在這個虛擬的消息應用中,咱們會給新的通知添加一個累計新通知數量的數據,每產生新的通知都會累加這個計數,以此來記錄用戶收到的新信息的數量。
.then((currentNotification) => {
let notificationTitle;
const options = {
icon: userIcon,
}
if (currentNotification) {
// 咱們有一個已經打開的通知,讓咱們利用它來作些什麼
const messageCount = currentNotification.data.newMessageCount + 1;
options.body = `You have ${messageCount} new messages from ${userName}.`;
options.data = {
userName: userName,
newMessageCount: messageCount
};
notificationTitle = `New Messages from ${userName}`;
// 記得關閉舊的通知
currentNotification.close();
} else {
options.body = `"${userMessage}"`;
options.data = {
userName: userName,
newMessageCount: 1
};
notificationTitle = `New Message from ${userName}`;
}
return registration.showNotification(
notificationTitle,
options
);
});
複製代碼
咱們會累加當前展現的通知的信息數,同時依據這個數據來設置通知的主題和內容信息。若是當前沒有展現通知,咱們則會展現一個新的通知,其數據中 newMessageCount
的值爲 1。
那麼第一條信息的通知會是如下這樣:
第二條通知會以這樣的方式覆蓋已有的通知:
這種方法的好處是,若是你的用戶目擊了通知一個接着一個的出現,相比於只用最新的信息替代當前的通知內容,消息看起來則更緊密相連。
我一直強調的是,你必須在收到推送信息時展現通知,這個規則在大多數狀況下是正確的。只有在一種場景下你不須要展現通知, 那就是用戶已經打開了你的站點,而且站點已是呼起的狀態。
在你的推送事件中,你能夠經過檢測目標窗口是否已被打開並呼起,來決定是否須要展現通知。
得到瀏覽器全部窗口、查詢當前已呼起窗口的代碼能夠參考以下:
function isClientFocused() {
return clients.matchAll({
type: 'window',
includeUncontrolled: true
})
.then((windowClients) => {
let clientIsFocused = false;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.focused) {
clientIsFocused = true;
break;
}
}
return clientIsFocused;
});
}
複製代碼
咱們通常使用 clients.matchAll()
來得到當前瀏覽器下全部窗口對象, 而後遍歷其結果去檢查 focused
參數。
在推送事件內,咱們會使用以下方法來決定是否展現通知:
const promiseChain = isClientFocused()
.then((clientIsFocused) => {
if (clientIsFocused) {
console.log('Don\'t need to show a notification.');
return;
}
// 窗口並無被呼起,咱們須要展現通知
return self.registration.showNotification('Had to show a notification.');
});
event.waitUntil(promiseChain);
複製代碼
咱們已知,能夠在用戶正在瀏覽咱們站點的時候不進行通知。可是,若是你仍然想讓用戶知道這個推送事件已經發生,但又以爲進行通知太過強硬,應該如何處理?
其中一個方法就是利用 service worker 給頁面發送消息,這種狀況下頁面可以給用戶展現通知或更新,以此讓用戶知曉到這個推送事件的發生。固然,只有當用戶對於輕量級通知感到更友好時,這種作法纔有用。
若是咱們接收到了一個推送,而且檢測到了咱們的 APP 已經被打開了,那麼咱們就能夠"發送消息"給每一個打開的頁面,就像如下這樣:
const promiseChain = isClientFocused()
.then((clientIsFocused) => {
if (clientIsFocused) {
windowClients.forEach((windowClient) => {
windowClient.postMessage({
message: 'Received a push message.',
time: new Date().toString()
});
});
} else {
return self.registration.showNotification('No focused windows', {
body: 'Had to show a notification instead of messaging each page.'
});
}
});
event.waitUntil(promiseChain);
複製代碼
在每一個頁面中,咱們經過監聽消息(message)事件來接收消息:
navigator.serviceWorker.addEventListener('message', function(event) {
console.log('Received a message from service worker: ', event.data);
});
複製代碼
在這個消息監聽器中,你能夠作任何你想作的事,例如展現自定義的視圖,或者徹底忽略這個消息。
值得注意的是,若是你沒有在你的網頁中定義消息監聽器,那麼 service worker 推送的消息將不會作任何事。
有一個場景可能超出了這本書的範疇,可是依然值得探討,那就是緩存你但願用戶點擊通知後訪問的網頁,以此來提高web應用的總體用戶體驗。
這就須要設置你的 service worker 來處理這些 fetch
事件,但若是你監聽了 fetch
事件,請確保在展現你的通知以前,經過緩存你須要的頁面和資源來充分利用它在 push
事件中的優點。
想要了解更多緩存相關信息,請參考服務工做線程:簡介