1 Punkte von GN⁺ 2025-02-22 | 1 Kommentare | Auf WhatsApp teilen
  • Ein Pong-Experiment wurde umgesetzt, das den freien Platz einer nicht geschlossenen Tab-Leiste wie einen Bildschirm nutzt und die Favicons von 240 Chrome-Tabs als Pixel verwendet
  • Das 8×30-Tab-Raster wird per AppleScript erzeugt, und jeder Tab berechnet seine Position über windowIndex und tabIndex in den URL-Query-Parametern
  • setInterval in Hintergrund-Tabs wurde auf etwa 1 Sekunde verlangsamt, aber nachdem der Timer in einen Web Worker verlegt wurde, konnten die Favicons wieder schnell genug aktualisiert werden
  • Statt eines serverseitigen WebSocket werden Tabs derselben Domain über die Broadcast Channel API synchronisiert, und der Haupt-Tab startet die Animation erst, nachdem alle Tabs registriert sind
  • Der größte Flaschenhals war das Neuerzeugen von Hunderten Favicons pro Frame; nachdem nur noch tatsächlich geänderte Tabs aktualisiert wurden, lief die Animation deutlich flüssiger

Wie 240 Tabs wie ein einzelner Bildschirm genutzt werden

  • Das Projekt ist ein Experiment, bei dem der von offenen Browser-Tabs belegte Raum genutzt wird, um Pong auszuführen
  • Der Bildschirm besteht aus einem 8×30-Tab-Raster, aufgebaut aus 8 Chrome-Fenstern mit jeweils 30 Tabs
  • Ball und Schläger bewegen sich zwischen dem HTML-Canvas im Vordergrundfenster und dem Favicon-Bereich der oberen Tab-Leiste hin und her
  • Der Code ist auf GitHub unter faviconic veröffentlicht und mit dem Hinweis versehen, dass es sich um „awful“ Prototyp-Code handelt

Die Idee begann mit Flappy Favi

  • Die Inspiration kam von Flappy Favi, einer von Tru gebauten Flappy-Bird-Version, die innerhalb eines Favicons läuft
  • Ein Favicon ist ein kleines Bild in der Browser-Tab-Leiste, aber allein ist es zu klein, um gut sichtbar zu sein
  • Um ein Bild über mehrere Tabs hinweg zu zeichnen, mussten zunächst drei Probleme gelöst werden
    • Wie sich Tabs in einem ansehnlichen Raster anordnen lassen
    • Wie sich Favicons auch in Hintergrund-Tabs schnell aktualisieren lassen
    • Wie sich die Bildschirme mehrerer Tabs synchronisieren lassen

Mit AppleScript ein Chrome-Tab-Raster erzeugen

  • Zunächst wurde die Machbarkeit geprüft, indem in Chrome wiederholt auf den Button für neue Tabs geklickt wurde, um Tabs schmaler zu machen, und ein zweites Fenster passend platziert wurde, um eine zweite Zeile zu bilden
  • Da das manuelle Erstellen eines großen Rasters umständlich war, wurde dies mit AppleScript automatisiert
  • Das Skript öffnet 8 Chrome-Fenster, erstellt in jedem Fenster 30 Tabs und positioniert die Fenster so, dass sie übereinander gestapelt sind
  • Da Chrome versuchte, geschlossene Tabs wiederherzustellen, musste das zu Beginn des Skripts bereinigt werden
  • In jeder Tab-URL sind windowIndex und tabIndex enthalten
    • Der zuletzt geöffnete Tab erhält zusätzliche Parameter wie isMain=true, numWindows, numTabs und fullWidth

Favicons in Hintergrund-Tabs schnell aktualisieren

  • Der Browser sucht standardmäßig nach der Favicon-URL, aber wenn im HTML-head ein Element mit dem Favicon-Pfad hinzugefügt und aktualisiert wird, lässt sich das Icon austauschen
  • Die Favicon-Aktualisierung in Chrome fühlt sich auf ungefähr 4-mal pro Sekunde begrenzt an
  • In Hintergrund-Tabs ist die Ressourcennutzung eingeschränkt, sodass selbst eine kleine Schleife, die das Favicon alle 250 ms ändern sollte, praktisch nur etwa einmal pro Sekunde lief
  • Es wurde versucht, den Hintergrund-Audio-Callback der Web Audio API als Umgehung zu nutzen, was jedoch nicht zuverlässig funktionierte
  • Danach wurde der Timer in einen Web Worker verlegt; der Worker sendet Nachrichten an das Hauptdokument, das daraufhin das Favicon aktualisiert, und damit war das Problem gelöst
  • Die Beispielstruktur funktioniert so, dass der Worker ein Emoji auf ein OffscreenCanvas zeichnet, in eine data URL umwandelt und an das Hauptdokument sendet
    • Das Hauptdokument aktualisiert dann das eigentliche Favicon, indem es das href von link rel="shortcut icon" ändert

