Vue3利用Notification API實現(xiàn)瀏覽器通知功能
Notification API介紹。- 關(guān)閉瀏覽器后,點擊歷史通知仍能打開站點并跳轉(zhuǎn)目標(biāo)頁,如何實現(xiàn)。
1. 先說結(jié)論
只用 new Notification() 不夠。要覆蓋“舊通知點擊跳轉(zhuǎn)”,必須:
- 發(fā)送階段:優(yōu)先
ServiceWorkerRegistration.showNotification() - 點擊階段:在
public/notification-sw.js監(jiān)聽notificationclick - 跳轉(zhuǎn)策略:先找已有窗口并
focus(),沒有再openWindow()
一句話:把點擊處理從頁面 JS 移到 Service Worker。
2. Notification API 參數(shù)
2.1new Notification(title, options)的核心參數(shù)
title:通知標(biāo)題(必填)body:正文內(nèi)容icon:大圖標(biāo)(建議 192x192 或 256x256)badge:小徽標(biāo)(Android 常見,建議單色清晰圖)tag:通知分組標(biāo)識;相同tag會覆蓋舊通知data:自定義數(shù)據(jù)載荷(本方案用來傳url)requireInteraction:true表示通知不自動關(guān)閉(瀏覽器行為可能有差異)silent:是否靜音(不同瀏覽器支持度不同)
示例(占位鏈接):
new Notification('系統(tǒng)提醒', {
body: '您有一條待處理消息',
icon: 'https://example.com/assets/notify-icon.png',
badge: 'https://example.com/assets/notify-badge.png',
tag: 'todo-1001',
requireInteraction: true,
data: {
url: 'https://example.com/app/todo?id=1001'
}
})
2.2 常用事件
notification.onclick:頁面存活時可用notification.onclose:通知關(guān)閉回調(diào)notification.onerror:創(chuàng)建或展示失敗回調(diào)
頁面被關(guān)閉后,onclick 不可靠,所以才需要 SW 的 notificationclick。
2.3 權(quán)限相關(guān) API
Notification.permission:default/granted/deniedNotification.requestPermission():請求授權(quán)(需要用戶手勢觸發(fā)更穩(wěn))
3. 項目落地實現(xiàn)(3 步)
3.1 注冊通知 Service Worker
文件:src/main.ts
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/notification-sw.js').catch((error: unknown) => {
console.warn('[NotificationSW] register failed:', error)
})
})
}
作用:讓瀏覽器知道通知點擊事件由 public/notification-sw.js 接管。
3.2 統(tǒng)一封裝通知發(fā)送(優(yōu)先 SW,失敗降級)
文件:src/composables/useBrowserNotification.ts
項目實現(xiàn)的關(guān)鍵點:
- 權(quán)限不是
granted直接攔截 - 先拿 SW registration,再
showNotification - 通過
data.url傳跳轉(zhuǎn)目標(biāo) - SW 發(fā)送失敗再降級到
new Notification
核心片段:
const notificationOptions: NotificationOptions = {
body: options.body,
icon: notificationIcon,
badge: notificationBadgeIcon,
requireInteraction: options.requireInteraction ?? false,
tag: options.tag,
data: {
url: options.clickUrl ?? ''
}
}
await registration.showNotification(options.title, notificationOptions)
3.3 在 SW 中處理點擊(關(guān)鍵中的關(guān)鍵)
文件:public/notification-sw.js
self.addEventListener('notificationclick', (event) => {
event.notification.close()
const targetUrl = String(event.notification?.data?.url || '').trim()
if (!targetUrl) return
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
for (const client of clients) {
if (client.url === targetUrl && 'focus' in client) {
return client.focus()
}
}
return self.clients.openWindow(targetUrl)
})
)
})
這段邏輯保證:
- 有現(xiàn)成頁面:聚焦現(xiàn)有頁
- 沒有頁面:新開頁并跳轉(zhuǎn)
- 瀏覽器關(guān)閉后點擊歷史通知:依然可回站
4. 為什么“舊通知點擊可跳轉(zhuǎn)”
點擊系統(tǒng)通知時,事件發(fā)給的是 Service Worker,不依賴頁面是否還活著。
因此即使用戶關(guān)了頁面,只要 SW 生效,仍可完成 focus/openWindow。
5. 注意
- 使用 HTTPS 或
localhost,否則 Notification/SW 都可能不可用 clickUrl建議絕對地址,避免路由 base 造成解析偏差tag要按業(yè)務(wù)維度設(shè)計(例如module-item-123),防止通知刷屏- 對
denied狀態(tài)給出 UI 引導(dǎo),提示去瀏覽器設(shè)置中手動開啟 requireInteraction行為在不同瀏覽器有差異,需實機驗證
useBrowserNotification全量源碼
import { computed, ref, type ComputedRef, type Ref } from 'vue'
import notificationBadgeIcon from '@/assets/images/notify-badge-placeholder.png'
import notificationIcon from '@/assets/images/notify-icon-placeholder.png'
type NotifyPermission = NotificationPermission | 'unsupported'
interface SendBrowserNotificationOptions {
title: string
body: string
clickUrl?: string
tag?: string
requireInteraction?: boolean
autoCloseMs?: number
onClick?: () => void
}
interface UseBrowserNotification {
message: Ref<string>
isSupported: Ref<boolean>
permissionState: Ref<NotifyPermission>
supportText: ComputedRef<string>
permissionLabel: ComputedRef<string>
requestNotifyPermission: () => Promise<void>
sendBrowserNotification: (options: SendBrowserNotificationOptions) => void
}
export const useBrowserNotification = (): UseBrowserNotification => {
const message = ref('等待操作')
const isSupported = ref<boolean>(typeof window !== 'undefined' && 'Notification' in window)
const permissionState = ref<NotifyPermission>(isSupported.value ? Notification.permission : 'unsupported')
const supportText = computed(() => (isSupported.value ? '是' : '否'))
const permissionLabel = computed(() => {
if (permissionState.value === 'unsupported') return '瀏覽器不支持'
if (permissionState.value === 'granted') return '已授權(quán)'
if (permissionState.value === 'denied') return '已拒絕'
return '未授權(quán)(default)'
})
const updatePermissionState = (): void => {
permissionState.value = isSupported.value ? Notification.permission : 'unsupported'
}
const requestNotifyPermission = async (): Promise<void> => {
if (!isSupported.value) {
message.value = '當(dāng)前瀏覽器不支持 Notification API'
return
}
try {
const result = await Notification.requestPermission()
permissionState.value = result
message.value = `權(quán)限申請結(jié)果:${result}`
} catch (error) {
message.value = '申請通知權(quán)限失敗,請稍后重試'
console.error('Notification.requestPermission failed:', error)
}
}
const getServiceWorkerRegistration = async (): Promise<ServiceWorkerRegistration | null> => {
if (typeof window === 'undefined' || !('serviceWorker' in navigator)) return null
try {
return await navigator.serviceWorker.getRegistration()
} catch {
return null
}
}
const sendBrowserNotification = (options: SendBrowserNotificationOptions): void => {
if (!isSupported.value) {
message.value = '當(dāng)前瀏覽器不支持 Notification API'
return
}
updatePermissionState()
if (permissionState.value !== 'granted') {
message.value = '請先授權(quán)通知權(quán)限后再發(fā)送'
return
}
const autoCloseMs = options.autoCloseMs ?? 4000
;(async () => {
const registration = await getServiceWorkerRegistration()
if (registration) {
try {
const notificationOptions: NotificationOptions = {
body: options.body,
icon: notificationIcon,
badge: notificationBadgeIcon,
requireInteraction: options.requireInteraction ?? false,
tag: options.tag,
data: {
url: options.clickUrl ?? ''
}
}
await registration.showNotification(options.title, notificationOptions)
message.value = `通知已發(fā)送:${options.title}`
return
} catch (error) {
console.warn('ServiceWorker showNotification failed, fallback to page notification:', error)
}
}
try {
const notificationOptions: NotificationOptions = {
body: options.body,
icon: notificationIcon,
badge: notificationBadgeIcon,
requireInteraction: options.requireInteraction ?? false
}
// 不傳 tag 時允許系統(tǒng)通知疊加顯示;傳 tag 時按 tag 覆蓋同組通知
if (options.tag) {
notificationOptions.tag = options.tag
}
const notice = new Notification(options.title, notificationOptions)
const shouldAutoClose = !(options.requireInteraction ?? false)
const autoCloseTimer = shouldAutoClose
? window.setTimeout(() => {
notice.close()
}, autoCloseMs)
: null
notice.onclick = () => {
window.focus()
notice.close()
if (options.clickUrl) {
window.open(options.clickUrl, '_blank', 'noopener,noreferrer')
}
options.onClick?.()
message.value = '已點擊通知,窗口已嘗試聚焦'
}
notice.onclose = () => {
if (autoCloseTimer !== null) {
window.clearTimeout(autoCloseTimer)
}
}
notice.onerror = () => {
if (autoCloseTimer !== null) {
window.clearTimeout(autoCloseTimer)
}
message.value = '通知發(fā)送失敗,請檢查瀏覽器通知設(shè)置'
}
message.value = `通知已發(fā)送:${options.title}`
} catch (error) {
message.value = '創(chuàng)建通知失敗,請檢查瀏覽器設(shè)置'
console.error('Notification constructor failed:', error)
}
})().catch((error: unknown) => {
message.value = '創(chuàng)建通知失敗,請檢查瀏覽器設(shè)置'
console.error('sendBrowserNotification failed:', error)
})
}
return {
message,
isSupported,
permissionState,
supportText,
permissionLabel,
requestNotifyPermission,
sendBrowserNotification
}
}
public/notification-sw.js全量源碼
self.addEventListener('notificationclick', (event) => {
event.notification.close()
const targetUrl = String(event.notification?.data?.url || '').trim()
if (!targetUrl) return
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
for (const client of clients) {
if (client.url === targetUrl && 'focus' in client) {
return client.focus()
}
}
return self.clients.openWindow(targetUrl)
}),
)
})
以上就是Vue3利用Notification API實現(xiàn)瀏覽器通知功能的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Notification瀏覽器通知的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue輪播圖插件vue-awesome-swiper的使用代碼實例
本篇文章主要介紹了vue輪播圖插件vue-awesome-swiper的使用代碼實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼
這篇文章主要介紹了vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

