多窗口量子纠缠技术实现

1. 前言

最近看到下面这个,多窗口下实现量子纠缠的特效感觉很惊艳和有创意。

请添加图片描述

除 Three.js 的特效果部分,技术实现上还是很简单的。

2. 技术点分析

这里面核心两个技术点:

  1. 不同 Tab 窗口下的消息通讯。
  2. 窗口的位置获取。

2.1 Tab 窗口通讯

比较常用的有两种方式:

  1. Storage API 的 StorageEvent。
  2. BroadcastChannel API。
2.1.1 StorageEvent 实现

通过设置 localStorage/sessionStorage 的 setItem 可以在其他同源窗口下触发 StorageEvent 事件,实现广播效果:

[Exposed=Window]
interface StorageEvent : Event {
  constructor(DOMString type, optional StorageEventInit eventInitDict = {});

  readonly attribute DOMString? key;
  readonly attribute DOMString? oldValue;
  readonly attribute DOMString? newValue;
  readonly attribute USVString url;
  readonly attribute Storage? storageArea;

  undefined initStorageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional DOMString? key = null, optional DOMString? oldValue = null, optional DOMString? newValue = null, optional USVString url = "", optional Storage? storageArea = null);
};

dictionary StorageEventInit : EventInit {
  DOMString? key = null;
  DOMString? oldValue = null;
  DOMString? newValue = null;
  USVString url = "";
  Storage? storageArea = null;
};

演示:

<button id="a">我的位置</button>
<script>
  window.onstorage = (e) => {
    const { key, newValue, oldValue, url } = e;
    console.log(
      `key: ${key}, newValue: ${newValue}, oldValue: ${oldValue}, url: ${url}`,
      e
    );
  };
  a.addEventListener("click", () => {
    localStorage.setItem(
      "position",
      JSON.stringify({
        x: Math.random(),
        y: Math.random(),
      })
    );
  });
</script>

在这里插入图片描述

兼容性:

在这里插入图片描述

2.1.2 BroadcastChannel 实现

通过创建同名频道进行通讯。

[Exposed=(Window,Worker)]
interface BroadcastChannel : EventTarget {
  constructor(DOMString name);

  readonly attribute DOMString name;
  undefined postMessage(any message);
  undefined close();
  attribute EventHandler onmessage;
  attribute EventHandler onmessageerror;
};

演示:

<button id="a">我的位置</button>
<script>
  const channel = new BroadcastChannel("my_channel");
  channel.onmessage = (e) => {
    console.log("收到广播", e.data);
  };
  a.addEventListener("click", () => {
    console.log("发送广播");
    channel.postMessage({ x: 100, y: 200 });
  });
</script>

在这里插入图片描述

兼容性:

在这里插入图片描述

2.2 位置获取

通过获取浏览器窗口在屏幕的位置+内外宽高,就可以获取到绝对的位置。

在这里插入图片描述

const absoluteCenter = {
  x:
    window.screenX +
    (window.outerWidth - window.innerWidth) +
    window.innerWidth / 2,
  y:
    window.screenY +
    (window.outerHeight - window.innerHeight) +
    window.innerHeight / 2,
};

3. 实现

下面我基于 BroadcastChannel 实现一个示例。

实现不同窗口间,根据其他窗口自行创建箭头,并实时指向彼此:

在这里插入图片描述

请添加图片描述

通讯间需要定义清楚消息类型:

const MSG_TYPE = {
  POSITION: 0,
  NEW: 1,
  REMOVE: 2,
  OTHER: 3,
};

如果要兼容浏览器崩溃下无法发送移除消息,可以加入喂狗程序:

function sendHeartbeat() {
  channel.postMessage({ type: "heartbeat", pageId: wId });
  lastHeartbeat = Date.now();
}

setInterval(function () {
  if (Date.now() - lastHeartbeat > heartbeatInterval * 2) {
    console.log(wId + "离线");
  }
}, heartbeatInterval);

