浏览器跨标签星球火了,简单探究一下实现原理

一、前言

最近 推特上 一位懂设计和写代码的大神一个两个浏览器之间 星球粒子交互的动画火了, 让人看了大呼脑洞大开, 浏览器竟然还能这么玩!!!

准备自己也搞搞玩一下

二、实现

其实实现类似的功能需要的难点并不多,不在乎以下几个步骤

  • 1、 屏幕坐标和窗口坐标转换
  • 2、 跨标签通讯

1、 先来看第一个点, 获取屏幕坐标与窗口坐标

js 复制代码
// 屏幕坐标转换为窗口坐标
const screenToClient = (screenX, screenY) => {
  const clienX = screenX - window.screenX;
  const clienY = screenY - window.screenY - barHeight();
  return [clienX, clienY];
};

// 窗口坐标转换为屏幕坐标
const clientToScreen = (clienX, clienY) => {
  const screenX = clienX + window.screenX;
  const screenY = clienY + window.screenY + barHeight();
  return [screenX, screenY];
};

我们先简单实现一个卡片, 通过url上面传递颜色值, 设置定位

在卡片本上设置上点击拖动等事件

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>跨标签通讯</title>
  </head>
  <style>
    .card {
      width: 300px;
      height: 300px;
      background-color: #f00;
      position: fixed;
      top: 100px;
      left: 100px;
    }
  </style>
  <body>
    跨标签通讯
    <div class="card">card</div>
  </body>
  <script>
    const barHeight = () => window.outerHeight - window.innerHeight;
    const cardDom = document.querySelector(".card");
    cardDom.style.top = 100 + "px";
    cardDom.style.left = 100 + "px";
    cardDom.style.background =
      new URLSearchParams(window.location.search).get("color") || "red";

    window.onload = function () {
      cardDom.onmousedown = function (e) {
        cardDom.style.cursor = "pointer";
        let x = e.pageX - cardDom.offsetLeft;
        let y = e.pageY - cardDom.offsetTop;
        window.onmousemove = function (e) {
          cardDom.style.left = e.clientX - x + "px";
          cardDom.style.top = e.clientY - y + "px";
          // 发送消息
          const clientCoordinateX = e.clientX - x;
          const clientCoordinateY = e.clientY - y;
          const ScreenCoordinate = clientToScreen(
            clientCoordinateX,
            clientCoordinateY
          );
          sendMessage(ScreenCoordinate);
        };
        window.onmouseup = function () {
          window.onmousemove = null;
          window.onmouseup = null;
          cardDom.style.cursor = "unset";
        };
      };
    };
  </script>
</html>

单个元素的拖动就实现了, 很简单, 如何让其他标签的元素也能同步进行, 需要实现跨标签方案了, 可以参考该文章- 跨标签页通信的8种方式

我们就选择第一种,使用 BroadCast Channel, 使用也很简单

js 复制代码
// 创建 Broadcast Channel
const channel = new BroadcastChannel("myChannel");
// 监听消息
channel.onmessage = (event) => {
  // 处理接收到的消息
  console.log('接收',event)
};
// 发送消息
const sendMessage = (message) => {
  channel.postMessage(message);
};

只需要在移动时发送消息, 再其他标签页就可以接收到值了, 现在关键的就是收到发送的坐标点后, 如何处理, 其实关键就是要让几个窗口的卡片位置转化到同一个纬度, 让其再超出浏览器的时候,再另一个窗口的同一个位置出现, 所以就需要将窗口坐标转化成屏幕坐标,发送给其他窗口后, 再转化成窗口坐标进行渲染即可

完整代码实现如下

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>跨标签通讯</title>
  </head>
  <style>
    .card {
      width: 300px;
      height: 300px;
      background-color: #f00;
      position: fixed;
      top: 100px;
      left: 100px;
    }
  </style>
  <body>
    跨标签通讯
    <div class="card">card</div>
  </body>
  <script>
    const barHeight = () => window.outerHeight - window.innerHeight;
    const cardDom = document.querySelector(".card");
    cardDom.style.top = 100 + "px";
    cardDom.style.left = 100 + "px";
    cardDom.style.background =
      new URLSearchParams(window.location.search).get("color") || "red";

    // 屏幕坐标转换为窗口坐标
    const screenToClient = (screenX, screenY) => {
      const clienX = screenX - window.screenX;
      const clienY = screenY - window.screenY - barHeight();
      return [clienX, clienY];
    };

    // 窗口坐标转换为屏幕坐标
    const clientToScreen = (clienX, clienY) => {
      const screenX = clienX + window.screenX;
      const screenY = clienY + window.screenY + barHeight();
      return [screenX, screenY];
    };

    // 创建 Broadcast Channel
    const channel = new BroadcastChannel("myChannel");
    // 监听消息
    channel.onmessage = (event) => {
      // 处理接收到的消息
      const [clienX, clienY] = screenToClient(...event.data);
      // 不同窗口的卡片要在同一个位置, 要放到同一个坐标系下面,保持屏幕坐标一致
      cardDom.style.left = clienX + "px";
      cardDom.style.top = clienY + "px";
    };

    // 发送消息
    const sendMessage = (message) => {
      channel.postMessage(message);
    };

    window.onload = function () {
      cardDom.onmousedown = function (e) {
        cardDom.style.cursor = "pointer";
        let x = e.pageX - cardDom.offsetLeft;
        let y = e.pageY - cardDom.offsetTop;
        window.onmousemove = function (e) {
          cardDom.style.left = e.clientX - x + "px";
          cardDom.style.top = e.clientY - y + "px";
          // 发送消息
          const clientCoordinateX = e.clientX - x;
          const clientCoordinateY = e.clientY - y;
          const ScreenCoordinate = clientToScreen(
            clientCoordinateX,
            clientCoordinateY
          );
          sendMessage(ScreenCoordinate);
        };
        window.onmouseup = function () {
          window.onmousemove = null;
          window.onmouseup = null;
          cardDom.style.cursor = "unset";
        };
      };
    };
  </script>
</html>
相关推荐
鬼手点金21 分钟前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
柳杉22 分钟前
借助 Codex 和可视化工作流,从调研到完整实现,3D 大屏竟然可以这么简单
前端·ai编程·数据可视化
IMPYLH1 小时前
HTML 的 <wbr> 元素
前端·html
Dovis(誓平步青云)1 小时前
常用命令藏得太深?怎么做一个快捷命令坞
运维·前端·自动化·音视频·prometheus
IT_陈寒2 小时前
Vite的这个问题坑了我三天,原来如此解决!
前端·人工智能·后端
网易云信2 小时前
大模型能上岗做销售吗?我们让 12 款国产模型交了 420 份作业
前端·后端
蜗牛互联网2 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python
计算机魔术师2 小时前
Perplexity:每个 Project 都是一个 Git 仓库
前端
Andrew5513 小时前
让 AI 写页面不再“一眼假”:我把设计品味做成了一个 Claude Code Skill 流水线
前端
乐享qicheng3 小时前
14-AI智能管家JSBridge深度问答
前端