Erstellung eines Plexus-Effekts mit Plain JavaScript

In unserem zweiten Blogbeitrag aus dem "Tagebuch eines Entwicklers" stellen wir Euch die Erstellung eines Partikel-Plexus mit Plain JavaScript vor.
Unser Entwickler Daniel Fischer erklärt Euch wieder in einzelnen Arbeitsschritten mit anschaulichen Beispielen seine persönliche Herangehensweise bei der Umsetzung.

Der Effekt

Im Folgenden erstelle ich für Euch einen interaktiven Partikeleffekt (sog. Plexus) mit purem JavaScript und dem HTML5-Canvas.

Unser Endergebnis sieht so aus (ihr könnt die Partikel  mit der Maus verschieben):

Der Code steht am Ende zum Download bzw. zum Kopieren bereit.

Um diesen Effekt nachzubauen, brauchen wir lediglich eine HTML-Datei.

Hier die grobe Funktionsweise des Effekts:

  • Wir erstellen zufällige Punkte auf dem Canvas.

  • Jeder Punkt hat eine Position und eine Velocity, also eine Geschwindigkeit bzw. Richtung, in die er sich bewegt.

  • Jeder Punkt muss überprüfen, ob er nah genug an einem anderen Punkt ist. Falls ja, werden wir eine Linie zwischen ihnen zeichnen.

  • Jeder Punkt muss überprüfen, ob er nah genug an der Maus ist, um dann wegbewegt zu werden.

Dies ist zwar nicht die beste Methode, aber die einfachste. Weitere Infos dazu folgen am Ende meines Blogbeitrags.

Aber kurz vorab noch etwas Vektorrechnung!

Wir brauchen Vektoren, um die Partikel zu bewegen und um Distanzen und Kräfte zu berechnen.

Was sind Vektoren?

Jede Position und Velocity werden wir als Vektor mit 2 Komponenten speichern (x und y).  Einen Vektor könnt Ihr Euch als Pfeil vom Ursprung eines Koordinatensystems zu den x- und y-Werten vorstellen bzw. als einen Pfeil von einem Punkt zu einem anderen:

Screenshot von www.geogebra.org/classic

Hier sieht man den Vektor von A nach B.
Wenn wir nun A und B mit unseren Partikeln und den Pfeil durch unsere Linie ersetzen, haben wir bereits z. T. unseren gewünschten Effekt erzielt.

Länge von Vektoren

Die Länge eines Vektors ist für uns ebenfalls sehr nützlich. In diesem Fall ist diese Länge nämlich auch gleichzeitig die Distanz zwischen den Punkten.

Wie oben schon angemerkt, brauchen wir diese, um die Abstände zwischen den Partikeln und zur Maus zu bestimmen. Um die Länge zu berechnen, nehmen wir einfach folgende Formel:

Screenshot von de.serlo.org/mathe/1777/l%C3%A4nge-eines-vektors


Die länge des Vektors (2, 2) aus dem Bild oben beträgt also:

Vektoren normalisieren

Das hört sich kompliziert an, ist aber ganz einfach.
Beim Normalisieren eines Vektors teilen wir diesen einmal durch seine Länge bzw. multiplizieren ihn mathematisch mit dem Kehrwert der Länge.
So hat er nur noch die Länge 1.

Warum sollten wir Vektoren normalisieren?

Wir können Vektoren nicht nur als Positionen benutzen, sondern auch als Kraft  und Bewegungsrichtung interpretieren.
In unserem Fall werden die Partikel z.B. mit einer "Kraft" von der Maus wegbewegt.

Dazu nutzen wir die Distanz zwischen den Partikeln und der Maus.
Da diese Distanz bei allen Partikeln unterschiedlich ist, wäre auch die Kraft unterschiedlich, die wir brauchen, um sie zu verschieben.
Damit das Wegschieben einheitlich aussieht, normalisieren wir die Vektoren.

