单开网页应用利器 - BroadcastChannel

news2024/11/23 15:21:46

前言

前段时间在做一个基于 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()

实现

原理搞清楚了,那么接下来就是实战了。 先上效果图:

e39882d469d7df7c8d6c897b2843653f.gif

图中使用了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 类。其中 pageeditorName 必须填写。

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 ,内部包含了跨上下文同源通信的接口。它没有定义消息传输协议,故不同上下文中的不同文档需要自己实现。目前来看兼容性方面也基本没有问题。087af2f89703d90380bc08956c355e61.png

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

fd52afc6b5b4bd16b7c4617c658fa0f9.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/508692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单片机c51中断 — 中断键控流水灯

项目文件 文件 关于项目的内容知识点可以见专栏单片机原理及应用 的第五章,中断 在第4章的实例2中,按键检测是采用查询法进行的,其流程图如图所示 问题是这样的:由于查询法 -按键查询、标志位修改及彩灯循环几个环节是串联关系…

微信小程序从入门到精通

目录 前言一,初学小程序1.1 小程序概述1.2 基础配置1.2.1 注册开发账号1.2.2 获取AppID1.2.3 微信开发者工具1.2.4 修改代理模式 1.3 第一个小程序1.4 开发文档1.5 机型1.6 项目基本结构1.6.1 页面内部文件1.6.2 app.json1.6.3 project.config.json1.6.4 sitemap.js…

开关电源基础07:离线式开关电源变压器设计(1)

说在开头:关于第六届索尔维会议(2) 爱因斯坦一天都挺开心的,反正难题出给了玻尔,他还在自己的房间里拉起了小提琴,有人说爱因斯坦小提琴拉的跟锯木头一样,那也不至于那么夸张,但是水…

RK3568平台开发系列讲解(Linux内存篇)Linux内存管理框架

🚀返回专栏总目录 文章目录 一、内核态内存分配二、用户态内存分配三、内存篇章更新哪些内容沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们一起将整个内存管理的体系串起来。 对于内存的分配需求,可能来自内核态,也可能来自用户态。 一、内核态内存分配…

Spring Boot集成ShardingSphere实现读写分离 | Spring Cloud 43

一、读写分离 1.1 背景 面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操…

NetApp 利用适用于混合云的实时解决方案解决芯片设计方面的数据管理挑战

电子设计自动化 (EDA) 成本持续增加,而周期时间缩短。这些都为 EDA 设计带来了前所未有的挑战,对现代高性能工作流的需求变得从未如此巨大。 联想凌拓芯片设计行业存储解决方案及最佳实践 联想凌拓芯片行业数据存储与管理解决方案,针对EDA…

驱动设计的思想:面向对象/分层/分离(以LED操作为例)

1. 面向对象 字符设备驱动程序抽象出一个file_operations结构体; 对于LED,写的程序针对硬件部分抽象出led_operations结构体。 2. 分层 上下分层,之前写的LED驱动程序就分为2层: ① 上层实现硬件无关的操作,比如注册…

一文搞懂——MySQL索引事务JDBC

目录 一、索引 1.1 索引是什么? 1.2 怎样创建索引? 1.3 索引使用的数据结构是什么? 1.4 索引相关的概念 1.5 索引失效的原因 二、事务 2.1 事务是什么? 2.2 为什么要使用事务? 2.3 事务的使用 2.4 事务的特性…

黑马头条(学习笔记)

​ 目录 一. 项目概述 二、项目初始化 移动端 REM 适配: 关于 PostCSS 配置文件: Autoprefixer 插件的配置 : postcss-pxtorem 插件的配置: 关于字体图标: 配置路由: 封装请求模块: 三:登录注册&…

ChatGPT有话说:虚拟现实 VS 增强现实

以下内容均为ChatGPT根据用户引导和提示作出的阐述和说明。 一、引言 虚拟现实和增强现实是当前最受瞩目的创新技术。虚拟现实是指利用计算机生成的虚拟环境,用户可以通过佩戴VR头戴式显示器等设备完全沉浸在其中,感受到身临其境的感觉。而增强现实则是…

三分钟上手安全渗透系统Kali Linux

kali linux系统集成了常用的安全渗透工具,省去了安装工具的时间,做安全相关的工作是非常推荐使用的。 安装Kalii Linux 安装系统 一般使用虚拟机进行安装,Kali Linux基于Debian内核,虚拟机的操作系统选择Debian 7.x 64 选择系统…

【JAVA】用Java实现简易图书管理系统

【JAVA】用Java实现简易图书管理系统 1. 设计背景和系统功能和设计方法1.1设计背景1.2系统功能1.3设计方法 2. 设计思路3. 设计模块和代码实现3.1 Book类的实现3.2 BookList类的实现(书架)3.3 User类的实现(用户类)3.3.1 AdminUser(管理员类&…

家用洗地机好用吗?值得推荐的家用洗地机

谁说家务苦差事?现在有了洗地机,家庭清洁变得更加简单、快捷、干净,让您轻松应对家庭日常清洁的要求。洗地机采用先进的技术,自动感应地面脏污,智能调节出水量和吸力,不仅能够保持地面清洁,更能…

深入理解MapReduce:使用Java编写MapReduce程序【上进小菜猪】

📬📬我是上进小菜猪,沈工大软件工程专业,爱好敲代码,持续输出干货。 MapReduce是一种用于处理大规模数据集的并行编程模型。由于其高效性和可扩展性,MapReduce已成为许多大型互联网公司处理大数据的首选方…

隐语v0.8.2版本更新,首次发布TEEU

隐语v0.8.2版本更新🌟 应用层 机器学习: - MPC 纵向 LR (SSRegression)新增 Policy SGD 优化器和 Early Stopping 支持,减少调参成本,加快收敛速度; - WOE 分箱进行了若干优化,性…

HR SaaS市场竞争火热,肯耐珂萨缘何持续领跑? |爱分析调研

摘要: 中国人力资源数字化市场规模快速增长,各路厂商云集,呈现百花齐放的态势。作为人力资源管理一体化云解决方案的龙头服务商,肯耐珂萨坚定执行价值导向的差异化竞争策略,15年引领行业创新,依托行业领先方…

linux下的Qt打包常见原因分析和雷区,获取一键式打包脚本(能避免各种问题)

目录 一. 大致如下常见问题: (1)找不到程序所依赖的Qt库 version Qt_5 not found (required by (2)Could not Load the Qt platform plugin "xcb" in "" even though it was found &#xff0…

64/32位Linux系统的地址空间布局对比分析

Ubuntu从17.10开始不再官方支持32位(i386)架构(严格的说是从18.04开始的,因为17.10不支持32位的PC版,但是支持32位的SERVER版,但是偶数稳定版确实是从18.04开始的),只支持64位(amd64)架构,这是因为随着时间…

为什么ChatGPT用强化学习而非监督学习?

为什么ChatGPT非得用强化学习,而不直接用监督学习?原因不是那么显而易见。在上周发布的《John Schulman:通往TruthGPT之路》一文中,OpenAI联合创始人、ChatGPT主要负责人John Schulman分享了OpenAI在人类反馈的强化学习&#xff0…

去阿里面试,面试前20分钟突然要求候选人展示过去的工作方案,候选人拒绝后,竟被取消面试!...

离职时,你会把自己的工作成果拷贝下来留档吗? 一位网友说: 面试阿里,面试前20分钟,面试官突然要求他展示过去的工作成果,因为之前是用公司电脑,离职时把电脑交上去了,没有任何留档&a…