Multi-Window Experiences

with p5.js

Checkin? 😢

💪 😵‍💫 😖 💪 💪 🏃‍♂️ 😖 🌀 🏃 💪

Mein Weg zu diesem Workshop

Was meint Multi-Window Experiences?

Ziele des Workshops

  • Wie können Daten zwischen Fenstern ausgetauscht werden?
  • Wie lassen sich diese Ansätze mit p5.js sinnvoll verbinden?
  • Was lässt sich gestalterisch damit machen? 🧐
  • bissl Spaß haben 👻

Wie kommen wir dahin?

Zielbild: Wo geht die Reise hin?
Szenarien: Kleiner konzeptioneller Unterbau
Sharing Data: Verschiedene Ansätze zum Austausch von Daten zwischen Fenstern. mit Übung
Working with p5.js: Kleiner Rundflug über p5.js
Code-Code-Code: Aufgaben zum selber coden 🎊
Rückblick/ Ausblick: Was war – was geht?

Material

Disclaimer ⚠️

Wir werden die meisten Konzepte & Techniken nur sehr oberflächlich behandeln.
Der Code ist teils leider recht umfassend und unübersichtlich.
Für p5.js wird uns Repertoire fehlen. 😢
Babylonisches Sprachgewirr 😱
Die meisten Konzepte & Techniken hab ich noch nie in der Hand gehabt oder gar ernsthaft genutzt. 🧐

Disclaimer, Teil 2 ‼️

Es wurden keine Performance Messungen vorgenommen.
Wir befassen uns nur mit zwei Fenstern.
Insgesamt ist sehr wenig getestet worden, insbesondere wurden kaum Cross-Browser Tests gemacht.
Nicht alle sinnvollen Ansätze werden in einem Sketch(?) zusammen gebracht, z.B. Physics & Multi-Window
Alle Implementierungen versuchen möglichst nah an Standards zu sein.

Wohin geht es?

Single Window, Simple Object // Single Window, Complex Object // Multi-Window, Simple Object // Multi-Window, Complex Object // Rotating Circles // Casting

Welche Multi-Window-Cases könnten betrachtet werden?

Position on Screen

Multiple Windows

Distance

Angle

Right or left

Intersection

Overlap & Casting

Multi Screen

Woher kommen die Daten
und wie können wir sie austauschen?

Position on screen via window.screenX & window.screenY

Share Data via window.opener

Store shared data via Local Storage

Use of a higher-level Shared Worker

Using an external component: WebSocket

Observe Window Position

No event listener is available 😢

Position on screen via window.screenX & window.screenY

Step 1: Detect Window Position

.

console.log('Window position:', window.screenX, window.screenY);

Step 2: Observe Window Position

.

const myWindowPosition = {
  x: false,
  y: false
}
/* break */
const trackMyWindowPosition = () => {
  if (myWindowPosition.x === window.screenX 
    && myWindowPosition.y === window.screenY) return;
  /* break */
  myWindowPosition.x = window.screenX;
  myWindowPosition.y = window.screenY;
  /* break */
  // Do something meaningful with the data 🕺🏼
  console.log(myWindowPosition.x, myWindowPosition.y);
}
/* break */
trackMyWindowPosition();
const observeMyWindowPosition = setInterval(trackMyWindowPosition, 500);

Position on screen via window.screenX & window.screenY

Share Data via window.opener

Baseline Widely available

Share Data via window.opener

Using window.opener

Baseline Widely available

// In opener/parent window
/* break */
const childWindowPosition = {
  x: false,
  y: false
}
/* break */
// Open child window
childWindow = window.open(url, 'child-window');
/* break */
// Read window position from child
if (childWindowPosition.x === childWindow.screenX 
  && childWindowPosition.y === childWindow.screenY) return;
  /* break */
childWindowPosition.x = childWindow.screenX;
childWindowPosition.y = childWindow.screenY;
// In opened/child window
/* break */
const parentWindowPosition = {
  x: false,
  y: false
}
/* break */
// Read window position from parent
if (parentWindowPosition.x === window.opener.screenX 
  && parentWindowPosition.y === window.opener.screenY) return;
  /* break */