Genug Mathematik, auf zur Implementierung mit Code!

Datei-Setup

Alles was wir brauchen,  ist eine leere HTML-Datei. Diese lässt sich direkt mit Google Chrome, Firefox etc. öffnen.

HTML-Code

Ganz oben in der Datei definieren wir einen Canvas mit einer ID. Den Inhalt der ID können wir uns selbst aussuchen.

Darunter erstellen wir den Script-Tag, der unser JavaScript enthalten wird:

<canvas id="particles" style="background-color: black;"></canvas>

<script>

</script>
            Code kopieren
         

JavaScript-Code

Ich empfehle Euch, den Code in eine eigene Datei auszulagern. Dies bleibt aber Eure Entscheidung.  
Im Folgenden gehe ich auch nicht auf die JavaScript-Programmiersprache und die Syntax selbst ein, da dies ein riesiges Thema für sich ist.

Klassen/Objekte definieren

Zuerst müssen wir unsere Partikel und Vektoren definieren, um später Objekte von ihnen zu erstellen.

Ganz oben im Script erstellen wir zuerst die Definition für einen Vektor.

Dieser hat einen x- und einen y- Wert sowie eine Methode, die uns seine Länge wiedergibt, sowie eine Methode die uns den normalisierten Vektor wiedergibt:

vector2 = function (x, y) {
    this.x = x;
    this.y = y;

    this.length = () => {
         return Math.sqrt(this.x * this.x + this.y * this.y);
    }

    this.normalize = () => {
        let len = this.length();
        return new vector2(this.x / len, this.y / len);
    }
}
            Code kopieren
         

Die Definition für ein Partikel ist etwas größer. Hier benutzen wir unseren definierten vector2 als Position und Velocity.
Dazu brauchen wir noch einen Radius und eine Farbe.
Zusätzlich ist eine Methode erforderlich, die den Punkt auf das Canvas malt.

 particle = function (position, velocity, radius, color = "white") {
    this.pos = position;
    this.vel = velocity;
    this.radius = radius;
    this.color = color;

    this.draw = (ctx) => {
        ctx.beginPath();
        ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2);
        ctx.closePath();
        ctx.fillStyle = this.color;
        ctx.fill();
    }
}
            Code kopieren
         

Jetzt benötigen wir noch eine Methode, die unsere Partikel basierend auf ihrer Velocity bewegen kann. Allerdings müssen wir dabei aufpassen, dass wir nicht aus dem Canvas "rausfliegen". ;-)

this.basicMovement = () => {
            //wenn wir die Grenzen vom Canvas erreichen, soll die Richtung geändert werden!
    if (this.pos.x + this.radius > canvasWidth || this.pos.x - this.radius < 0) this.vel.x *= -1;
    if (this.pos.y + this.radius > canvasHeight || this.pos.y - this.radius < 0) this.vel.y *= -1;

    this.pos.x += this.vel.x;
    this.pos.y += this.vel.y;
}
            Code kopieren
         

Mit einer letzten Methode für die Partikel führen wir die Bewegung aus und machen die Interaktion mit der Maus möglich. Danach zeichnen wir die Partikel auf das Canvas.

