单开网页应用利器 - BroadcastChannel
前言
前段时间在做一个基于
psd
模板生成图片的应用,其中重要的功能就是打开编辑器页面来设计出图。但是有个问题,每当我点击一个模板,就会新开一个浏览器页签。现代浏览器是以空间换时间的运行思路来提高效率,这就导致了内存开销会越来越大,也曾想过postmessage来解决这个问题,但是呢postmessage
是跨域广播,说白了,我post
的消息任意页签都能listen
到,不友好。最近刷抖音时,看到了一个前端教学视频,其中就讲到了网页音乐单开的实现方式,核心原理:**BroadcastChannel
**。
技术原理
BroadcastChannel
接口代理了一个命名频道,可以让指定 origin 下的任意 browsing context 来订阅它。它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间相互通信。通过触发一个 message
事件,消息可以广播到所有监听了该频道的 BroadcastChannel
对象。
创建或者加入频道
客户端通过构造函数,传入频道名称即可创建或加入频道。如果当前不存在此命名的频道,就会初始化并创建。
// 创建或加入频道
const channel = new BroadcastChannel('editor_channel');
发送消息
基于刚才创建或加入的频道实例,调用 postMessage
方法发送消息。可以使用 BroadcastChannel.postMessage()
发送一条任意 Object
类型的消息,给所有同源下监听了该频道的所有浏览器上下文。消息以 message
事件的形式发送给每一个绑定到该频道的广播频道。
channel.postMessage(message: any);
接受消息
通过监听 message
事件即可接收到同频道发送的任意消息。
channel.addEventListener('message', ({data: any}) => {console.log(data);
})
// 或者
channel.onmessage = ({data: any}) => {console.log(data);
}
异常处理
通过监听 messageerror
事件即可捕获异常。
qh_channel.addEventListener('messageerror', e) => {console.error(e);
})
// 或者
qh_channel.onmessagerror = (e) => {console.error(e);
}
断开连接
调用 close()
方法即可断开对象和基础通道之间的链接。
qh_channel.close()
实现
原理搞清楚了,那么接下来就是实战了。 先上效果图:
图中使用了2个受控页面,目的是想验证多个页面能够被统一控制,而且咱也的确控制不了用户的某些操作。
封装 Channel
类
const editorList = [];
/*** @description: 页面通信类* @param {String} channelName channel名称* @param {String} page 实例化channel的页面名称* @param {Boolean} isEditor 是否是编辑器* @param {String} editorName 编辑器页面名称* @param {Function} onmessage 接收到消息的回调* @return {Channel} channel实例*/
export default class Channel {constructor({ channelName, page, isEditor = false, editorName, onmessage }) {if (!page) throw new Error('page is required');if (!isEditor && !editorName) throw new Error('editorName is required');this.__uuid__ = Math.random().toString(36).substr(2);this.isEditor = isEditor; // 是否是编辑器this.editorName = editorName; // 编辑器页面名称this.page = page; // 实例化channel的页面名称this.name = channelName ?? 'qh_channel'; // channel名称this.channel = new BroadcastChannel(this.name);this.addEvent(onmessage);this.load(); // 告诉其他页面,我初始化完了}addEvent(onmessage) {this.channel.onmessage = ({ data: { type, page, data, uuid } }) => {if (!this.isEditor) {if (page === this.editorName) {// 如果是编辑器页面发送的消息this.updateEditor(type, uuid);}} else if (type === 'load' && page !== this.page) {// 其他页面加载时,告诉知我已经存在了this.load();}if (onmessage) {onmessage({ type, page, data });}};}// 如果用户手动打开了多个编辑器,需要更新编辑器列表updateEditor(type, uuid) {const index = editorList.indexOf(uuid);if (type === 'load') {if (index === -1) {editorList.push(uuid);}} else if (type === 'unload') {if (index !== -1) {editorList.splice(index, 1);}}}postMessage(data) {if (!!editorList.length || this.isEditor) {const newData = { page: this.page, uuid: this.__uuid__, ...JSON.parse(JSON.stringify(data)) };this.channel.postMessage(newData);return true;}return false;}load() {this.channel.postMessage({ type: 'load', uuid: this.__uuid__, page: this.page });}unload() {this.channel.postMessage({ type: 'unload', uuid: this.__uuid__, page: this.page });this.channel.onmessage = null;this.channel.close();}close() {}
}
主控页面逻辑
在主控页面引入并实例化 Channel
类。其中 page
和 editorName
必须填写。
import Channel from '@/utils/channel';const editorChannel = new Channel({page: 'template_index',editorName: 'editor_index',onmessage: ({ type, page, data }) => {if (type === 'confirm' && page === 'editor_index') {Modal.confirm({title: '警告',icon: createVNode(ExclamationCircleOutlined),content: '模板还未保存,确认替换?',onOk() {editorChannel.postMessage({ type: 'force_data', data });},});}},
});
window.onunload = () => {editorChannel.unload();
};
onUnmounted(() => {editorChannel.unload();
});// 点击事件
const itemClick = (node) => {if (!editorChannel.postMessage({ type: 'data', data: node })) {const rt = router.resolve(`/editor/${node.id}`);safeOpen(rt.href);}
};
前边提到了 safeOpen
,实现方案也很简单,具体为什么要 safeOpen
,请自行 google
。
window.open(url, target, 'noreferrer,noopener');
受控页面逻辑
受控页面同样引入并实例化 Channel
类。page
必须填写并且要标记 isEditor: true
,明确告知这是编辑器页面,这样 Channel
类就知道在 onmessage
时知道如何处理逻辑了。
import Channel from '@/utils/channel';const editorChannel = new Channel({page: 'editor_index',isEditor: true,onmessage: ({ type, data }) => {if (type === 'data') {if (dirty) {// 有修改,在主控页面弹窗提醒editorChannel.postMessage({ type: 'confirm', data });} else {// 无修改window.location.href = `/editor/${data.id}`;}} else if (type === 'force_data') {// 强制更新数据window.location.href = `/editor/${data.id}`;}},
});
window.onunload = () => {editorChannel.unload();
};
onUnmounted(() => {editorChannel.unload();
});
其实呢代码量也不多,核心就是 Channel
类,记录编辑器广播通信的 __uuid__
(这里之所以使用数组记录是因为用户的确可以通过 url
地址强制多开,那咱也没办法,只能一并记录更新),当主控页面想要打开新的编辑器时,优先调用 postMessage
方法,根据其返回结果判断编辑器是否存在,如果存在就触发受控页面更新路由并加载,如果不存在就打开新页签。
总结
BroadcastChannel
是一个非常简单的 API
,内部包含了跨上下文同源通信的接口。它没有定义消息传输协议,故不同上下文中的不同文档需要自己实现。目前来看兼容性方面也基本没有问题。
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。
相关文章:

单开网页应用利器 - BroadcastChannel
前言 前段时间在做一个基于 psd 模板生成图片的应用,其中重要的功能就是打开编辑器页面来设计出图。但是有个问题,每当我点击一个模板,就会新开一个浏览器页签。现代浏览器是以空间换时间的运行思路来提高效率,这就导致了内存开销…...

OpenCv更改颜色空间以及图像阈值
本文主要讲解以下几个方面: 如何将图片从一个颜色空间转换到另一个,例如 BGR 到 Gray,BGR 到 HSV 等。简单阈值法另外,我们会创建一个从图片中提取彩色对象的应用。 1.改变颜色空间 cv.cvtColor(img, flag) 参数flag表示颜色空间转换的方…...
(邱维声)高等代数课程笔记:基,维数与坐标
3.5 基,维数与坐标 \quad 本节,继续研究线性空间的结构。一般地,设 V V V 是数域 K K K 上的一个线性空间。 \quad 首先,我们先将“线性相关”与“线性无关”的概念由“有限”向“无限”推广。 对比其它高等代数教程,…...

Spring Security + Jwt 集成实现登录
文章目录 前言Maven 相关依赖配置文件自定义springsecurity相关认证流程继承WebSecurityConfigurerAdapter继承AbstractAuthenticationToken继承AbstractAuthenticationProcessingFilter实现AuthenticationProvider实现UserDetailsService实现AccessDeniedHandler实现Authentic…...

yolov5 用自己的数据集进行训练
在训练之前先要按照一定目录格式准备数据: VOC标签格式转yolo格式并划分训练集和测试集_爱钓鱼的歪猴的博客-CSDN博客 目录 1、修改数据配置文件 2、修改模型配置文件 3、训练 1、修改数据配置文件 coco.yaml 拷贝data/scripts/coco.yaml文件, pa…...

1951-2023最新中国基础地理信息,包括水系、行政区、DEM高程、气象站经纬位置、土地利用,这些数据获取方法介绍
水系: 流域内所有河流、湖泊等各种水体组成的水网系统,称作水系。其中,水流最终流入海洋的称作外流水系,如太平洋水系、北冰洋水系;水流最终流入内陆湖泊或消失于荒漠之中的,称作内流水系。 [1] 流域面积的…...
CAD处理控件Aspose.CAD功能演示:在 C#中以编程方式搜索 DWG 图形文件中的文本
Aspose.CAD 是一个独立的类库,以加强 Java应用程序处理和渲染CAD图纸,而不需要AutoCAD或任何其他渲染工作流程。该CAD类库允许将DWG, DWT, DWF, DWFX, IFC, PLT, DGN, OB…...

实验二十、压控电压源二阶 LPF 幅频特性的研究
一、题目 研究压控电压源二阶低通滤波电路品质因数 Q Q Q 对频率特性的影响。 二、仿真电路 电路如图1所示。集成运放采用 LM324AJ,其电源电压为 15V。 图 1 压控电压源二阶低通滤波电路幅频特性的测试 图1\,\,压控电压源二阶低通滤波电路幅频特性的测试 图1压控…...

类和对象【C++】【中篇】
目录 一、类的6个默认成员函数 1、构造函数 2、析构函数 3、拷贝构造函数 4、赋值重载函数 二、赋值运算符重载 一、类的6个默认成员函数 注意:默认成员函数不能在类外面定义成全局函数。因为类里没有的话会自动生成,就会产生冲突。 1、构造函数…...

2.SpringBoot运维实用篇
SpringBoot运维实用篇 基础篇发布以后,看到了很多小伙伴在网上的留言,也帮助超过100位小伙伴解决了一些遇到的问题,并且已经发现了部分问题具有典型性,预计将有些问题在后面篇章的合适位置添加到本套课程中,作为解…...
【c++】浅讲引用
【c】浅讲引用 前言引用定义作用做输出型参数引用作返回值总结 关于引用的权限 结尾 前言 博主开始细学c和linux了 这次就带来浅学了的引用。 引用 定义 引用不是在内存中开辟一个新空间的新变量 类似于给变量取别名,和取别名的对象在空间中公用一个对象 例&#…...
CSS布局基础(文字[行内<块>]与行内[块]垂直对齐方式 文字溢出显示省略号)
文字[行内<块>]与行内[块]垂直对齐方式 文字[行内<块>]与行内[块]垂直对齐方式概述图片底部空隙问题 文字溢出显示省略号单行文字多行文字 文字[行内<块>]与行内[块]垂直对齐方式 概述 vertical-align: top | middle| bottom | baseline(默认) | sub | sup…...

AI自动写文章_免费在线原创文章生成器
自动写文章生成器 自动写文章生成器是一种利用人工智能和自然语言处理技术,帮助用户快速生成文章的工具。该软件可以根据用户的需求和选择,自动生成符合要求的文章,无需手动编写和修改。 自动写文章生成器的主要功能包括以下几个方面&#…...

Java阶段二Day15
Java阶段二Day15 文章目录 Java阶段二Day15复习前日知识点对象数据类型注入数组类型注入集合类型的注入p命名空间引入外部属性文件 基于XML管理beanbean的作用域bean的生命周期代码演示生命周期后置处理器处理展示基于XML的自动装配 基于注解管理bean开启组件扫描使用注解定义B…...

从月薪3000到月薪20000,自动化测试应该这样学...
绝大多数测试工程师都是从功能测试做起的,工作忙忙碌碌,每天在各种业务需求学习和点点中度过,过了好多年发现自己还只是一个功能测试工程师。 随着移动互联网的发展,从业人员能力的整体进步,软件测试需要具备的能力要…...
Python魔法方法 单例模式
前言 本文介绍一下python中常用的魔法方法以及面向对象中非常重要的单例模式。 魔法方法 python中一切皆对象,因为python是面向对象的编程语言。python给类和对象提供了大量的内置方法,这些内置方法也称魔法方法。这些魔法方法总是在某种条件下自动触…...

计算机网络基础知识(三)—— 什么是OSI七层模型?
文章目录 00 | 🛸发展史🛸01 | 🛸OSI七层参考模型🛸02 | 🛸OSI七层参考模型的信息流向🛸 OSI七层模型是Open Systems Interconnection Reference Model的缩写,是由国际标准化组织(IS…...
Python(符号计算常微分方程)谐振子牛顿运动方程
牛顿运动方程 牛顿运动方程可以写成以下形式 F d p d t m d v d t m d 2 r d t 2 \mathbf{F}\frac{d \mathbf{p}}{d t}m \frac{d \mathbf{v}}{d t}m \frac{d^2 \mathbf{r}}{d t^2} Fdtdpmdtdvmdt2d2r 恒力问题 具有恒定力的问题意味着恒定的加速度。 典型的例子是…...

OpenCL编程指南-1.2OpenCL基本概念
OpenCL概念基础 面向异构平台的应用都必须完成以下步骤: 1)发现构成异构系统的组件。 2)探查这些组件的特征,使软件能够适应不同硬件单元的特定特性。 3)创建将在平台上运行的指令块(内核)。 4)…...

使用 ChatGPT 辅助学习——为自己找一个老师
我们每个人都有许多标签,例如高中生、成绩中等、文科,根据这些标签我和其他拥有相同标签的人分配了相同的教程、班级和老师,这可以带来效率上的提升,因为同一份教程、老师就可以服务几十上百人,而无须为每个人定制&…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...