So habe ich meinen animierten Hintergrund gebaut

Der Hintergrund meiner Startseite ist ein 2D-Canvas, kein Video und kein CSS-Farbverlauf. In jedem Frame wird ein weiches Rauschfeld auf einem Raster abgetastet und entschieden, wo kleine Quadrate erscheinen. Hier ist der Effekt Schicht für Schicht erklärt.

01

Ein Rauschfeld abtasten

Ich beginne mit 3D-Simplex-Noise. Die dritte Koordinate bleibt zunächst bei null. Alle 12 CSS-Pixel nehme ich eine Probe und rechne das Ergebnis von ungefähr −1…1 auf 0…1 um. Die Graustufen zeigen die Rohwerte: Benachbarte Werte ändern sich sanft und bilden Wolken statt zufälligem Bildrauschen.

const noise3D = createNoise3D();
for (let y = 0; y <= height; y += 12) {
  for (let x = 0; x <= width; x += 12) {
    const value = (noise3D(x * 0.002, y * 0.002, 0) + 1) / 2;
  }
}
Live-Vorschau · Ein Rauschfeld abtasten

02

Durch das Feld wandern

Die Noise-Funktion hat eine dritte Dimension. Wenn dort die vergangene Zeit eingesetzt wird, entsteht in jedem Frame ein neuer, benachbarter Schnitt. Die Formen verändern sich fließend, ohne dass das ganze Canvas verschoben wird. Mit 0,00004 bleibt die Bewegung dezent (requestAnimationFrame liefert die Zeit in Millisekunden).

const t = time * 0.00004;
const value = (noise3D(x * 0.002, y * 0.002, t) + 1) / 2;
requestAnimationFrame(draw);
Live-Vorschau · Durch das Feld wandern

03

Helle Bereiche auswählen und einfärben

Der Großteil des Feldes soll im dunklen Hintergrund verschwinden. Werte bis 0,6 lasse ich weg und rechne den Rest von 0,6…1 auf 0…1 um. In dieser Zwischenansicht bestimmt der neue Wert die Deckkraft blauer Quadrate. So sieht man die Helligkeitsverteilung vor der endgültigen Ja-oder-nein-Entscheidung.

if (value <= 0.6) continue;
const remapped = (value - 0.6) / (1 - 0.6);
// Nur Vorschau: rgba(76, 169, 239, 0.6 * remapped)
Live-Vorschau · Helle Bereiche auswählen und einfärben

04

Punkte durch Dithering erzeugen

Auf der Startseite wird jedes Quadrat entweder gezeichnet oder ausgelassen. Eine wiederholte 4×4-Bayer-Matrix gibt jeder Rasterposition einen anderen Schwellenwert. Der Vergleich mit der umgerechneten Helligkeit macht aus weichen Flächen gemusterte Gruppen gleichfarbiger blauer Punkte. Die Matrixwerte werden um 0,5 erhöht und durch 16 geteilt, damit sie zwischen null und eins liegen.

const spacing = 12;
if (value <= 0.6) continue;
const remapped = (value - 0.6) / (1 - 0.6);
const bayer = [
  [0, 8, 2, 10], [12, 4, 14, 6],
  [3, 11, 1, 9], [15, 7, 13, 5],
];
const threshold = (bayer[row % 4][col % 4] + 0.5) / 16;
if (remapped > threshold) {
  ctx.fillRect(x - 2.5, y - 2.5, 5, 5);
}
Live-Vorschau · Punkte durch Dithering erzeugen

Alles zusammensetzen

Wechsle zwischen den vier Schichten und sieh dir an, wie aus demselben Rauschfeld der fertige Hintergrund wird: Rohwerte, Bewegung, farbige Highlights und schließlich geditherte Punkte.

Live-Vorschau · Noise

Performance beachten

Das Noise wird in JavaScript abgetastet und das Canvas in jedem Animationsframe gelöscht und neu gezeichnet. Für den Aufwand zählt vor allem die Zahl der Noise-Abtastungen, nicht nur die Zahl der sichtbaren Punkte.

  • Das Raster nicht zu fein machen

    Bei 12 px Abstand braucht ein Canvas mit 1920 × 1080 Pixeln etwa 14.600 Noise-Abtastungen pro Frame. Ein halb so großer Abstand vervierfacht diesen Aufwand ungefähr. Die Rasterweite bestimmt also sowohl das Aussehen als auch die Rechenlast.

  • Unnötiges Zeichnen vermeiden

    Werte unter der Helligkeitsschwelle erreichen fillRect nicht. Darüber werden dank Bayer-Dithering nur Quadrate gezeichnet, die den Schwellenwert überschreiten. Das Noise wird weiterhin an jedem Rasterpunkt abgetastet, aber es werden weniger Rechtecke gemalt.

  • Display und Nutzerwünsche berücksichtigen

    requestAnimationFrame plant die Frames gemeinsam mit dem Browser. Die Canvas-Auflösung folgt devicePixelRatio, wird aber zur Begrenzung des Pixel-Speichers bei 2 gedeckelt. Größenänderungen werden um 120 ms verzögert; bei reduzierter Bewegung gibt es statt einer Animation nur ein Standbild.

  • Nur Sichtbares animieren

    Anders als das einzelne, bildschirmfüllende Canvas der Startseite enthält diese Anleitung mehrere Vorschauen. Jede nutzt einen IntersectionObserver, um außerhalb des sichtbaren Bereichs keine neuen Frames anzufordern und beim Zurückscrollen wieder zu starten.

Auf der Seite einbauen

Das fertige Canvas liegt hinter dem Inhalt der Startseite und füllt seinen Container. In jedem Frame wird es gelöscht und neu gezeichnet. Für hochauflösende Displays skaliere ich die Canvas-Pixel; bei Größenänderungen passe ich sie erneut an. Bei reduzierter Bewegung zeichne ich nur ein Standbild. Die Vorschauen oben pausieren außerdem außerhalb des sichtbaren Bereichs.

Auf der Startseite ansehen →