完整代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body,
      html {
        height: 100%;
      }

      .arrow {
        width: 80vmin;
        height: 80vmin;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%) rotate(0deg);
      }

      .arrow::after {
        content: "";
        display: block;
        width: 100vmax;
        height: 3px;
        background-color: red;
        position: absolute;
        top: 50%;
        right: 50%;
        transform: translateY(-50%);
      }

      .arrow img {
        width: 100%;
        height: 100%;
      }

      #center {
        width: 30px;
        height: 30px;
        border-radius: 50%;
        z-index: 999;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }

      #msg {
        position: absolute;
        left: 1em;
        top: 1em;
      }
    </style>
  </head>

  <body>
    <div id="center"></div>
    <div id="msg"></div>

    <script>
      const otherWindows = new Map();
      const channel = new BroadcastChannel("tab_position_channel");
      const wId = Date.now() + "-" + Math.random();
      const color =
        "#" + Math.floor(Math.random() * parseInt("ffff", 16)).toString(16);
      let _new = true;
      const MSG_TYPE = {
        POSITION: 0,
        NEW: 1,
        REMOVE: 2,
        OTHER: 3,
      };
      const lastPosition = {
        x: 0,
        y: 0,
      };

      window.onunload = () => {
        channel.postMessage({
          type: MSG_TYPE.REMOVE,
          wId,
        });
      };

      channel.onmessage = ({ data: { type, x, y, wId: otherId } }) => {
        console.log(`type: ${type}, x: ${x}, y: ${y}, wId: ${wId}`);
        switch (type) {
          case MSG_TYPE.NEW:
            _AddWin({ otherId, x, y });

            channel.postMessage({
              type: MSG_TYPE.OTHER,
              x: lastPosition.x,
              y: lastPosition.y,
              wId,
            });

            break;
          case MSG_TYPE.POSITION:
            otherWindows.set(otherId, {
              x,
              y,
            });

            [...otherWindows.keys()].forEach((otherId) => {
              const arrow = document.getElementById(otherId);
              const { x, y } = otherWindows.get(otherId);
              arrow.style.transform = `translate(-50%, -50%) rotate(${
                (Math.atan2(lastPosition.y - y, lastPosition.x - x) * 180) /
                Math.PI
              }deg)`;
            });

            break;
          case MSG_TYPE.OTHER:
            if (!otherWindows.has(otherId)) {
              _AddWin({ otherId, x, y });
            }
            break;
          case MSG_TYPE.REMOVE:
            otherWindows.delete(otherId);
            document.getElementById(otherId)?.remove();
            break;
        }
      };

      function _AddWin({ otherId, x, y }) {
        otherWindows.set(otherId, {
          x,
          y,
        });

        const arrow = document.createElement("div");
        arrow.className = "arrow";
        arrow.id = otherId;
        arrow.style.transform = `translate(-50%, -50%) rotate(${
          (Math.atan2(lastPosition.y - y, lastPosition.x - x) * 180) / Math.PI
        }deg)`;
        arrow.innerHTML = `<img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%221000%22%20height%3D%221000%22%3E%3Cpath%20fill%3D%22currentColor%22%20fill-opacity%3D%22.8%22%20d%3D%22M1005%20530.682H217.267v-61.364H1005v61.363z%22%2F%3E%3Cpath%20fill%3D%22currentColor%22%20fill-opacity%3D%22.8%22%20d%3D%22M217.989%20581.415%204%20503.552l213.989-79.967z%22%2F%3E%3C%2Fsvg%3E" />`;
        document.body.appendChild(arrow);
      }

      function render() {
        requestAnimationFrame(() => {
          const absoluteCenter = {
            x:
              window.screenX /* + (window.outerWidth - window.innerWidth)*/ +
              window.innerWidth / 2,
            y:
              window.screenY +
              (window.outerHeight - window.innerHeight) +
              window.innerHeight / 2,
          };

          if (
            absoluteCenter.x !== lastPosition.x ||
            absoluteCenter.y !== lastPosition.y
          ) {
            lastPosition.x = absoluteCenter.x;
            lastPosition.y = absoluteCenter.y;

            if (_new) {
              _new = false;
              channel.postMessage({
                type: MSG_TYPE.NEW,
                x: lastPosition.x,
                y: lastPosition.y,
                wId,
              });
            } else {
              channel.postMessage({
                type: MSG_TYPE.POSITION,
                x: absoluteCenter.x,
                y: absoluteCenter.y,
                wId,
              });
            }

            otherWindows.forEach(({ x, y }, otherId) => {
              const angle =
                (Math.atan2(lastPosition.y - y, lastPosition.x - x) * 180) /
                Math.PI;
              document.getElementById(
                otherId
              ).style.transform = `translate(-50%, -50%) rotate(${angle}deg)`;
            });
          }

          msg.innerHTML =
            otherWindows.size === 0
              ? "未发现其他窗口"
              : `发现窗口: <ol>${[...otherWindows.keys()]
                  .map((it) => `<li>${it}</li>`)
                  .join("")}</ol>`;
          render();
        });
      }

      render();
      center.style.backgroundColor = color;
    </script>
  </body>
