Raster portrait
Ein Bild aus einem ruhigen Raster. Dieselbe Technik wie im Hintergrund dieser Webseite.
Bewege die Maus über die Vorschau. Das Raster bleibt an seinem Platz, während sich die Striche in der Nähe drehen.
Anpassen
Touch-Geräte zeigen das Bild ohne Mausinteraktion. Bei reduzierter Bewegung erscheint das Ergebnis sofort.
Ein Raster, zwei Aufgaben
Der Hintergrund dieser Webseite und diese Demo verwenden dieselbe Komponente: EngravingCanvas. Sie zeichnet ein transparentes Bild als Punkte und kurze Striche. Außerhalb des Motivs bleibt ein schwaches Grundraster sichtbar. Die Maus dreht die Striche in ihrer Umgebung, ohne die Rasterpunkte zu verschieben.
Es gibt keine Simulation von Geschwindigkeit, Beschleunigung oder Partikelkollisionen. Der Eindruck eines lokalen Flow Fields entsteht durch ein Richtungsfeld, das direkt aus dem Abstand zur Maus berechnet wird. Die festen Positionen halten den Effekt ruhig und lesbar.
1. Das Bild vorbereiten
Die Motive sind lokale WebP-Dateien mit transparentem Hintergrund. Nach dem Laden wird das Bild proportional in ein unsichtbares 2D-Canvas mit 480 × 520 Pixeln eingepasst und zentriert. Der freie Rand bleibt transparent. Mit getImageData() wird dieser Bereich einmal als RGBA-Pixeldaten gelesen. Diese Daten bleiben im Arbeitsspeicher; sie werden nicht in jedem Frame neu aus dem Canvas gelesen.
Der Alpha-Kanal ist hier entscheidend: Er bestimmt, wo das Motiv überhaupt zum Raster beitragen darf. Ein schwarzer Hintergrund im Quellbild wäre ein Teil des Motivs und würde ebenfalls sichtbar werden.
2. Ein gemeinsames Raster erzeugen
Das Raster wird beim Ändern der Canvas-Größe, des Bildes oder der Einstellungen neu berechnet. Seine Positionen liegen in der Mitte gleichmäßig verteilter Zellen. Standardmäßig beträgt der Abstand 6 CSS-Pixel, bei einer Canvas-Breite unter 700 Pixeln 7 CSS-Pixel. Der Regler der Demo überschreibt diesen Ausgangswert.
Der zweite Wert vergrößert den Abstand auf großen Flächen. Damit liegt die Größenordnung bei etwa 40.000 Punkten; durch das Aufrunden von Zeilen und Spalten ist das keine harte Obergrenze.
Es gibt nur ein Raster. Hintergrund und Motiv sind keine überlagerten Partikelschichten. Jeder Punkt bekommt eine Helligkeitsinformation und eine Deckkraft. Im leeren Bereich startet die Deckkraft bei 0,065, auf schmalen Flächen bei 0,045. Die spätere Shader-Berechnung senkt diese Werte nochmals ab.
3. Das Motiv auf das Raster abbilden
Der Bildbereich füllt 96 Prozent der Canvas-Höhe, beginnt bei 4 Prozent der Höhe und ist rechts ausgerichtet. Für seine Breite verwendet der Renderer das gemeinsame Abtastformat mit dem Verhältnis 1205 / 1305. Die verschiedenen Quellbilder wurden zuvor proportional in dieses Format eingepasst.
Aus einer Rasterposition wird eine normierte Bildposition (u, v). Liegt sie innerhalb des Bildbereichs, wird der entsprechende Pixel aus den gespeicherten Daten gelesen. Die Helligkeit wird näherungsweise aus den RGB-Kanälen berechnet:
Die Gewichtung nach rechts hält die linke Seite zurückhaltender. Das ist auf der Webseite der Bereich mit dem Text. Der Kontrastregler verändert nur den zusätzlichen Beitrag des Motivs: Bei null verschwindet das Bild, das Grundraster bleibt bestehen. In dieser Demo startet der Kontrast bei 1,25. imageOpacity: 0.6 ersetzt den zusätzlichen Deckkraftfaktor von 0,16 beziehungsweise 0,48, damit das Motiv auch in kleinen Vorschauen erkennbar bleibt. Die Kartenansicht verwendet 0,72 und einen Rasterabstand von 4 Pixeln. Der Webseiten-Hintergrund behält seine bisherigen Werte.
Die RGB-Gewichtung ist eine gestalterische Helligkeitsnäherung im vorhandenen Farbraum, keine physikalisch genaue Luminanzmessung.
4. Punkte mit WebGL zeichnen
Die CPU packt Ausgangs- und Zielwerte in einen Float32Array und lädt sie in einen GPU-Buffer. Pro Punkt sind zehn Float-Werte hinterlegt: Ausgangsposition, Ausgangshelligkeit und Ausgangsdeckkraft; die entsprechenden vier Zielwerte; sowie zwei Detailwerte. Diese Detailwerte sind im aktuellen Raster null. Es wird kein zufälliges Rauschen pro Frame erzeugt.
Der Vertex-Shader bestimmt die feste Position, die Punktgröße, die Deckkraft und die Strichrichtung. Die Umrechnung von CSS-Pixeln in den WebGL-Koordinatenbereich sieht so aus:
home ist immer die Rasterposition des Ziels. Selbst während eines Bildwechsels bleiben die Punkte deshalb an ihrem Platz. Im dunklen Theme zeichnet der Fragment-Shader weiße Markierungen, im hellen Theme schwarze. Die Helligkeitsgewichtung wird passend dazu umgekehrt.
portrait ist ein weicher Übergang zwischen schwachem Grundraster und stärkerem Motiv, abgeleitet aus der Deckkraft. Er ist keine separate Bildmaske. Die GPU zeichnet alle Punkte mit einem einzigen Aufruf pro Canvas:
Die Vorschau verwendet beim Anlegen des WebGL-Kontexts premultipliedAlpha: true. Der Blend-Aufruf multipliziert die RGB-Ausgabe bereits mit Alpha; diese Einstellung lässt den Browser das Ergebnis passend zusammensetzen. Der bestehende Seitenhintergrund behält seine bisherige Einstellung false, damit sein zurückhaltender Look erhalten bleibt. Dort wird die bereits gewichtete RGB-Ausgabe beim Zusammensetzen nochmals mit Alpha gewichtet. Deshalb ist die Vorschau bei gleichen Rasterwerten deutlicher sichtbar. Die Bildabtastung, Shader und Übergänge sind gemeinsam.
5. Die Maus als lokales Richtungsfeld
Die Mausposition wird am Fenster erfasst. Für die Demo wird der linke und obere Canvas-Rand davon abgezogen, sodass die Rechnung im lokalen Koordinatensystem stimmt. Beim festen Seitenhintergrund entspricht dieses weitgehend dem Viewport.
Die Einflussstärke nimmt mit der Entfernung weich ab. radius ist eine Skala für dieses Abklingen, keine harte Kreisgrenze. Bei genau diesem Abstand beträgt der Einfluss noch etwa 37 Prozent. Die Striche richten sich in der Nähe tangential um die Maus aus. Gleichzeitig werden die Punkte etwas größer und nehmen deutlicher die Strichform an.
Mausposition und Einflussstärke werden zeitabhängig geglättet. Das vermeidet abrupte Sprünge bei unterschiedlichen Bildraten. Beim Verlassen des Fensters oder beim Fokusverlust klingt die Stärke ab. Touch-Ereignisse lösen den Effekt nicht aus.
6. Aus einem Punkt wird ein Strich
Der Fragment-Shader kennt mit gl_PointCoord die Position innerhalb jedes gezeichneten Punktquadrats. Er zentriert und dreht diese Koordinate, berechnet eine weiche Kreisform sowie einen kurzen Strich und mischt beide Formen.
Die weichen Ränder kommen aus diesen Berechnungen. Das WebGL-Canvas selbst wird ohne Antialiasing angelegt. So bleibt die Form kontrollierbar und braucht keine zusätzliche Textur pro Punkt.
7. Aufbau und Bildwechsel
Der Aufbau beginnt rechts. Jeder Punkt erhält eine Verzögerung aus seiner horizontalen Position. Danach führt smoothstep von seinem Ausgangswert zum Zielwert. Mit geladenem Bild läuft die Zeitbasis über 1,65 Sekunden; das Grundraster ohne Bild nutzt 1,8 Sekunden.
detail.x ist aktuell null, daher gibt es keine zufällige Verzögerung. Beim ersten Aufbau startet die Deckkraft bei null. Beim Bildwechsel werden Helligkeit und Deckkraft in die neuen Werte überführt. Wird ein Übergang durch einen weiteren Bildwechsel unterbrochen, berechnet JavaScript zuerst den gerade sichtbaren Zwischenstand. Dieser wird zum Ausgangspunkt des nächsten Übergangs. Eine Anfrage-ID verhindert, dass ein spät geladenes älteres Bild die neuere Auswahl überschreibt.
„Aufbau wiederholen“ setzt die Ausgangsdeckkraft bewusst wieder auf null. Änderungen der Regler werden unmittelbar angewendet. Eine Größenänderung berechnet das Raster neu und übernimmt sofort das neue Layout.
8. Aufwand und Grenzen
Die Bildabtastung läuft nur nach dem Laden eines Motivs. Raster und GPU-Buffer werden nur bei Änderungen neu erstellt. Die laufende Animation verändert Uniforms; React rendert nicht jeden Frame neu.
Die Zeichenrate ist auf etwa 30 Frames pro Sekunde begrenzt. requestAnimationFrame prüft weiterhin die Zeit, gezeichnet wird erst nach mindestens 33,3 Millisekunden. Die Pixeldichte wird auf 1,5 begrenzt. Ein unsichtbarer Browser-Tab oder ein Canvas außerhalb des sichtbaren Bereichs pausiert die Schleife. Bei reduzierter Bewegung gibt es keinen laufenden Aufbau und keine Mausanimation; Änderungen werden statisch gezeichnet.
Solange das Canvas sichtbar ist, läuft die Schleife auch nach einem fertigen Übergang weiter. Ein einzelner Draw-Aufruf ist außerdem keine Garantie für dieselbe Leistung auf jedem Gerät. GPU, Fläche und Browser spielen weiterhin eine Rolle. Bei Verlust des WebGL-Kontexts stoppt der Renderer; eine automatische Wiederherstellung ist derzeit nicht implementiert.
Das Canvas ist dekorativ und vor Screenreadern verborgen. Die beschrifteten Regler sind normale HTML-Inputs und funktionieren per Tastatur. Bei fehlendem WebGL oder einem Ladefehler zeigt die Demo einen Hinweis; der Artikel bleibt zugänglich. Externe Bilder würden für das Auslesen ihrer Pixel zusätzlich eine passende CORS-Freigabe benötigen.
Quellen und Code
- Gemeinsamer Renderer:
src/components/engraving-canvas.tsx - Einbindung auf der Webseite:
src/components/page-halftone.tsx - Demo:
src/components/experiments/raster-portrait-demo.tsx - Pixeldaten mit getImageData lesen — MDN
- Punkte mit drawArrays zeichnen — MDN