parentWindowPosition.x = window.opener.screenX;
parentWindowPosition.y = window.opener.screenY;

Advantages of window.opener 🥳

  • direct reference to the parent window
  • easy to call functions or access variables
  • minimal setup, no external messaging needed

Disadvantages 😢

  • only works if the window was opened via window.open()
  • breaks on reloads or navigation
  • no support for cross-tab communication
  • tight coupling between windows

Share Data via window.opener

Share Data via Broadcast Channel API & window.postMessage

Publish–Subscribe Messaging

Using Broadcast Channel API

The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

// In (first) window
/* break */
// Open Broadcast Channel
const windowPositionChannel = new BroadcastChannel('window-position');
/* break */
/* some lines of funky code */
/* break */
// Send my window position to the channel
windowPositionChannel.postMessage(myWindowPosition);
/* break */
// Receive messages from the channel
windowPositionChannel.onmessage = (event) => {
  const otherWindowPosition = event.data;
  /* break */
  // Do something meaningful with the data 🕺🏼
  console.log(otherWindowPosition.x, otherWindowPosition.y);
}
// In (second) window
/* break */
// Open Broadcast Channel
const windowPositionChannel = new BroadcastChannel('window-position');
/* break */
/* some lines of funky code */
/* break */
// Send my window position to the channel
windowPositionChannel.postMessage(myWindowPosition);
/* break */
// Receive messages from the channel
windowPositionChannel.onmessage = (event) => {
  const otherWindowPosition = event.data;
  /* break */
  // Do something meaningful with the data 🕺🏼
  console.log(otherWindowPosition.x, otherWindowPosition.y);
}

Broadcast Channel API & window.postMessage()

The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it. The Broadcast Channel API is restricted on same origin communication.

If a participant (tab, window, worker) sends a message via BroadcastChannel.postMessage(), it is sent to all other participants in the channel - but not to the sender itself.

⚠️ Note that window.postMessage() also works without the Broadcast Channel API.

Advantages 🥳

  • combines broadcasting and direct messaging
  • works well for decoupled, event-based communication
  • no shared memory needed — everything flows through messages
  • no direct window references needed
  • scalable to multiple tabs and controlled windows

Disadvantages 😢

  • requires same-origin (for BroadcastChannel)
  • no built-in state — messages must be actively passed around
  • no message history — late listeners miss earlier messages

Store shared data via LocalStorage

Storage Event Triggering Paradigm

Store shared data via Local Storage

Store shared data via LocalStorage

LocalStorage allows browser windows of the same origin to persistently store and share string-based key–value pairs. The storage event lets other open windows react to changes in real time, making it useful for simple cross-window communication.

// Write changes to local storage
const trackMyWindowPosition = () => {
  /* break */
  if (myWindowPosition.x === window.screenX 
    && myWindowPosition.y === window.screenY) return;
  /* break */
  myWindowPosition.x = window.screenX;
  myWindowPosition.y = window.screenY;
  /* break */
  // Store Window Data in Local Storage
  localStorage.setItem('windowPosition', JSON.stringify(myWindowPosition));
}
// Listen to changes in local storage
window.addEventListener('storage', (event) => {
  /* break */
  if(!event.key === 'windowPosition') return;
  const otherWindowPosition = JSON.parse(event.newValue);
  /* break */
  // Do something meaningful with the data 🕺🏼
  console.log(otherWindowPosition.x, otherWindowPosition.y);
});

Advantages 🥳

  • simple key–value storage, persistent across reloads
  • built-in storage event allows reactive cross-tab communication
  • no direct window references needed
  • easy to implement and debug

Disadvantages 😢

  • string-only storage (no complex data types without serialization)
  • storage events don’t fire in the same window 🧐
  • not designed for high-frequency updates or large data volumes

Store shared data via Local Storage

Use of a higher-level Shared Worker

Shared Process with Message-Based Communication

Use of a higher-level Shared Worker

