{"slug": "experimential-cross-tab-communication", "title": "Experimential Cross Tab Communication", "summary": "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.", "body_md": "index.html\n\n      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.\n      \nLearn more about bidirectional Unicode characters\n\n \n    Show hidden characters\n\n<!DOCTYPE html>\n\n<html lang=\"en\">\n\n<head>\n\n    <meta charset=\"UTF-8\">\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n    <title>Cross Tab Communication</title>\n\n    <style>\n\n        * {\n\n            margin: 0;\n\n            padding: 0;\n\n            box-sizing: border-box;\n\n            overflow: hidden;\n\n        }\n\n    </style>\n\n</head>\n\n<body>\n\n    <canvas id=\"default\"></canvas>\n\n    <script src=\"/index.js\"></script>\n\n</body>\n\n</html>\n\nindex.js\n\n      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.\n      \nLearn more about bidirectional Unicode characters\n\n \n    Show hidden characters\n\nconst TAB_ID = crypto.randomUUID();\n\nconst TAB_TIMEOUT = 100;\n\nconst channel = new BroadcastChannel('cross-tab-com');\n\nconst canvas = document.getElementById('default');\n\nconst ctx = canvas.getContext('2d');\n\nconst posMap = {};\n\nconst lastSeenMap = {};\n\nchannel.addEventListener('message', (event) => {\n\n    if (event.data.tabId === TAB_ID) return;\n\n    posMap[event.data.tabId] = { x: event.data.x, y: event.data.y };\n\n    lastSeenMap[event.data.tabId] = performance.now();\n\n});\n\nfunction getWindowMiddlePosition() {\n\n    const chromeHeight = window.outerHeight - window.innerHeight;\n\n    return {\n\n        x: window.screenX + window.innerWidth / 2,\n\n        y: window.screenY + chromeHeight + window.innerHeight / 2,\n\n    };\n\n}\n\nfunction globalToLocal(globalX, globalY) {\n\n    const chromeHeight = window.outerHeight - window.innerHeight;\n\n    return {\n\n        x: globalX - window.screenX,\n\n        y: globalY - window.screenY - chromeHeight,\n\n    };\n\n}\n\nfunction post() {\n\n    const { x, y } = getWindowMiddlePosition();\n\n    channel.postMessage({ tabId: TAB_ID, x, y });\n\n}\n\nfunction period() {\n\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n    drawLines();\n\n    drawPoints();\n\n    requestAnimationFrame(period);\n\n}\n\nfunction drawLines() {\n\n    const currentGlobal = getWindowMiddlePosition();\n\n    const currentLocal = globalToLocal(\n\n        currentGlobal.x,\n\n        currentGlobal.y\n\n    );\n\n    const points = [{ x: currentLocal.x, y: currentLocal.y }];\n\n    for (const id in posMap) {\n\n        const remoteGlobal = posMap[id];\n\n        const remoteLocal = globalToLocal(remoteGlobal.x, remoteGlobal.y);\n\n        points.push({ x: remoteLocal.x, y: remoteLocal.y });\n\n    }\n\n    ctx.strokeStyle = 'black';\n\n    ctx.lineWidth = 1;\n\n    for (let i = 0; i < points.length; i++) {\n\n        for (let j = i + 1; j < points.length; j++) {\n\n            ctx.beginPath();\n\n            ctx.moveTo(points[i].x, points[i].y);\n\n            ctx.lineTo(points[j].x, points[j].y);\n\n            ctx.stroke();\n\n        }\n\n    }\n\n}\n\nfunction drawPoints() {\n\n    const currentGlobal = getWindowMiddlePosition();\n\n    const currentLocal = globalToLocal(\n\n        currentGlobal.x,\n\n        currentGlobal.y\n\n    );\n\n    ctx.beginPath();\n\n    ctx.arc(currentLocal.x, currentLocal.y, 10, 0, 2 * Math.PI);\n\n    ctx.fillStyle = 'red';\n\n    ctx.fill();\n\n    for (const id in posMap) {\n\n        const remoteGlobal = posMap[id];\n\n        const remoteLocal = globalToLocal(\n\n            remoteGlobal.x,\n\n            remoteGlobal.y\n\n        );\n\n        ctx.beginPath();\n\n        ctx.arc(remoteLocal.x, remoteLocal.y, 10, 0, 2 * Math.PI);\n\n        ctx.fillStyle = 'blue';\n\n        ctx.fill();\n\n    }\n\n}\n\nfunction resizeCanvas() {\n\n    canvas.width = window.outerWidth;\n\n    canvas.height = window.outerHeight;\n\n}\n\nfunction cleanupDeadTabs() {\n\n    const now = performance.now();\n\n    for (const id in lastSeenMap) {\n\n        if (now - lastSeenMap[id] > TAB_TIMEOUT) {\n\n            delete lastSeenMap[id];\n\n            delete posMap[id];\n\n        }\n\n    }\n\n}\n\nwindow.addEventListener(\"resize\", resizeCanvas);\n\nresizeCanvas();\n\nperiod();\n\nsetInterval(post, 10);\n\nsetInterval(cleanupDeadTabs, 100);", "url": "https://wpnews.pro/news/experimential-cross-tab-communication", "canonical_source": "https://gist.github.com/000hen/59800f20c9f5af9e1e317beb0a767635", "published_at": "2026-05-21 10:36:39+00:00", "updated_at": "2026-05-22 13:09:18.593626+00:00", "lang": "en", "topics": ["developer-tools", "web3"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/experimential-cross-tab-communication", "markdown": "https://wpnews.pro/news/experimential-cross-tab-communication.md", "text": "https://wpnews.pro/news/experimential-cross-tab-communication.txt", "jsonld": "https://wpnews.pro/news/experimential-cross-tab-communication.jsonld"}}