Tab-Registrierung und Synchronisierung per Broadcast Channel

  • Für die Kommunikation zwischen Tabs mussten zwei Aufgaben gelöst werden
    • Jeder Tab muss wissen, in welchem Fenster und an welcher Tab-Position er sich befindet
    • Es musste entschieden werden, über welchen Kommunikationskanal Updates an die Tabs übermittelt werden
  • Die Positionsbestimmung wurde gelöst, indem windowIndex und tabIndex aus den von AppleScript eingefügten URL-Query-Parametern gelesen wurden
  • Das erste Proof of Concept verband jeden Tab mit einem WebSocket-Server, der die Bilder auslieferte
  • Der WebSocket-Ansatz passte jedoch nicht gut zum Ziel des Projekts
    • Er kollidierte mit dem ästhetischen Wunsch, das Ganze serverlos in Browser-Tabs weltweit bereitstellen zu können
    • Weil Tabs sich zu unterschiedlichen Zeitpunkten verbinden, wurden Updates nicht synchronisiert
  • Danach wurde auf die Broadcast Channel API umgestellt, die Informationen an mehrere Tabs derselben Domain übermitteln kann
  • Ein Hintergrund-Tab sendet eine registration-Nachricht mit seinem Tab-/Fenster-Index über den Broadcast Channel, und der Haupt-Tab antwortet mit einem ack
  • Der Haupt-Tab startet die Animation erst, nachdem er die Registrierung aller Hintergrund-Tabs erhalten hat

Canvas und Favicons korrekt ausrichten

  • Damit es so aussieht, als würden im Vordergrundfenster gezeichnete Objekte direkt in die Tab-Leiste „hineinfliegen“, werden das eigentliche Canvas und die Favicons oben wie ein einziges erweitertes Canvas behandelt
  • Für die Umsetzung mussten die tatsächlichen Maße des Chrome-Fensters direkt vermessen werden
    • 92 Pixel vom linken Fensterrand bis zum ersten Favicon
    • 58 Pixel von der Unterkante des Favicons bis zur Oberkante des eigentlichen Fensters
    • Die Favicon-Größe beträgt 16×16
  • Mit diesen Messwerten und der Zahl der Tabs/Fenster werden drei Werte berechnet
    • Die Breite des an den Favicons ausgerichteten sichtbaren Canvas
    • Die Breite jedes Tabs
    • Die Gesamtbreite und -höhe des gesamten „Canvas“, einschließlich Favicon-Zeile, URL-Leiste und eigentlichem Canvas
  • Wenn sich ein Rechteck unterhalb der URL-Leiste befindet, wird es auf das eigentliche Canvas gezeichnet; liegt es oberhalb der URL-Leiste, werden die Bereichsinformationen an andere Tabs gesendet
  • Jeder Tab berechnet dann seine eigenen Pixelkoordinaten und aktualisiert sein Favicon als weiße/schwarze Pixel

Der Flaschenhals war nicht die Datenübertragung, sondern die Favicon-Erzeugung

  • Die erste Implementierung funktionierte zwar, aber die Canvas-Animation ruckelte, und selbst beim Zeichnen innerhalb von requestAnimationFrame lief sie nicht flüssig
  • Zunächst wurde vermutet, dass der Main Thread zu viele Daten kopiert, weil er die Pixelzustände aller Favicons berechnet, in den Broadcast Channel schreibt und Hunderte Tabs diese wieder lesen
  • Danach wurde nur noch die Position des Rechtecks gesendet, und jeder Tab berechnete selbst, ob es eine Überlappung gibt, doch die Performance verbesserte sich nicht
  • Das eigentliche Problem war, dass jeder Tab in jedem Frame ein 4×4-Schwarzweiß-Favicon neu erzeugte und mit toDataURL("image/png") eine URL dafür erzeugte
  • Selbst wenn sich das Ergebnis-Canvas nicht änderte, erzeugten Hunderte Tabs mehrmals pro Sekunde immer wieder dieselben kleinen weißen Favicons neu
  • Nachdem nur noch dann aktualisiert wurde, wenn sich das Favicon tatsächlich änderte, verbesserte sich die Performance deutlich
  • Warum die Arbeit in den anderen Tabs die Animation im Vordergrund-Tab so stark verlangsamte, blieb allerdings letztlich unklar