Shared Worker with postMessage()

This creates a new connection to an existing or newly started SharedWorker whose code is located in the shared.js file. In contrast to workers, which start a separate thread per window or tab, a SharedWorker is only started once per Origin - all tabs share the same worker process.

// Shared Worker Code in shared.js
/* break */
const connections = [];
/* break */
onconnect = function (e) {
  const port = e.ports[0];
  connections.push(port);
  /* break */
  port.onmessage = function (event) {
    const data = event.data;
    /* break */
    // Echo to all other windows
    connections.forEach(p => {
      if (p !== port) {
        p.postMessage(data);
      }
    });
  };
  /* break */
  port.start(); // Required for older browsers
};
// Client Code
// Start or connect to worker
const worker = new SharedWorker('shared.js');
worker.port.start();
/* break */
const trackMyWindowPosition = () => {
  /* ... */
  // Store Window Data in Shared Worker
  worker.port.postMessage(myWindowPosition);
}
/* break */
// Observe my window position
const observeMyWindowPosition = setInterval(trackMyWindowPosition, 500);
/* break */
// Receive messages from the worker
worker.port.onmessage = (event) => {
  /* break */
  const otherWindowPosition = event.data;
  /* break */
  // Do something meaningful with the data 🕺🏼
  console.log(otherWindowPosition.x, otherWindowPosition.y);
};

Advantages 🥳

  • one central background process shared by all windows
  • ideal for coordinating state across multiple tabs
  • allows custom logic, shared memory, and message routing
  • fully decouples windows — no references required

Disadvantages 😢

  • requires custom implementation of state & communication
  • slightly more complex setup (ports, onconnect, message handling)
  • same-origin only
  • not available in some contexts (e.g. Incognito mode in Chrome)

Use of a higher-level Shared Worker

Endlich Aufgaben 🎊

Repo erklären! 🧐

Endlich Aufgaben 🎊

01. Share Data between Windows: You are given a starter file with a code skeleton. Your job is to implement a working communication channel that allows each window to display: its own position (based on window.screenX / screenY) and the position of the other window. 30min

Let's go 👨🏽‍💻

Shared Worker with Central State Management

This SharedWorker script manages real-time communication between multiple browser windows by tracking and sharing their screen positions.

Each connected window can send its current position, retrieve its own stored position, or request the positions of all other connected windows.

Working with p5.js

About p5.js

p5.js is an open-source JavaScript library that brings the core principles of creative coding to the web. It was initiated in 2013 by Lauren McCarthy, an artist and programmer, as a browser-based reinterpretation of Processing, the influential visual programming environment developed by Casey Reas and Ben Fry at the MIT Media Lab.

Core Structure of a Sketch

The tools uses a simple sketch structure based on two main functions: setup(), which runs once at the beginning, and draw(), which runs continuously like a loop. To get started, you can write your code in the p5.js Web Editor.

function setup() {
  createCanvas(400, 400);
}
/* break */
function draw() {
  background(220);
  ellipse(mouseX, mouseY, 50, 50);
}

Useful Snippets: ColorMode

colorMode() in p5.js sets how colors are interpreted. Using HSB (Hue, Saturation, Brightness) is often better because it separates color, intensity, and lightness, making color control more intuitive.

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 360, 100, 100, 100);
  fill(90, 100, 70, 100);
  stroke(270, 100, 70, 100);
  strokeWeight(10);
}

function draw() {
  background(0, 0, 0, 100);
  ellipse(mouseX, mouseY, 50, 50);
}

Useful Snippets: random() & randomSeed()

random() in p5.js returns a random number, useful for adding unpredictability. randomSeed() sets the starting point for random values, making results repeatable — helpful for debugging or creating consistent outcomes.

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 360, 100, 100, 100);
  fill(90, 100, 70, 100);
  stroke(270, 100, 70, 100);
  strokeWeight(10);
}

function draw() {
  background(0, 0, 0, 100);

  const dotSize = random(-20, 100);
  ellipse(mouseX, mouseY, dotSize);
}

