包阅导读总结
1. 浏览器通知、Notification API、WebSocket、需求调研、限制与解决
2. 本文围绕网页实现类似 QQ 消息提示的需求展开,介绍了使用 Notification API 实现浏览器通知的方法,包括其基本用法、实例方法、事件等,同时指出了其限制,并给出了部分问题的解决方式,还探讨了浏览器未打开也能通知的技术原理。
3.
– 需求背景
– 产品经理希望网页能像 QQ 一样在有新消息时进行提示,即使页面被遮挡或收起。
– 技术方案
– 使用 WebSocket 监听后端消息。
– 选择 Notification API 发出通知。
– 讲解其基本用法、实例方法和可注册事件。
– 限制与解决
– 包括 HTTPS 要求、浏览器和系统通知权限、免打扰模式、浏览器差异、兼容性等限制。
– 给出如通知手动关闭、点击无法回到页面等问题的解决方式。
– 扩展知识
– 介绍浏览器未打开也能发送通知的原理,包括服务工作线程、推送通知、Web 推送 API 等。
思维导图:
文章地址:https://juejin.cn/post/7402781955077095474
文章来源:juejin.cn
作者:李仲轩
发布时间:2024/8/14 8:37
语言:中文
总字数:4281字
预计阅读时间:18分钟
评分:91分
标签:网页通知,Notification API,前端开发,浏览器API,用户体验
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
本文用于记录浏览器发出通知的知识点,相关API为Notification
效果
需求
很平常的一个工作日,「产品经理」给我发了个消息:
产品:你能让网页也像QQ一样,来了消息能进行提示吗?我说的不是网页内的弹出消息
我:刚搞完上个需求,别搞嗷,你小子
产品是一个比较随和的人,所以上面的对话比较随意;具体的聊天内容,我这里就省略了,跟他对话之后,他的大致需求如下:
- 页面中展示预警内容
- 检测到预警情况会通知用户且更新页面内容
- 用户不会一直将页面展示在最前面,所以需要做到页面即使被遮挡、收起,用户也能收到提示,达到及时提示的目的
解析
把功能点解析一下,得出了以下内容:
- 需要使用
WebSocket
来监听后端消息,收到消息之后进行通知用户和更新页面内容 - 寻找一种方式来发出通知
其中的难度在于第二点,找哪种方式来发出通知?我的第一反应其实有两个选项:
electron
的主进程是有办法来处理发出通知的操作的,只是那时候我在团队内部引入electron
的技术点还属于测试阶段,完整Demo没有走通,卡在了最后的发布更新这一阶段。所以如果当时采用这个方案,后续的处理,不稳定也不可控。
基于以上考虑,我最终把目光放到了BOM API
上面,我想起了看过的红宝书
中,有一个关于浏览器通知的知识点,可能可以处理这个需求。
调研(展示效果基于windows的谷歌浏览器)
在查看了红宝书
内容之后,得到了一个BOM API
:Notification
,这个API
可以让页面发出通知,基本用法如下:
function notifyMe() { if (!("Notification" in window)) { alert("当前浏览器不支持桌面通知"); } else if (Notification.permission === "granted") { const notification = new Notification("你好!"); } else if (Notification.permission !== "denied") { Notification.requestPermission().then((permission) => { if (permission === "granted") { const notification = new Notification("你好!"); } }); } }
这个例子中涉及三个东西:
Notification.permission
,当前通知的权限,有granted
、denied
、default
三个值,分别表示允许、拒绝、默认Notification.requestPermission
,请求当前页面的通知权限,调用这个方法,浏览器左上角会出现询问窗口,表示是否允许该页面发出通知new Notification("你好!")
,发出通知的简易代码。
如果你出于好奇,在本地服务执行了上面的代码,那么你的右下角就会有这样一个东西,并且隔一会儿就消失:
Notification
接下来讲一下这个API
如何使用:
- 构造函数
new Notification(title)new Notification(title, options)
在这里的配置选项中,我只列举了四个常用属性,其余属性,可以自行到MDN查看;使用以上属性可以构建一个比较丰富的通知:
new Notification('这是标题', { body: '这是正文', icon: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', requireInteraction: true, image: 'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png',})
- 实例方法每一个创建的通知实例都有一个
close
方法,用于关闭或移除先前显示的通知。
const notif = new Notification('这是标题');setTimeout(() => { notif.close();}, 500);
- 事件
可注册的事件有四类
- click:当用户点击通知时触发
- close:当用户关闭通知时触发
- error:当通知发生错误时触发
- show:当通知显示时触发
const notif = new Notification('这是标题');notif.onclick = () => { console.log('点击了通知');};notif.onclose = () => { console.log('关闭了通知');};notif.onerror = () => { console.log('通知出错');};notif.onshow = () => { console.log('显示了通知');};
基础Demo
ok,走到这里,通知或者说消息的问题已经有眉目了;那么就到了处理业务逻辑的时候了。
先来理一下Demo
中需要实现的逻辑(Demo
页面使用React
,WebSocket
使用express
、ws
实现):
- 构建一个
WebSocket
,接收后端消息,模拟预警情况:这里我用express
和ws
来模拟 - 处理接收到预警情况之后的发送通知逻辑和更新页面内容逻辑:发送通知使用
Notification API
,更新页面内容用收到后端消息,新增一条表格数据来模拟
const express = require('express');const WebSocket = require('ws');const WebSocketServer = WebSocket.WebSocketServer;const app = express();app.get('/', (req, res) => { res.send({ ok: 1, });});app.listen(3000, () => { console.log('express start');});const wss = new WebSocketServer({port: 8080});wss.on('connection', function connection(ws) { ws.on('error', console.error); ws.on('message', function message(data) { console.log('received: %s', data); }); setInterval(() => { ws.send('something', {binary: false}); }, 4000);});
const notificationSet = () => { if (!('Notification' in window)) { setVisible(true); } else if (Notification.permission === 'granted') { initNotifyPermission(); } else if (Notification.permission === 'default') { Notification.requestPermission().then((permission) => { if (permission === 'granted') { initNotifyPermission(); } else { messageApi.open({ type: 'error', content: '您禁用了该页面发出通知,这将导致您收不到后续更新提示,请在浏览器设置中放开该页面通知权限', duration: 10, }); } }); } }; const webSocketSet = () => { const socket = new WebSocket('ws://localhost:8080'); socket.addEventListener('open', function () { socket.send('Hello Server!'); }); socket.addEventListener('message', function (event) { setTableData((data) => [ ...data, { key: Math.random().toString(36).slice(-6), name: 'Joe Black', age: 32, address: 'Sydney No. 1 Lake Park', tags: ['cool', 'teacher'], }, ]); new Notification('数据已更新,请立即查看', { body: '具体信息。。。', icon: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', requireInteraction: true, image: 'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png', }); });}; useEffect(() => { notificationSet(); webSocketSet(); }, []);
限制
如果你看到这里还没有选择退出,表明你可能真的要用这个知识点,也可能你跟我一样也是在做技术调研,坏消息就是这个API
有几个限制点,你需要考量一下:
-
HTTPS:此项功能仅在一些支持的浏览器的安全上下文(HTTPS)中可用;也就是说页面访问的地址需要是
https
的;在本地调试时,要注意以下情况:Local: http://localhost:8080/
可以使用这个API
Network: http://xxx.xx.xx.xx:8080/
不能使用
-
浏览器通知权限:通知能否显示,受浏览器对该页面的权限控制,用户点击拒绝之后,只有提示用户去浏览器设置中,将该页面通知权限打开才能正常使用
-
系统通知权限:通知能否显示,也受系统对浏览器的权限控制;若是浏览器被系统禁止发出通知,即使网页允许发送,最终也看不到通知,也需要用户去系统设置中打开对浏览器的通知权限。
-
免打扰模式:某些系统有免打扰模式,如果这个模式被开启,那么也是接受不到通知的
-
浏览器差异:各个浏览器(Chrome、Safari、Edge等)对通知的外观、逻辑等实现有差异,这些差异是否会影响你的具体需求,需要自行测试之后斟酌
-
兼容性:此处是浏览器兼容情况,请查阅
瑕疵
上面的基础Demo
已经能实现接收后端消息后更新页面并发出通知,但是如果你运行了上面的代码,会发现还存在一些问题:
-
由于设置的是通知不自动关闭,发出多个通知之后,需要手动关闭,很麻烦
解决方式:发出的通知收集到一个数组里面,统一管理。
-
点击通知,不能回到页面中
解决方式:给通知的点击事件添加如下代码:
notif.onclick = () => { window.focus();};
-
页面可见的时候不需要发出通知,不可见才发出通知
解决方式:利用visibilityState属性发出通知之前,判断页面可见性:
if (document.visibilityState !== 'visible') { }
-
页面可见时,关闭之前的通知
解决方式:利用visibilitychange事件监听页面可见性变化
document.addEventListener('visibilitychange', () => { });
-
没有通知音效,提醒效果不明显
解决方式:页面增加一个audio元素,设置为隐藏,手动控制播放
<audio src={warmTipsAudio} style={{ display: 'none' }} ref={warmTipsAudioRef}></audio>
warmTipsAudioRef.current.play();
完整Demo代码
我把完整的Demo
代码,放到这里了(需要科学上网),一共两个文件:
WebSocket
的文件(express
+ws
),可以在package.json
配置一个类似"ws": "node ./web/index.ts"
的命令来启动React
+antd
的页面
PS:音频文件没法提供,可以使用ttsmaker 文本转语音工具自己生成一个放到项目中去。
扩展
这里还有一个额外的知识,就是我发现某些网页能够实现:即使浏览器没有打开,也可以发送通知的功能,这个我去了解了一下,大致原理如下,有需要的朋友自行阅读:
「即使浏览器未打开,某些网页也能发出桌面通知」,通常是通过浏览器推送通知(Browser Push Notifications)技术实现的。具体来说,这项功能依赖于以下几个关键技术组件:
-
服务工作线程(Service Workers):
服务工作线程是一种独立于网页主线程的脚本,能够在后台运行,即使浏览器未打开,或者用户没有打开特定网站。它们在浏览器中作为一个中间层,处理网络请求、推送通知、缓存等任务。服务工作线程可以在后台保持活跃,并在接收到来自服务器的推送消息时触发通知。 -
推送通知(Push Notifications):
推送通知是由服务工作线程处理的消息。这些通知通过推送服务从服务器发送到用户的浏览器,即使用户当前没有访问该网站。推送服务将消息传递到浏览器中的服务工作线程,后者可以根据消息的内容在桌面上显示通知。 -
Web 推送 API(Web Push API):
这是一个标准化的 API,允许网站在用户授权后注册一个服务工作线程,并通过推送服务发送通知。网站首先需要获得用户的许可,才能使用这项功能。一旦用户同意,网站可以在服务工作线程中注册推送事件,并配置通知的内容。 -
浏览器推送服务(Browser Push Service):
各大浏览器都有自己的推送服务,用于中转来自网站的推送消息。这些服务确保消息能够在合适的时间传递给浏览器,即使用户未打开浏览器。这些推送服务通常与操作系统通知系统集成,确保通知能在桌面上显示。
工作流程概述
- 用户授权:当用户访问支持推送通知的网站时,网站会请求通知权限。
- 服务工作线程注册:如果用户同意,浏览器会在后台注册一个服务工作线程。
- 推送通知:网站服务器通过浏览器的推送服务向用户的设备发送通知,即使浏览器未打开,服务工作线程也能接收到这些消息,并在桌面上显示通知。
这种机制使得网站能够在用户没有主动访问时,仍然与用户保持互动,提供及时的信息或提醒。
总结
本文从需求调研的角度出发,研究了浏览器发出通知的方式,讲解了Notification API
的使用和其局限性,实现了一个小Demo
,最后探索了浏览器不打开也能发出通知的实现方式。
那么就到这里了,下一篇文章再见!!!