Warum Pong statt Snake gewählt wurde

  • Nach dem Experiment mit bewegten Rechtecken wurde eine kleine „Engine“ aufgeräumt und entschieden, welches Spiel darauf gebaut werden sollte
  • Der erste Kandidat war Snake, da es blockbasiert ist und daher gut zu Favicons zu passen schien
  • Es wurde jedoch entschieden, dass Snake zu stark blockbasiert ist und dadurch der Effekt verloren geht, dass kontinuierliche Bewegung auf dem Vordergrund-Canvas in diskrete Pixel in der Tab-Leiste übergeht
  • Am Ende fiel die Wahl auf Pong
    • Ball und Schläger müssen häufig zwischen Canvas und Tab-Leiste hin und her wechseln
    • Genau diese Bewegung passt gut zum visuellen Effekt des Projekts

Notizen zur Pong-Implementierung

  • Nachdem die API zum Zeichnen von Favicons bereitstand, war die eigentliche Pong-Implementierung vergleichsweise einfach
  • Der rechte Computergegner versucht stets, die Mitte seines Schlägers auf die Mitte des Balls auszurichten
  • Der Winkel, in dem der Ball vom Schläger abprallt, wird mit einfacher Trigonometrie anhand der relativen Position zum Schlägerzentrum berechnet
  • Dieser Ansatz ist nicht realistisch, verhindert aber, dass der Ball immer denselben Winkel beibehält
  • Für die Kollisionserkennung wird eine Funktion verwendet, die prüft, ob sich zwei Rechtecke schneiden
  • Um den Effekt zu betonen, dass Ball und Schläger flüssig in die Favicons hineinlaufen, wurde dem Ball ein Trail hinzugefügt

Das vierte Spiel, das im Recurse Center entstand

  • Das Projekt wurde während eines Batches im Recurse Center entwickelt
  • Das Recurse Center wird als eine kostenlose Art Writer’s Retreat fürs Programmieren beschrieben
  • Dieses Projekt war das vierte Spiel, das in etwa 40 Tagen entstanden ist
  • Interessierte Leser werden dazu ermutigt, eine Bewerbung beim Recurse Center in Betracht zu ziehen