Useful Snippets: map()

map() in p5.js converts a number from one range to another. It's useful for scaling values, like turning mouse position into color or speed.

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 360, 100, 100, 100);
  fill(90, 100, 70, 100);
}

function draw() {
  background(0, 0, 0, 100);
  const hue = map(mouseX, 0, 400, 0, 360);
  const dotSize = map(mouseY, 0, 400, 0, 50);
  fill(hue, 100, 100, 100);
  ellipse(mouseX, mouseY, dotSize);
}

Useful Snippets: dist()

dist() in p5.js calculates the distance between two points. It's useful for measuring how far apart objects are, often used in movement or collision detection.

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 360, 100, 100, 100);
  fill(90, 100, 70, 100);
}

function draw() {
  background(0, 0, 0, 100);
  const dotSize = dist(0, 0, mouseX, mouseY);
  ellipse(width/2, height/2, dotSize);
}

Brain-Freeze: translate() & rotate()

translate() in p5.js moves the origin of the drawing space, letting you draw shapes in a new position. rotate() turns the drawing space, useful for rotating shapes around a point.

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 360, 100, 100, 100);
  fill(335, 92, 87, 100);
}

function draw() {
  background(0, 0, 0, 100);
  translate(width/2, height/2);
  rotate(45);
  rect(-30, -30, 60, 60);
}

Useful Snippets: Simple Animation

This code creates a rotating rectangle in the center of the canvas. The translate() function moves the origin to the center, and rotate(angle) spins the rectangle around that point. angle increases over time, causing continuous rotation, while the semi-transparent background creates a fading trail effect.

let angle = 0;

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 360, 100, 100, 100);
  fill(335, 92, 87, 100);
}

function draw() {
  background(0, 0, 0, 10);

  translate(width/2, height/2);
  rotate(angle);
  rect(0, 0, 60, 60);

  angle += 0.08;
}

Time to assemble.

Share Window Data Concepts & p5.js

In order to create a link between the sketch and the screen, we move and scale the canvas to the size of the screen.

In order to create a link between the sketch and the screen, we move and scale the canvas to the size of the screen.

Fit Canvas to Screen Size

This code adjusts the canvas based on the browser window’s position on the screen, allowing the sketch to stay aligned with the physical screen space.

let posX, posY;

function draw() {
  background(0, 0, 0, 100);

  if (drawingParams.context === 'screen') {
    // Move the origin of the canvas to the top left corner of the screen
    const originX = -window.screenX;
    const originY = -window.screenY;
    canvas.width = screen.width;
    canvas.height = screen.height;
    translate(originX, originY);
    posX = width / 2;
    posY = height / 2;

  }else{
    posX = window.innerWidth / 2;
    posY = window.innerHeight / 2;
  }

  ellipse(posX, posY, 60, 60);
}

Multi Window Sketch Assignments 😎

02. Draw Line between Windows: Extend the p5 sketch so that a line is drawn from your own center to the other window’s center. 15min

03. Dual-Window Visualization Experiment Turn a given starter sketch into an interactive or generative visual system that lives across two windows. 30min

Let's go 👨🏽‍💻

How could the transfer of an element from one window to another be realized?

Casting /Hand-off Assignment

04. Visual Handoff Between Windows: You're given a starter sketch with a simple visual and a working SharedWorker; extend it so that when two windows visibly overlap on screen, the visual automatically moves to the other window. 30min

Let's go 👨🏽‍💻

Final Thoughts

There are many technical ways to exchange data between browser windows — from direct messaging with postMessage to reactive broadcasting with BroadcastChannel, or even shared background logic via SharedWorker.

Most approaches ultimately rely on sending messages back and forth, but the real challenge lies in crafting a meaningful design concept around that connection.

In the end, your architecture is just the foundation. What you build on top is what matters most.

By the way, designer and coder Björn Staal works with the localStorage-based approach — showing that even the simplest mechanisms can lead to compelling, interactive experiences if paired with a strong idea.

Physics??? 🧐

Danke für's Mitmachen

https://christiannoss.de