不同标签页或窗口间的 【主动推送消息机制】 方式有哪些?(不借助服务端)

关键词:不同页签信息主动推送

在不借助服务器端的帮助下,实现不同标签页或窗口间的主动推送消息机制。

作者备注:

这里要注意一下, 这里讨论的不是跨页签通信,而是跨页签主动推送信息 。如果仅仅是跨页签通信, 那么浏览器的本地存储都可以都可以使用了。 所以排除了本地存储类 API 的介绍

BroadcastChannel API:

作者备注

这个很有意思, 有一个文章, 国内某大佬复刻了《跨窗口量子纠缠粒子效果》就是用的 这个 API
juejin.cn/post/730705...

BroadcastChannel API 是一种在相同源的不同浏览器上下文之间实现简单高效通信的方法。这意味着它可以在同一网站的多个标签页或窗口之间发送消息。这是由 HTML5 规范引入的,用于改进 Web Workers 中的通信方法。

下面是如何使用 BroadcastChannel API 的基本指南及几个示例。

创建与发送消息

javascript 复制代码
// 在任何一个 tab 或 iframe 中创建一个广播频道
const channel = new BroadcastChannel("my-channel-name");

// 发送一个消息到频道
channel.postMessage("Hello from a tab!");

监听消息

javascript 复制代码
// 监听这个频道的消息
channel.addEventListener("message", function (event) {
  if (event.data === "Hello from a tab!") {
    console.log("Message received: ", event.data);
  }
});

实现频道消息通信

假设你有两个标签页,并且你想更新每个标签页来显示另一个标签页中发生的事情,比如用户数量计数器:

javascript 复制代码
// 在第一个标签页中
self.addEventListener("load", () => {
  const channel = new BroadcastChannel("visitor-channel");
  let visitorCount = 0;

  // 定时发送随机的用户活动消息
  setInterval(function () {
    visitorCount++;
    channel.postMessage(`Visitor count increased to: ${visitorCount}`);
  }, 5000);
});

// 在另一个标签页中
self.addEventListener("load", () => {
  const channel = new BroadcastChannel("visitor-channel");

  // 监听消息来更新用户数量
  channel.addEventListener("message", function (event) {
    if (event.data.startsWith("Visitor count")) {
      // 用接收到的用户数量更新显示
      updateVisitorCountDisplay(event.data);
    }
  });

  // 这个方法将设置标签页上的用户计数显示
  function updateVisitorCountDisplay(message) {
    // 这里写用于更新显示的代码
    console.log(message);
  }
});

在这个例子中,一个标签页通过定期发送新的消息来模拟用户活动的增加,这个消息在所有监听该频道的上下文中传递。另一个或多个标签页将监听这个频道来接收和响应这些更新。

注意事项:

  • 频道内的通信 仅在同源浏览器上下文(具有相同的协议、域名和端口号)之间有效,也就是说,不同的网站之间的通信是不被允许的,以保护每个网站的安全性。
  • 频道中的通信是 单向的,你可以通过频道向所有连接

Service Workers:

利用 Service Workers,各个标签页可以通过 clients.matchAll() 方法找到所有其他客户端(如打开的标签页),然后使用 postMessage 发送消息。

这个方法相比 BroadcastChannel 更加灵活,因为 Service Workers 可以通过 FocusNavigate 事件来控制页面的焦点和导航等。

ServiceWorkers 提供了在后台运行脚本的能力,这些脚本可以在网络受限或没有网络的情况下运行。当你用 ServiceWorkers 进行页面间的通信,你可以利用它们来推送消息到打开的 Clients(如浏览器标签页)。

要使用 ServiceWorkers 实现从不同 Tab 中主动推送信息,可以通过以下几个步骤:

1. 编写 ServiceWorker 文件

首先,创建名为 sw.js 的 ServiceWorker 文件。这个文件在你的网站目录下,会在用户访问网站时注册并激活。

javascript 复制代码
// sw.js

self.addEventListener("message", (event) => {
  if (event.data === "New message from another tab") {
    self.clients
      .matchAll({
        type: "window",
        includeUncontrolled: true,
      })
      .then((windowClients) => {
        windowClients.forEach((client) => {
          client.postMessage("New message for " + client.id);
        });
      });
  }
});

2. 在主页面注册 ServiceWorker

在主页面(index.html)通过 JavaScript 注册这个 ServiceWorker 文件。

javascript 复制代码
// index.html

if ("serviceWorker" in navigator) {
  navigator.serviceWorker
    .register("/sw.js")
    .then((registration) => {
      console.log("Service Worker registered with scope:", registration.scope);
    })
    .catch((error) => {
      console.log("Service Worker registration failed:", error);
    });
}

3. 监听 message 事件

在主页面使用 navigator.serviceWorker.controller 来检查是否已经有 ServiceWorker 主动控制。

javascript 复制代码
if (navigator.serviceWorker.controller) {
  // Post a message to the ServiceWorker
  navigator.serviceWorker.controller.postMessage("This is from main page");
}

4. 从其他 Tab 推送消息

在其他 Tab 上,一旦 ServiceWorker 被该页面控制后,可以通过同样的 postMessage 方法发送消息。

SharedWorker:

SharedWorker 提供了一种更传统的跨文档通信机制,在不同文档间共享状态和数据。你需要创建一个 SharedWorker 对象,并在所有的文档里监听来自该 worker 的消息。

简单场景的 SharedWorker 的使用步骤:

  1. 创建和连接:
javascript 复制代码
// 创建一个 SharedWorker,并指定要加载的脚本
var myWorker = new SharedWorker("worker.js");
// 开启端口通信
myWorker.port.start();
  1. 端口通信: 使用端口接收和发送消息
javascript 复制代码
// 发送数据给worker
myWorker.port.postMessage({ command: "start", data: [1, 2, 3] });

// 监听来自worker的消息
myWorker.port.onmessage = function (event) {
  if (event.data) {
    console.log("Result from worker:", event.data);
  }
};
  1. 实现 worker 逻辑:

worker.js 内,通过 onconnect 事件监听端口连接,并在使用 postMessage 发送数据的页面之间转发消息。

javascript 复制代码
// worker.js

// 自身的事件监听器
self.onconnect = function (event) {
  var port = event.ports[0];

  // 监听端口的消息
  port.onmessage = function (e) {
    if (e.data.command === "start") {
      var result = someHeavyComputation(e.data.data);
      port.postMessage({ result: result });
    }
  };
};

// 在这里执行一些开销较大的计算逻辑
function someHeavyComputation(data) {
  // 在这里进行计算...
  return data.reduce(function (previousValue, currentValue) {
    return previousValue + currentValue;
  }, 0);
}
  1. 通知其他页面更新:

当你希望基于上文提到的 SharedWorker 执行的计算结果通知其他所有的页面更新时,可以利用 SharedWorkerGlobalScope 中的 clients 对象。

javascript 复制代码
// 在 worker.js 中

self.addEventListener("message", (e) => {
  if (e.data === "Update all clients") {
    // 遍历所有客户端
    self.clients.matchAll().then((clients) => {
      clients.forEach((client) => {
        // 发送消息更新它们
        client.postMessage("Please update your state");
      });
    });
  }
});

使用 localStorage 的变更监听

虽然 localStorage 没有直接提供跨标签页推送机制,但是可以使用 window.addEventListener('storage', listener) 监听 storage 事件,实现不同标签页间的通信。

javascript 复制代码
// 标签页1修改了 localStorage
localStorage.setItem("someKey", "someValue");

// 其他标签页监听 storage 事件
window.addEventListener("storage", function (event) {
  if (event.storageArea === localStorage && event.key === "someKey") {
    console.log(event.newValue);
  }
});

使用 iframe 的 message 事件

如果排他性不是问题(所有标签页都属于同一客户端),可以使用 iframe 来传递消息,父窗口和 iframe 可以使用 DOM 中的 message 事件系统相互通信。

要使用 iframemessage 事件实现不同页签之间的通信,你需要两个关键项的配合:父页面和 iframe 页面之间的协调工作。这种通信非常灵活,因为你可以根据自己需要进行信息的发送和监听。

示例步骤:

1. 创建一个父页面

在父页面中,我们创建一个 iframe 并监听 message 事件。

html 复制代码
<!-- parent.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Parent Page</title>
  </head>
  <body>
    <iframe src="iframe.html" style="display:none;"></iframe>

    <script>
      // 监听 iframe 发送的 message 事件
      window.addEventListener("message", function (event) {
        if (event.origin !== "http://example.com") {
          // 确保消息源是可信的
          return;
        }
        if (event.data && event.data.greeting) {
          console.log("Message received from iframe:", event.data);
          // 如果iframe向父页面问好(向父页面发送了一条消息)
          // 假设我们还想再向iframe发送一些信息
          document.querySelector("iframe").contentWindow.postMessage(
            {
              response: "Hello iframe! This is the parent window speaking.",
            },
            "http://example.com"
          );
        }
      });
    </script>
  </body>
</html>

2. 创建一个 iframe 页面

iframe.html 页面中,我们需要发送消息到父页面并监听父页面的消息。

html 复制代码
<!-- iframe.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Iframe Page</title>
  </head>
  <body>
    <script>
      // 假设我们有一些需要发送到父页面的信息
      function sendMessageToParent() {
        parent.postMessage({ greeting: "Hello, I am the iframe!" }, "http://example.com");
      }

      // 当页面加载完成后,发送消息
      window.onload = function () {
        sendMessageToParent();
      };

      // 监听来自父页面的消息
      window.addEventListener("message", function (event) {
        if (event.origin !== "http://example.com") {
          // 反向验证消息源的可信度
          return;
        }
        if (event.data && event.data.response) {
          console.log("Message received from parent:", event.data);
          // 可根据消息实现特定的逻辑
        }
      });
    </script>
  </body>
</html>
相关推荐
码农爱java11 分钟前
设计模式--装饰器模式【结构型模式】
java·设计模式·面试·装饰器模式·原理·23 中设计模式
发呆的薇薇°29 分钟前
react里使用Day.js显示时间
前端·javascript·react.js
跑跑快跑33 分钟前
React vite + less
前端·react.js·less
web1368856587142 分钟前
ctfshow_web入门_命令执行_web29-web39
前端
GISer_Jing1 小时前
前端面试题合集(一)——HTML/CSS/Javascript/ES6
前端·javascript·html
清岚_lxn1 小时前
es6 字符串每隔几个中间插入一个逗号
前端·javascript·算法
胡西风_foxww1 小时前
【ES6复习笔记】Map(14)
前端·笔记·es6·map
星就前端叭1 小时前
【开源】一款基于SpringBoot的智慧小区物业管理系统
java·前端·spring boot·后端·开源
缘友一世1 小时前
将现有Web 网页封装为macOS应用
前端·macos·策略模式
上海运维Q先生1 小时前
面试题整理19----Metric的几种类型?分别是什么?
运维·服务器·面试