</html>

在线效果:https://lecepin.github.io/transfer-across-tabs-by-BroadcastChannel/

Github 项目地址: https://github.com/lecepin/transfer-across-tabs-by-BroadcastChannel

原文地址: https://github.com/lecepin/blog/blob/main/%E5%A4%9A%E7%AA%97%E5%8F%A3%E9%87%8F%E5%AD%90%E7%BA%A0%E7%BC%A0%E6%8A%80%E6%9C%AF%E5%AE%9E%E7%8E%B0.md

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小红书蒲公英平台开通后,有哪些注意的地方,以及如何进行报价?

今天来给大家聊聊当小红书账号过1000粉后&#xff0c;开通蒲公英需要注意的事项。 蒲公英平台是小红书APP中的一个专为内容创作者设计的平台。它为品牌和创作者提供了一个完整的服务流程&#xff0c;包括内容的创作、推广、互动以及转换等多个方面。 2.蒲公英平台的主要功能 &…

LIS检验系统,涵盖实验室全部管理流程,适合各种实验机构的业务流程

LIS检验系统源码&#xff0c;云LIS医学检验系统源码 LIS检验系统涵盖实验室的全部管理流程&#xff0c;包括从检验申请、标本采集、实验检测、报告发布的、质控管理、科室事务、试剂管理等完整流程的功能&#xff0c;实现智能化、自动化、规范化管理&#xff0c;遵循医学实验室…

查看虚拟机的版本

1、查看虚拟机的版本 cat /etc/centos-release 2、开启防火墙 systemctl start firewalld 3、查看防火墙状态 firewall-cmd --state

基于SpringBoot+maven+Mybatis+html慢性病报销系统(源码+数据库)

一、项目简介 本项目是一套基于SpringBootmavenMybatishtml慢性病报销系统&#xff0c;主要针对计算机相关专业的正在做bishe的学生和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目可以直接作为bishe使用。 项目都经过严格调试&a…

【数据结构(六)】排序算法介绍和算法的复杂度计算(1)

文章目录 1. 排序算法的介绍1.1. 排序的分类 2. 算法的时间复杂度2.1. 度量一个程序(算法)执行时间的两种方法2.2. 时间频度2.2.1. 忽略常数项2.2.2. 忽略低次项2.2.2. 忽略系数 2.3. 时间复杂度2.4. 常见的时间复杂度2.5. 平均时间复杂度和最坏时间复杂度 3. 算法的空间复杂度…

浅析AI智能视频监控技术在城市交通中的作用及意义

城市交通作为整个城市的整体脉络&#xff0c;每天都发挥着重要作用&#xff0c;为了最大程度地避免城市交通堵塞、提高城市交通效率&#xff0c;智能视频监控系统发挥了重要作用。具体表现在以下几个方面&#xff1a; 1、交通违规监管&#xff1a;TSINGSEE青犀智能视频监控系统…

软件开发代码审核制度

建立软件代码审核制度是确保代码质量和团队协作的重要步骤。以下是建立有效的软件代码审核制度的一些建议&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.制定代码审核准则&#xff1a; 明确定义代…

【C/PTA —— 15.结构体2(课内实践)】

C/PTA —— 15.结构体2&#xff08;课内实践&#xff09; 7-1 计算职工工资7-2 计算平均成绩7-3 找出总分最高的学生7-4 通讯录的录入与显示 7-1 计算职工工资 #include<stdio.h> #include<stdlib.h> typedef struct GZ {char name[6];double j;double f;double z;…

如何将 MySQL 数据库转换为 SQL Server

本文解释了为什么组织希望将其 MySQL 数据库转换为 Microsoft SQL 数据库。本文接着详细介绍了尝试转换之前需要记住的事项以及所涉及的方法。专业的数据库转换器工具将帮助您快速将 MySQL 数据库记录转换为 MS SQL Server。 在继续之前&#xff0c;我们先讨论一下 MySQL 到 M…

React/Vue/Svelte 前端项目中开始使用TailwindCSS

