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

一、前言

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

准备自己也搞搞玩一下

二、实现

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

  • 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>
相关推荐
前端开发爱好者4 分钟前
一键 i18n 国际化神库!适配 Vue、React!
前端·javascript·vue.js
前端开发爱好者5 分钟前
Vite 移动端调试利器!开发效率飙升 300%!
前端·javascript·vue.js
BillKu27 分钟前
容器元素的滚动条回到顶部
前端·javascript·vue.js
weixin_4233919330 分钟前
React 19 全面解析:颠覆性的新特性与实战指南
前端·javascript·react.js
weixin_4233919330 分钟前
React Hooks 钩子
前端·javascript·react.js
CUGGZ31 分钟前
第三代 React,怎么玩?
前端·javascript·react.js
星哥说事33 分钟前
狂揽82.7k的star,这款开源可视化神器,轻松创建流程图和图表
前端
硅基宙宇AIGC34 分钟前
阿里Qoder重磅登场:AI编程平台新王者,程序员的饭碗要换了吗?
前端
影i35 分钟前
跨域登录 / Token 共享 踩坑记录
前端
小爱同学_1 小时前
从前端模块化历史到大厂面试题
前端·javascript·面试