1 Kommentare

 
GN⁺ 2025-02-22
Hacker-News-Kommentare
  • Hallo! Das habe ich gebaut. Ich war neugierig, ob es bei den HN-Lesern gut ankommen würde :)
    Ich beantworte gern Fragen. Ein paar verstreute Gedanken dazu: Ich bin wirklich neugierig, wie das mit animierten Favicons aussehen würde. Firefox unterstützt animierte Favicons, also könnte man zum Beispiel die zukünftige Position des Balls vorhersagen und ein animiertes SVG erzeugen, was vermutlich eine deutlich bessere Framerate bringen würde.
    Ein Freund hat mir offline gesagt, dass die Rasterisierung von canvas normalerweise auf der GPU passiert, daher lag meine Performance-Intuition, die ich wegen der ruckelnden Animation hatte, wohl ziemlich daneben.
    Ich bin mir nur einigermaßen sicher, dass Chrome Favicon-Updates auf viermal pro Sekunde begrenzt. Es gibt mehrere Möglichkeiten, ein Favicon zu aktualisieren, also könnte es sein, dass ich etwas übersehen habe

    • Unter Leuten, die absurdes Pong gebaut haben: wirklich großartige Arbeit. Diese Art von technischer Performance-Kunst macht sowohl beim Bauen als auch beim Zuschauen Spaß. Vielleicht hängt das sogar mit dem Grund zusammen, warum ich jongliere: „Es muss doch einen Weg geben, das noch schwieriger zu machen“
    • Hast du das Problem inzwischen gelöst? Als einfaches Material zum Entwurf von JavaScript-Spielen ist Jake Gordons Blog gut. Er verwendet eine doppelt gepufferte Spielschleife mit vorderem und hinterem canvas.
      [0] https://jakesgordon.com/writing/javascript-pong/part1/
    • So gut, und dein Arbeitstempo ist wirklich unglaublich!
      Dass du seit 1 Million Checkboxes auch noch das hier, global cap locks, das Bad-Apple-RegEx, uuid und so weiter gebaut hast, ist ein beeindruckender Vorwärtsdrang
    • Jetzt will ich Doom sehen, das in Browser-Tabs läuft
  • Ich habe Nolens jüngsten Recurse-Vortrag gesehen, und diese komplett verrückten, aber im Kern urkomischen und großartigen Wegwerfspiele machen wirklich Spaß.
    Das erinnert mich an das frühere Internet. An die Zeit, als Leute einfach irgendetwas gebaut haben, nur um albern zu sein und Spaß zu haben. Letzte Nacht habe ich, inspiriert von seinen geposteten Sachen, mit BEAM-Decompiling und ein paar Tricks ein sehr lustiges Pseudo-Quine gebaut, bei dem die Seite ihren eigenen Quellcode ausgibt.
    Ich wünschte, ich hätte Zeit, selbst fortlaufend solche Dinge herauszuhauen, und allein die Tatsache, dass es Leute gibt, die auf diese Weise weiter interessante Dinge bauen, bringt mich zum Lachen.
    Für alle, die lachen wollen, ist hier der sort-of-quine-Teil: https://github.com/notactuallytreyanastasio/blog/blob/main/l...

  • Alles, was Nolen macht, gefällt mir. Für mich trifft es genau den Sweet Spot von Single-Purpose-Apps/-Sites, die in mir Nostalgie dafür wecken, wie sich das alte Internet angefühlt hat

  • Es gibt auch ein ähnliches Experiment von Matthew Rayfield, das statt Tab-Favicons die Adressleiste verwendet: https://www.youtube.com/watch?v=q7GtCLwTmV4

    • Matts Arbeit ist eine große Inspiration für mich!
      Als ich das hier gebaut habe, hatte ich seine URL-Arbeit definitiv im Hinterkopf, aber es war schon eine ganze Weile her, dass ich tatsächlich den Blog gelesen oder die Videos gesehen hatte. Es war wirklich lustig, mitten im Projekt wieder darauf zurückzukommen und mich daran zu erinnern, dass auch er in die Richtung „2D durch Stapeln von Fenstern erzeugen“ gegangen ist
  • Das erinnert mich daran:
    „Show HN: I saw this mind-blowing experiment, so I made a simple version of it“ (25.11.2023) https://news.ycombinator.com/item?id=38413660
    „Synchronize a 3D scene across multiple windows using Three.js and localStorage“ (27.11.2023) https://news.ycombinator.com/item?id=38437773

  • Ich behaupte: Als Nächstes kommt Doom

    • Doom ist etwas lästig. Soweit ich weiß, gibt es die Begrenzung auf 4 Frames pro Sekunde, also darauf, wie oft man Tabs überhaupt aktualisieren kann.
      Außerdem würde sich pro Frame wahrscheinlich ein viel größerer Teil des Bildschirms ändern, weshalb man deutlich mehr „canvas in Data-URL umwandeln“ bräuchte, um das Favicon zu aktualisieren. In der aktuellen Implementierung ist das teuer. Von OffscreenCanvas wusste ich allerdings nichts; das könnte helfen
    • Falls das dein Geschmack ist: Soweit ich weiß, hat Nolen bereits Doom in iPhoto zum Laufen gebracht
  • Falls das Problem in Firefox die schmaler werdenden Tabs sind: Man kann vermutlich immer noch den Firefox-Browser selbst inspizieren, die Stilregel für die minimale Tab-Breite finden und sie in userChrome.css eintragen.
    Dazu gibt es ein paar Anleitungen, und wahrscheinlich hat jemand für die Tab-Breite schon etwas vorbereitet.
    Wobei das vielleicht schon als Schummeln gilt. browser.tabs.tabMinWidth gibt es auch, aber soweit ich mich erinnere, erzwingt es immer noch einen Mindestwert

  • Die Band Ok Go hatte früher einmal ein Musikvideo, das sie zusammen mit Google Chrome gemacht haben, bei dem Browserfenster und Tänzer wirklich großartig synchronisiert waren und kaleidoskopartige Effekte entstanden. Daran musste ich hierbei denken