背景 TailwindCSS 近年来在前端圈非常流行&#xff0c;它摆脱了原有的CSS限制&#xff0c;以灵活实用为卖点&#xff0c;用户通过各种class组合即可构建出漂亮的用户界面。对于初学者而言&#xff0c;可能需要一些上手成本&#xff0c;一旦掌握实用技巧后&#xff0c;Tailwind…

TCP通信

第二十一章 网络通信 本章节主要讲解的是TCP和UDP两种通信方式它们都有着自己的优点和缺点 这两种通讯方式不通的地方就是TCP是一对一通信 UDP是一对多的通信方式 接下来会一一讲解 TCP通信 TCP通信方式呢 主要的通讯方式是一对一的通讯方式&#xff0c;也有着优点和缺点…

QQ录屏怎么录声音,如何解决声音录制问题?

在数字化时代&#xff0c;qq不仅是我们日常沟通的工具&#xff0c;还提供了强大的录屏功能&#xff0c;让用户能够轻松捕捉屏幕上的精彩瞬间。然而&#xff0c;很多用户可能会困惑于qq录屏怎么录声音。在本文中&#xff0c;我们将详细介绍如何通过QQ录屏功能录制屏幕及声音&…

【C/PTA —— 15.结构体2(课外实践)】

C/PTA —— 15.结构体2&#xff08;课外实践&#xff09; 7-1 一帮一7-2 考试座位号7-3 新键表输出7-4 可怕的素质7-5 找出同龄者7-6 排队7-7 军训 7-1 一帮一 #include<stdio.h> #include<string.h>struct student {int a;char name[20]; };struct student1 {int …

JavaWeb-HTTP协议

1. 什么是HTTP协议 HTTP超文本传输协(Hyper Text transfer protocol)&#xff0c;是一种用于用于分布式、协作式和超媒体信息系统的应用层协议。它于1990年提出&#xff0c;经过十几年的使用与发展&#xff0c;得到不断地完善和扩展。HTTP 是为 Web 浏览器与 Web 服务器之间的…

新生儿出生缺陷筛查的关键注意事项

引言&#xff1a; 新生儿的出生缺陷是一个复杂而广泛的问题&#xff0c;及早的筛查和诊断对于预防和管理这些缺陷至关重要。出生缺陷可能涉及各个系统&#xff0c;包括心脏、神经、遗传等&#xff0c;因此及时而全面的筛查对新生儿的健康至关重要。本文将深入探讨新生儿出生缺…

Matlab 点云对称性检测

文章目录 一、简介二、实现代码三、实现效果参考文献一、简介 这是一个很有趣的功能,它的思路其实与ICP算法的思路有些相似: 首先,它会初始化两个旋转角度,即绕x轴旋转与绕y轴旋转,初始的过程是将点对称(镜像)过去,计算与匹配点之间的距离误差,误差最小者为最优初始值…

抖店店铺没流量?商品销售量低?原因在于这几个!

我是电商珠珠 近期&#xff0c;很多人来跟我反馈&#xff0c;说自己的店铺没有流量和曝光&#xff0c;更别提出单了。 其实&#xff0c;想要自己的店铺月销百万&#xff0c;每天要做的工作有很多&#xff0c;比如分析市场、选品上架、优化、对接达人等&#xff0c;想要自己的…

JAVA后端自学技能实操合集

JAVA后端自学技能实操 内容将会持续更新中,有需要添加什么内容可以再评论区留言,大家一起学习FastDFS使用docker安装FastDFS(linux)集成到springboot项目中 内容将会持续更新中,有需要添加什么内容可以再评论区留言,大家一起学习 FastDFS 组名&#xff1a;文件上传后所在的 st…

网站建设app开发小程序制作|企业软件定制

网站建设app开发小程序制作|企业软件定制 网站建设和软件开发是现代社会非常重要的领域&#xff0c;它们对于企业、机构和个人来说都具有非常大的意义。随着移动互联网的快速发展&#xff0c;小程序制作也逐渐成为一种非常受欢迎的方式。 在过去&#xff0c;建立一个网站需要具…

Andorid sudio 换行方法

1.遇到的问题&#xff0c;二维码内容要换行 String text "成绩&#xff1a;1000 \n姓名&#xff1a;张三 \n姓名&#xff1a;张三 \n姓名&#xff1a;张三 \n姓名&#xff1a;张三 \n姓名&#xff1a;张三 \n姓名&#xff1a;张三 \n姓名&#xff1a;张三 \n姓名&#xff…