cd /news/developer-tools/experimential-cross-tab-communicatio… · home topics developer-tools article
[ARTICLE · art-8884] src=gist.github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Experimential Cross Tab Communication

Based on the provided code, this article describes an experimental web application that uses the `BroadcastChannel` API to enable real-time communication between multiple browser tabs or windows. Each tab generates a unique ID and periodically broadcasts its screen position, allowing the application to draw a network graph showing the relative positions of all connected tabs on a canvas. The system also includes logic to clean up tabs that have not sent a recent position update.

read2 min views17 publishedMay 21, 2026

index.html

  This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

Show hidden characters

<!DOCTYPE html>

<html lang="en"> <head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Cross Tab Communication</title>

    <style>

        * {

            margin: 0;

            padding: 0;

            box-sizing: border-box;

            overflow: hidden;

        }

    </style>

</head>

<body>

    <canvas id="default"></canvas>

    <script src="/index.js"></script>

</body>

</html>

index.js

  This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

Show hidden characters
const TAB_ID = crypto.randomUUID();

const TAB_TIMEOUT = 100;

const channel = new BroadcastChannel('cross-tab-com');

const canvas = document.getElementById('default');

const ctx = canvas.getContext('2d');

const posMap = {};

const lastSeenMap = {};

channel.addEventListener('message', (event) => {

    if (event.data.tabId === TAB_ID) return;

    posMap[event.data.tabId] = { x: event.data.x, y: event.data.y };

    lastSeenMap[event.data.tabId] = performance.now();

});

function getWindowMiddlePosition() {

    const chromeHeight = window.outerHeight - window.innerHeight;

    return {

        x: window.screenX + window.innerWidth / 2,

        y: window.screenY + chromeHeight + window.innerHeight / 2,

    };

}

function globalToLocal(globalX, globalY) {

    const chromeHeight = window.outerHeight - window.innerHeight;

    return {

        x: globalX - window.screenX,

        y: globalY - window.screenY - chromeHeight,

    };

}

function post() {

    const { x, y } = getWindowMiddlePosition();

    channel.postMessage({ tabId: TAB_ID, x, y });

}

function period() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    drawLines();

    drawPoints();

    requestAnimationFrame(period);

}

function drawLines() {

    const currentGlobal = getWindowMiddlePosition();

    const currentLocal = globalToLocal(

        currentGlobal.x,

        currentGlobal.y

    );

    const points = [{ x: currentLocal.x, y: currentLocal.y }];

    for (const id in posMap) {

        const remoteGlobal = posMap[id];

        const remoteLocal = globalToLocal(remoteGlobal.x, remoteGlobal.y);

        points.push({ x: remoteLocal.x, y: remoteLocal.y });

    }

    ctx.strokeStyle = 'black';

    ctx.lineWidth = 1;

    for (let i = 0; i < points.length; i++) {

        for (let j = i + 1; j < points.length; j++) {

            ctx.beginPath();

            ctx.moveTo(points[i].x, points[i].y);

            ctx.lineTo(points[j].x, points[j].y);

            ctx.stroke();

        }

    }

}

function drawPoints() {

    const currentGlobal = getWindowMiddlePosition();

    const currentLocal = globalToLocal(

        currentGlobal.x,

        currentGlobal.y

    );

    ctx.beginPath();

    ctx.arc(currentLocal.x, currentLocal.y, 10, 0, 2 * Math.PI);

    ctx.fillStyle = 'red';

    ctx.fill();

    for (const id in posMap) {

        const remoteGlobal = posMap[id];

        const remoteLocal = globalToLocal(

            remoteGlobal.x,

            remoteGlobal.y

        );

        ctx.beginPath();

        ctx.arc(remoteLocal.x, remoteLocal.y, 10, 0, 2 * Math.PI);

        ctx.fillStyle = 'blue';

        ctx.fill();

    }

}

function resizeCanvas() {

    canvas.width = window.outerWidth;

    canvas.height = window.outerHeight;

}

function cleanupDeadTabs() {

    const now = performance.now();

    for (const id in lastSeenMap) {

        if (now - lastSeenMap[id] > TAB_TIMEOUT) {

            delete lastSeenMap[id];

            delete posMap[id];

        }

    }

}

window.addEventListener("resize", resizeCanvas);

resizeCanvas();

period();

setInterval(post, 10);

setInterval(cleanupDeadTabs, 100);
── more in #developer-tools 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/experimential-cross-…] indexed:0 read:2min 2026-05-21 ·