最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Vue3借助NotificationAPI实现浏览器通知功能
时间:2026-06-08 08:33:47 编辑:袖梨 来源:一聚教程网
Notification API介绍。- 关闭浏览器后,点击历史通知仍能打开站点并跳转目标页,如何实现。
1. 先说结论
只用 new Notification() 不够。要覆盖“旧通知点击跳转”,必须:

- 发送阶段:优先
ServiceWorkerRegistration.showNotification() - 点击阶段:在
public/notification-sw.js监听notificationclick - 跳转策略:先找已有窗口并
focus(),没有再openWindow()
一句话:把点击处理从页面 JS 移到 Service Worker。
2. Notification API 参数
2.1new Notification(title, options)的核心参数
title:通知标题(必填)body:正文内容icon:大图标(建议 192x192 或 256x256)badge:小徽标(Android 常见,建议单色清晰图)tag:通知分组标识;相同tag会覆盖旧通知data:自定义数据载荷(本方案用来传url)requireInteraction:true表示通知不自动关闭(浏览器行为可能有差异)silent:是否静音(不同浏览器支持度不同)
示例(占位链接):
new Notification('系统提醒', { 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:通知关闭回调notification.onerror:创建或展示失败回调
页面被关闭后,onclick 不可靠,所以才需要 SW 的 notificationclick。
2.3 权限相关 API
Notification.permission:default/granted/deniedNotification.requestPermission():请求授权(需要用户手势触发更稳)
3. 项目落地实现(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 统一封装通知发送(优先 SW,失败降级)
文件:src/composables/useBrowserNotification.ts
项目实现的关键点:
- 权限不是
granted直接拦截 - 先拿 SW registration,再
showNotification - 通过
data.url传跳转目标 - SW 发送失败再降级到
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 中处理点击(关键中的关键)
文件: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) }) )})这段逻辑保证:
- 有现成页面:聚焦现有页
- 没有页面:新开页并跳转
- 浏览器关闭后点击历史通知:依然可回站
4. 为什么“旧通知点击可跳转”
点击系统通知时,事件发给的是 Service Worker,不依赖页面是否还活着。
因此即使用户关了页面,只要 SW 生效,仍可完成 focus/openWindow。
5. 注意
- 使用 HTTPS 或
localhost,否则 Notification/SW 都可能不可用 clickUrl建议绝对地址,避免路由 base 造成解析偏差tag要按业务维度设计(例如module-item-123),防止通知刷屏- 对
denied状态给出 UI 引导,提示去浏览器设置中手动开启 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 '已授权' if (permissionState.value === 'denied') return '已拒绝' return '未授权(default)' }) const updatePermissionState = (): void => { permissionState.value = isSupported.value ? Notification.permission : 'unsupported' } const requestNotifyPermission = async (): Promise<void> => { if (!isSupported.value) { message.value = '当前浏览器不支持 Notification API' return } try { const result = await Notification.requestPermission() permissionState.value = result message.value = `权限申请结果:${result}` } catch (error) { message.value = '申请通知权限失败,请稍后重试' 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 = '当前浏览器不支持 Notification API' return } updatePermissionState() if (permissionState.value !== 'granted') { message.value = '请先授权通知权限后再发送' 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 = `通知已发送:${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 时允许系统通知叠加显示;传 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 = '通知发送失败,请检查浏览器通知设置' } message.value = `通知已发送:${options.title}` } catch (error) { message.value = '创建通知失败,请检查浏览器设置' console.error('Notification constructor failed:', error) } })().catch((error: unknown) => { message.value = '创建通知失败,请检查浏览器设置' 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实现浏览器通知功能的详细内容,更多关于Vue3 Notification浏览器通知的资料请关注本站其它相关文章!