this.update = (ctx) => {
    //wenn wir Partikel nicht verschieben wollen, oder die Maus nicht auf dem Canvas ist
    if (!pushParticlesWithMouse || (mouse.x === undefined && mouse.y === undefined)) {
            //Normale bewegung mittels unserer Velocity
            this.basicMovement();
    } else {
           //Partikel mit Maus wegbewegen

           //Vektor von Partikel zur Maus
           let particleToMouse = new vector2(mouse.x - this.pos.x, mouse.y - this.pos.y);
           //Damit ist die Distanz zur Maus die Länge des Vektors
           let distance = particleToMouse.length();
           //Jetzt wird der Vektor normalisiert (durch seine Länge geteilt) -> also seine Länge wird jetzt 1 sein
          let force = new vector2(particleToMouse.x / distance, particleToMouse.y / distance);

          //wenn wir zu nah an der Maus sind
          if (distance <= maxDistToMouse) {

          //Wieder auf die größe des Canvas achten
          if (this.pos.x + this.radius > canvasWidth || this.pos.x - this.radius < 0) force.x = 0;
          if (this.pos.y + this.radius > canvasWidth || this.pos.y - this.radius < 0) force.y = 0;
              //jetzt können die Partikel verschoben werden
              this.pos.x -= force.x * pushForce;
              this.pos.y -= force.y * pushForce;
          } else {
                //wenn wir nicht mit zu nah an der Maus sind

               //berechne unsere Position voraus auf die wir uns zu bewegen mit unserer Geschwindigkeit
               let desiredPos = new vector2(this.pos.x + this.vel.x, this.pos.y + this.vel.y);
               //Vektor von berechneter Position zur Maus
               let desiredToMouse = new vector2(mouse.x - desiredPos.x, mouse.y - desiredPos.y);
               //Berechne erneut unsere Distanz zur Maus ausgehend von unserer vorausgerechneten Position
               let distance = desiredToMouse.length();

               //wenn wir nun wieder zu nah der Maus sind
               if (distance <= maxDistToMouse) {
                   //Kehre um
                   this.vel.x *= -1;
                   this.vel.y *= -1;
               } else {
                   //Sonst bewege dich normal weiter
                   this.basicMovement();
                 }
            }
        }
    //Partikel anzeigen lassen
    this.draw(ctx);
}
            Code kopieren
         

Als nächstes erstellen  wir unter der Definition des Partikels alle erforderlichen Variablen, mit denen wir zum Beispiel die Größe des Canvas, die Anzahl oder die Geschwindigkeit der Partikel einstellen können:

const canvasWidth = 574; //beliebiger Wert für die Breite des Canvas
const canvasHeight = 270;//beliebig Wert für die Höhe des Canvas
canvas.style.maxWidth = canvasWidth + "px";
canvas.style.maxHeight = canvasHeight + "px";

const pixelRatio = window.devicePixelRatio;
canvas.width = pixelRatio * canvasWidth;
canvas.height = pixelRatio * canvasHeight;

let ctx = canvas.getContext("2d"); //Wird benutzt um später Objekte zu zeichnen auf dem Canvas
ctx.scale(pixelRatio, pixelRatio);

const mouse = new vector2(0, 0);

const particleColor = "crimson";
const lineColor = "white";

const particles = [];
const particleAmount = 50;
const particleRadius = 2;
const particleSpeed = 1.5;

const maxDistBetweenParticles = 50; //Distanz in pixel zwischen Partikeln, damit eine Linie gezeichnet wird

const maxDistToMouse = 50; //Distanz in pixel zwischen Partikel und Maus, damit Partikel weggeschoben werden
const pushParticlesWithMouse = true;
const pushForce = 8; //Kraft mit der Partikel weggeschoben werden

            Code kopieren
         

Funktionen hinzufügen

Nun haben wir alle erforderlichen Zutaten für unseren Partikeleffekt.

Bevor wir die Partikel aber auf das Canvas malen können, fehlen noch folgende Schritte.
Wir müssen erst:

  • die Maus-Events zum Canvas hinzufügen.

  • die Partikel erstellen lassen.

  • die Animation starten.

Maus-Events

Wir brauchen zwei Maus-Events.

Das erste Event speichert uns die Position der Maus relativ zum Canvas, wenn wir die Maus über das Canvas bewegen.

Das zweite Event setzt die Position der Maus zurück, falls die Maus nicht mehr auf dem Canvas ist:

function addMouseEvents() {
    //Koordinaten unseres Canvas relativ zum HTML-Dokument und Scroll Position
    const canvasRect = canvas.getBoundingClientRect();
    const offset = {
        top: canvasRect.top + window.scrollY,
        left: canvasRect.left + window.scrollX,
    }

    canvas.addEventListener('mousemove', function (event) {
        //Mausposition relativ zum Canvas
        mouse.x = event.pageX - offset.left;
        mouse.y = event.pageY - offset.top;

        mouse.x = clamp(mouse.x, 0, canvasWidth);
        mouse.y = clamp(mouse.y, 0, canvasHeight);
    });

    canvas.addEventListener('mouseleave', function () {
        mouse.x = undefined;
        mouse.y = undefined;
    });
}
            Code kopieren
         

Die clamp-Funktion schränkt den Wert nochmal zwischen 0 und der Breite bzw. Höhe des Canvas ein.
Der entsprechende Codesieht so aus:

    function clamp(val, min, max) {
        if (val < min) return min;
        if (val > max) return max;
        return val;
    }

            Code kopieren
         

Partikel erstellen

Jetzt generieren wir die Partikel mit zufälligen Werten und speichern diese in einem Array.

Zufällig sind hier jeweils Position und Velocity, wobei die Werte der Position irgendwo zwischen 0 und der Breite/Höhe des Canvas liegen können.


Die Velocity liegt jedoch nur zwischen -0.5 und 0.5. Wir multiplizieren diese dann mit der Partikelgeschwindigkeit.
Hier müssen wir die Velocity noch normalisieren, damit sich alle Partikel gleich schnell, aber in verschiedene Richtungen bewegen:

function createParticles() {
    for (let i = 0; i < particleAmount; i++) {
        //zufällige Position erstellen
        let x = Math.random() * canvasWidth;
        let y = Math.random() * canvasHeight;
        let position = new vector2(x, y);

        //zufällige Velocity erstellen
        let dx = (Math.random() - 0.5) * particleSpeed;
        let dy = (Math.random() - 0.5) * particleSpeed;
        let velocity = new vector2(dx, dy);

        //normalisieren
        velocity = velocity.normalize();
        particles.push(new particle(position, velocity, particleRadius, particleColor));
    }
}
            Code kopieren
         

Fast fertig!

Partikel animieren

Wir müssen die Funktion zum Animieren des Canvas immer wieder neu aktualisieren, um Bewegungen darstellen zu können.
Sonst hätten wir  einfach nur ein statisches Bild.

 

Jedes Partikel muss dann einmal die Distanz zu jedem anderen Partikel überprüfen.

Wenn diese Distanz klein genug ist, zeichnen wir eine Linie zwischen den Partikeln. Erst nach dieser Berechnung dürfen sich die Partikel bewegen.

Das Ganze ist allerdings sehr rechenintensiv.
Bei 1000 Partikeln hätten wir  ca. O(n2 ) - 1 = (1000 * 1000) - 1 = 999.999 Berechnungen,
da jedes Partikel jedes andere Partikel außer sich selbst überprüfen muss .

Einen besseren Ansatz spreche ich noch am Ende meines Blogbeitrags an.
Hier der Code, mit dem wir die Partikel animieren können:

function animate() {
    //animate Funktion immer wieder aktualisieren
    requestAnimationFrame(animate);
    //letztes Frame leeren, um das zu verstehen, die Zeile einfach mal auskommentieren und testen
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);

    //für jedes Partikel
    for (let i = 0; i < particleAmount; i++) {
        //hole die Position des aktullen Partikels
        let currentPosition = particles[i].pos;

        //Für jedes andere Partikel
        for (let j = 0; j < particleAmount; j++) {
            //Außer für sich selbst
            if (particles[i] === particles[j]) continue;

            //wenn i + j kleiner als unsere Partikelanzahl, dann nimm die Position des "i+j-ten" Partikels, sonst fange von hinten wieder an
            let nextPosition = (i + j) < particleAmount - 1 ? particles[i + j].pos : particles[particleAmount - j - 1].pos;

            //Distanz zwischen den beiden Partikeln berechnen
            let distX = Math.abs(nextPosition.x - currentPosition.x);
            let distY = Math.abs(nextPosition.y - currentPosition.y);

            //wenn Distanz klein genug, dann zeichne eine Linie
            if (distX <= maxDistBetweenParticles && distY <= maxDistBetweenParticles) {
                ctx.beginPath();
                //starte beim aktuellen Partikel
                ctx.moveTo(currentPosition.x, currentPosition.y);
                //und ziehe eine Linie zum nächsten
                ctx.lineTo(nextPosition.x, nextPosition.y);
                //Zeichne die Linie mit unserer Farbe
                ctx.strokeStyle = lineColor;
                ctx.stroke();
            }

        }
        //erst nach allen Berechnungen Partikel aktualisieren
        particles[i].update(ctx);
    }
}
            Code kopieren
         

Fertig!

Der Effekt ist nun fertig. Um ihn noch etwas zu verbessern, könnten wir z. B.  mehrere Farben hinzufügen oder das Canvas responsiv machen etc.

Das ist jetzt allerdings Eure Aufgabe! ;-)

Partikel effizienter animieren

In diesem Blog genutzte Methode

Wie vorhin schon angemerkt, zählen wir ca. 1 Millionen Berechnungen bei 1000 Partikeln.
Da unser Vorgehen eine Komplexität von ca. O(n2) hat, steigt die Anzahl der Berechnungen exponentiell.

Für große Partikelmengen ist diese Methode also nicht geeignet und insbesondere auf Mobilgeräten nur sehr eingeschränkt nutzbar.

 

Die Lösung heißt " Quadtree"!  

Ein Quadtree ist eine Daten-/Baumstruktur, mit der wir einen 2D-Raum einteilen können.
Hier ein Beispiel aus einer Implementation von mir:

Die grünen Quadrate symbolisieren jeweils einzelne Knoten der Baumstruktur und können wiederum in 4 weitere Quadrate aufgeteilt werden, sobald zu viele Partikel in einem Quadrat sind.

Hier muss also nicht jedes Partikel alle anderen überprüfen. Stattdessen kann jedes Partikel die jeweiligen Partikel in seiner Nähe abfragen.

Mit diesem Ansatz sinkt die Komplexität auf ca. O (n * log10(n)) und bei 1000 Partikeln auf ca. 1000 * log10(1000) = 3000.

 

Grafischer Vergleich

Hier seht Ihr eine grafische Darstellung der Anzahl der Berechnungen beider Ansätze.
Grün steht für den alten Ansatz und Rot für den Quadtree-Ansatz:

Screenshot von www.geogebra.org/classic

 

Einen Quadtree könnt Ihr ebenfalls in einem dreidimensionalem Raum anwenden.
Dann heißt diese Baumstruktur Octree.

Weiterführende Infos zu Quadtree und Octree findet Ihr hier:
https://www.youtube.com/watch?v=OKiBmQ6ZNyU

Falls Ihr darüber hinaus Interesse habt, selbst einen Quadtree zu implementieren, empfehle ich Euch abschließend auch dieses Video:
https://www.youtube.com/watch?v=OJxEcs0w_kE

Hier der vollständige Code:

<canvas id="particles" style="background-color: black;"></canvas>

<script>
    vector2 = function (x, y) {
        this.x = x;
        this.y = y;

        this.length = () => {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        }

        this.normalize = () => {
            let len = this.length();
            return new vector2(this.x / len, this.y / len);
        }
    }

    particle = function (position, velocity, radius, color = "white") {
        this.pos = position;
        this.vel = velocity;
        this.radius = radius;
        this.color = color;

        this.draw = (ctx) => {
            ctx.beginPath();
            ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2);
            ctx.closePath();
            ctx.fillStyle = this.color;
            ctx.fill();
        }

        this.basicMovement = () => {
            //wenn wir die Grenzen vom Canvas erreichen, soll die Richtung geändert werden!
            if (this.pos.x + this.radius > canvasWidth || this.pos.x - this.radius < 0) this.vel.x *= -1;
            if (this.pos.y + this.radius > canvasHeight || this.pos.y - this.radius < 0) this.vel.y *= -1;

            this.pos.x += this.vel.x;
            this.pos.y += this.vel.y;
        }

        this.update = (ctx) => {
            //wenn wir Partikel nicht verschieben wollen, oder die Maus nicht auf dem Canvas ist
            if (!pushParticlesWithMouse || (mouse.x === undefined && mouse.y === undefined)) {
                //Normale bewegung mittels unserer Velocity
                this.basicMovement();
            } else {
                //Partikel mit Maus wegbewegen

                //Vektor von Partikel zur Maus
                let particleToMouse = new vector2(mouse.x - this.pos.x, mouse.y - this.pos.y);
                //Damit ist die Distanz zur Maus die Länge des Vektors
                let distance = particleToMouse.length();
                //Jetzt wird der Vektor normalisiert (durch seine Länge geteilt) -> also seine Länge wird jetzt 1 sein
                let force = new vector2(particleToMouse.x / distance, particleToMouse.y / distance);

                //wenn wir zu nah an der Maus sind
                if (distance <= maxDistToMouse) {

                    //Wieder auf die größe des Canvas achten
                    if (this.pos.x + this.radius > canvasWidth || this.pos.x - this.radius < 0) force.x = 0;
                    if (this.pos.y + this.radius > canvasWidth || this.pos.y - this.radius < 0) force.y = 0;
                    //jetzt können die Partikel verschoben werden
                    this.pos.x -= force.x * pushForce;
                    this.pos.y -= force.y * pushForce;
                } else {
                    //wenn wir nicht mit zu nah an der Maus sind

                    //berechne unsere Position voraus auf die wir uns zu bewegen mit unserer Geschwindigkeit
                    let desiredPos = new vector2(this.pos.x + this.vel.x, this.pos.y + this.vel.y);
                    //Vektor von berechneter Position zur Maus
                    let desiredToMouse = new vector2(mouse.x - desiredPos.x, mouse.y - desiredPos.y);
                    //Berechne erneut unsere Distanz zur Maus ausgehend von unserer vorausgerechneten Position
                    let distance = desiredToMouse.length();

                    //wenn wir nun wieder zu nah der Maus sind
                    if (distance <= maxDistToMouse) {
                        //Kehre um
                        this.vel.x *= -1;
                        this.vel.y *= -1;
                    } else {
                        //Sonst bewege dich normal weiter
                        this.basicMovement();
                    }
                }
            }

            //Partikel anzeigen lassen
            this.draw(ctx);
        }
    }

    const canvas = document.getElementById("blog_particles"); //! Hier die ID des Canvas im HTML-Code eingeben
    const canvasWidth = 574; //beliebiger Wert für die Breite des Canvas
    const canvasHeight = 270;//beliebig Wert für die Höhe des Canvas
    canvas.style.maxWidth = canvasWidth + "px";
    canvas.style.maxHeight = canvasHeight + "px";

    const pixelRatio = window.devicePixelRatio;
    canvas.width = pixelRatio * canvasWidth;
    canvas.height = pixelRatio * canvasHeight;

    let ctx = canvas.getContext("2d"); //Wird benutzt um später Objekte zu zeichnen auf dem Canvas
    ctx.scale(pixelRatio, pixelRatio);

    const mouse = new vector2(0, 0);

    const particleColor = "crimson";
    const lineColor = "white";

    const particles = [];
    const particleAmount = 50;
    const particleRadius = 2;
    const particleSpeed = 1.5;

    const maxDistBetweenParticles = 50; //Distanz in pixel zwischen Partikeln, damit eine Linie gezeichnet wird

    const maxDistToMouse = 50; //Distanz in pixel zwischen Partikel und Maus, damit Partikel weggeschoben werden
    const pushParticlesWithMouse = true;
    const pushForce = 8; //Kraft mit der Partikel weggeschoben werden

    addMouseEvents();
    createParticles();
    animate();


    function animate() {
        //animate Funktion immer wieder aktualisieren
        requestAnimationFrame(animate);
        //letztes Frame leeren, um das zu verstehen, die Zeile einfach mal auskommentieren und testen
        ctx.clearRect(0, 0, canvasWidth, canvasHeight);

        //für jedes Partikel
        for (let i = 0; i < particleAmount; i++) {
            //hole die Position des aktullen Partikels
            let currentPosition = particles[i].pos;

            //Für jedes andere Partikel
            for (let j = 0; j < particleAmount; j++) {
                //Außer für sich selbst
                if (particles[i] === particles[j]) continue;

                //wenn i + j kleiner als unsere Partikelanzahl, dann nimm die Position des "i+j-ten" Partikels, sonst fange von hinten wieder an
                let nextPosition = (i + j) < particleAmount - 1 ? particles[i + j].pos : particles[particleAmount - j - 1].pos;

                //Distanz zwischen den beiden Partikeln berechnen
                let distX = Math.abs(nextPosition.x - currentPosition.x);
                let distY = Math.abs(nextPosition.y - currentPosition.y);

                //wenn Distanz klein genug, dann zeichne eine Linie
                if (distX <= maxDistBetweenParticles && distY <= maxDistBetweenParticles) {
                    ctx.beginPath();
                    //starte beim aktuellen Partikel
                    ctx.moveTo(currentPosition.x, currentPosition.y);
                    //und ziehe eine Linie zum nächsten
                    ctx.lineTo(nextPosition.x, nextPosition.y);
                    //Zeichne die Linie mit unserer Farbe
                    ctx.strokeStyle = lineColor;
                    ctx.stroke();
                }

            }
            //erst nach allen Berechnungen Partikel aktualisieren
            particles[i].update(ctx);
        }

    }


    function addMouseEvents() {
        //Koordinaten unseres Canvas relativ zum HTML-Dokument und Scroll Position
        const canvasRect = canvas.getBoundingClientRect();
        const offset = {
            top: canvasRect.top + window.scrollY,
            left: canvasRect.left + window.scrollX,
        }

        canvas.addEventListener('mousemove', function (event) {
            //Mausposition relativ zum Canvas
            mouse.x = event.pageX - offset.left;
            mouse.y = event.pageY - offset.top;

            mouse.x = clamp(mouse.x, 0, canvasWidth);
            mouse.y = clamp(mouse.y, 0, canvasHeight);
        });

        canvas.addEventListener('mouseleave', function () {
            mouse.x = undefined;
            mouse.y = undefined;
        });
    }

    function createParticles() {
        for (let i = 0; i < particleAmount; i++) {
            //zufällige Position erstellen
            let x = Math.random() * canvasWidth;
            let y = Math.random() * canvasHeight;
            let position = new vector2(x, y);

            //zufällige Velocity erstellen
            let dx = (Math.random() - 0.5) * particleSpeed;
            let dy = (Math.random() - 0.5) * particleSpeed;
            let velocity = new vector2(dx, dy);

            //normalisieren
            velocity = velocity.normalize();
            particles.push(new particle(position, velocity, particleRadius, particleColor));
        }
    }

    function clamp(val, min, max) {
        if (val < min) return min;
        if (val > max) return max;
        return val;
    }

</script>
            Code kopieren
         

Kommentar schreiben

* Diese Felder sind erforderlich

Kommentare

Keine Kommentare

Kontaktdaten

Winterrabe - Media

Agentur für interaktive Medien
Jan Ritzmann
Kaiserstraße 128
42477 Radevormwald