Hinzufügen von Partikeleffekten zu DOM-Elementen mit Canvas

Avatar of Zach Saucier
Zach Saucier am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Werfen wir einen Blick darauf, wie wir Webseiten visuell ansprechender gestalten können, indem wir die Freiheit von <canvas> mit HTML-Elementen kombinieren. Insbesondere erstellen wir einen grundlegenden HTML-zu-Partikel-Effekt, aber die gleiche Technik könnte für viele Arten von Effekten verwendet werden.

Bevor wir beginnen, können Sie sich gerne den Quellcode im Repository herunterladen.

Repo ansehen

Erstellen des initialen Elements

Zuerst erstellen wir ein HTML-Element, auf dem wir aufbauen können. Ich verwende einen einfach gestylten Button, aber es könnte eigentlich jedes HTML-Element sein.

Siehe den Pen DOM to Canvas #1 von Zach Saucier (@Zeaklous) auf CodePen.

Ein moderner Browser wie Chrome, Firefox oder Edge ist erforderlich, um diese Demos anzuzeigen.

Aber wie können wir ein Canvas dazu bringen, dieses Element zu „sehen“, damit wir jedes Pixel manipulieren können? Um dies zu ermöglichen, müssen wir im Wesentlichen einen Schnappschuss unseres HTML-Elements machen – ähnlich einem „Bildschirmfoto“, aber nur für das bestimmte Element (den Button), das wir im Canvas manipulieren möchten.

Erstellen einer Canvas-Version unseres Elements

Obwohl es keine native Möglichkeit für Browser gibt, dies zu tun und uns die Manipulation in JavaScript zu ermöglichen, gibt es eine sehr praktische Bibliothek namens html2canvas, die uns helfen kann. Alles, was wir tun müssen, ist, die Bibliothek zu laden, dann html2canvas(element) aufzurufen, und sie gibt ein Promise zusammen mit einer Canvas-Version unseres Elements zurück! Verrückt genial.

Siehe den Pen DOM to Canvas #2 von Zach Saucier (@Zeaklous) auf CodePen.

Hier haben wir eine HTML-Version und eine Canvas-Version unseres Buttons nebeneinander. Wir können die Canvas-Version als unseren „Screenshot“ und als Quelle für Informationen verwenden, wie z. B. die Farbe eines Pixels an einer bestimmten Stelle.

Abrufen von Daten aus unserem Canvas

Dazu erstellen wir eine neue Funktion, um die Pixelinformationen an einer bestimmten Stelle abzurufen. Wir müssen das Canvas, von dem wir die Farbinformationen erhalten, auch nicht anzeigen, da wir stattdessen das ursprüngliche HTML-Element anzeigen möchten.

function getColorAtPoint(e) {
  // Get the coordinate of the click
  let x = e.offsetX;
  let y = e.offsetY;
  
  // Get the color data of the canvas version of our element at that location
  let rgbaColorArr = ctx.getImageData(x, y, 1, 1).data;

  // Do something with rgbaColorArr
}

Siehe den Pen DOM to Canvas #3 von Zach Saucier (@Zeaklous) auf CodePen.

Nun müssen wir ein Canvas-Partikel mit diesen Informationen erstellen.

Erstellen eines Canvas zur Anzeige von Partikeln

Wir haben noch kein Canvas, auf dem wir die Partikel platzieren können, da wir das von html2canvas erhaltene Canvas nur für den Zugriff auf Farbinformationen reservieren möchten. Also erstellen wir ein weiteres

var particleCanvas, particleCtx;
function createParticleCanvas() {
  // Create our canvas
  particleCanvas = document.createElement("canvas");
  particleCtx = particleCanvas.getContext("2d");
  
  // Size our canvas
  particleCanvas.width = window.innerWidth;
  particleCanvas.height = window.innerHeight;
  
  // Position out canvas
  particleCanvas.style.position = "absolute";
  particleCanvas.style.top = "0";
  particleCanvas.style.left = "0";
  
  // Make sure it's on top of other elements
  particleCanvas.style.zIndex = "1001";
  
  // Make sure other elements under it are clickable
  particleCanvas.style.pointerEvents = "none";
  
  // Add our canvas to the page
  document.body.appendChild(particleCanvas);
}

Abrufen von Koordinatendaten

Wir müssen auch weiterhin die Farbinformationen von unserer lokalen Koordinate abrufen – nicht nur die obere und linke Kante unseres Buttons, sondern auch die Position in globalen Koordinaten (bezogen auf die gesamte Webseite), um das Partikel an der richtigen Stelle auf dem Canvas zu erstellen.

Das können wir mit folgendem Code tun

btn.addEventListener("click", e => {
  // Get our color data like before
  let localX = e.offsetX;
  let localY = e.offsetY;
  let rgbaColorArr = ctx.getImageData(localX, localY, 1, 1).data;
  
  // Get the button's positioning in terms of the window
  let bcr = btn.getBoundingClientRect();
  let globalX = bcr.left + localX;
  let globalY = bcr.top + localY;
  
  // Create a particle using the color we obtained at the window location
  // that we calculated
  createParticleAtPoint(globalX, globalY, rgbaColorArr);
});

Erstellen eines Partikel-Prototyps

Und erstellen wir auch ein grundlegendes Partikel, das eine draw-Funktion mit Variablen hat

/* An "exploding" particle effect that uses circles */
var ExplodingParticle = function() {
  // Set how long we want our particle to animate for
  this.animationDuration = 1000; // in ms

  // Set the speed for our particle
  this.speed = {
    x: -5 + Math.random() * 10,
    y: -5 + Math.random() * 10
  };
  
  // Size our particle
  this.radius = 5 + Math.random() * 5;
  
  // Set a max time to live for our particle
  this.life = 30 + Math.random() * 10;
  this.remainingLife = this.life;
  
  // This function will be called by our animation logic later on
  this.draw = ctx => {
    let p = this;

    if(this.remainingLife > 0
    && this.radius > 0) {
      // Draw a circle at the current location
      ctx.beginPath();
      ctx.arc(p.startX, p.startY, p.radius, 0, Math.PI * 2);
      ctx.fillStyle = "rgba(" + this.rgbArray[0] + ',' + this.rgbArray[1] + ',' + this.rgbArray[2] + ", 1)";
      ctx.fill();
      
      // Update the particle's location and life
      p.remainingLife--;
      p.radius -= 0.25;
      p.startX += p.speed.x;
      p.startY += p.speed.y;
    }
  }
}

Erstellen einer Partikel-Factory

Wir brauchen auch eine Funktion, um diese Partikel basierend auf einigen Koordinaten und Farbinformationen zu erstellen, und stellen sicher, dass wir sie dem Array der erstellten Partikel hinzufügen

var particles = [];
function createParticleAtPoint(x, y, colorData) {
  let particle = new ExplodingParticle();
  particle.rgbArray = colorData;
  particle.startX = x;
  particle.startY = y;
  particle.startTime = Date.now();
  
  particles.push(particle);
}

Hinzufügen von Animationslogik

Wir brauchen auch eine Möglichkeit, alle erstellten Partikel zu animieren.

function update() {
  // Clear out the old particles
  if(typeof particleCtx !== "undefined") {
    particleCtx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  }

  // Draw all of our particles in their new location
  for(let i = 0; i < particles.length; i++) {
    particles[i].draw(particleCtx);
    
    // Simple way to clean up if the last particle is done animating
    if(i === particles.length - 1) {
      let percent = (Date.now() - particles[i].startTime) / particles[i].animationDuration[i];
      
      if(percent > 1) {
        particles = [];
      }
    }
  }
  
  // Animate performantly
  window.requestAnimationFrame(update);
}

window.requestAnimationFrame(update);

Wenn wir diese Teile zusammenfügen, können wir jetzt Partikel basierend auf unserem HTML-Element erstellen, wenn wir es anklicken!

Siehe den Pen DOM to Canvas #4 von Zach Saucier (@Zeaklous) auf CodePen.

Bei jedem Klick auf den Button fliegt ein Partikel heraus.

Klasse!

Wenn wir den *gesamten* Button bei Klick „explodieren“ lassen wollen, anstatt nur ein Pixel, müssen wir nur unsere Klickfunktion ändern

let reductionFactor = 17;
btn.addEventListener("click", e => {
  // Get the color data for our button
  let width = btn.offsetWidth;
  let height = btn.offsetHeight
  let colorData = ctx.getImageData(0, 0, width, height).data;
  
  // Keep track of how many times we've iterated (in order to reduce
  // the total number of particles create)
  let count = 0;
  
  // Go through every location of our button and create a particle
  for(let localX = 0; localX < width; localX++) {
    for(let localY = 0; localY < height; localY++) {
      if(count % reductionFactor === 0) {
        let index = (localY * width + localX) * 4;
        let rgbaColorArr = colorData.slice(index, index + 4);

        let bcr = btn.getBoundingClientRect();
        let globalX = bcr.left + localX;
        let globalY = bcr.top + localY;

        createParticleAtPoint(globalX, globalY, rgbaColorArr);
      }
      count++;
    }
  }
});

Siehe den Pen DOM to Canvas #5 von Zach Saucier (@Zeaklous) auf CodePen.

Jetzt scheint der Button beim Klicken in viele winzige Partikel zu explodieren.

Hoffentlich fühlt sich das Web jetzt *weniger* einschränkend an als zu Beginn dieses Artikels, da wir wissen, dass wir Ergänzungen (wie Canvas) nutzen können, um mehr kreative Freiheit auszuüben.


Wir können noch kreativer werden, indem wir Kantenerkennung verwenden, um festzustellen, ob sich unser Element außerhalb der Grenzen eines Containers befindet (d. h. nicht sichtbar ist) und Partikel erstellen, wenn unser Element diese Grenzen überschreitet.

Ein ganzer Artikel könnte über diesen Prozess geschrieben werden, da die Positionierung von Elementen in einem Webbrowser komplex ist, aber ich habe ein kleines Plugin namens Disintegrate erstellt, das die Handhabung für solche Dinge beinhaltet.

Disintegrate: Ein Plugin, das diesen Effekt für Sie erstellt

Disintegrate ist Open Source und kümmert sich um vieles, was für produktionsreifen Code mit dieser Technik erforderlich ist. Es ermöglicht auch, dass mehrere Elemente den gleichen Effekt auf der gleichen Seite anwenden, spezifizierte Container verwendet werden können, eine Möglichkeit besteht, spezifizierte Farben zu ignorieren, falls erforderlich, und Ereignisse für die verschiedenen wichtigen Momente des Prozesses.

Repo ansehen

Mit Disintegrate müssen wir nur data-dis-type="contained" auf unserem Button deklarieren, und es wird dazu führen, dass Partikel erstellt werden, wenn unser Element die Grenzen seines Containers überschreitet! Sehen Sie sich die Demo an.

Disintegrate's Contain-Effekt auf einem ziehbaren Element.

Eine andere Art von Effekt, die wir mit Disintegrate erstellen können, ist, bei der der Container unser Element direkt umgibt. Dies ermöglicht selbstumschließende Partikelanimationen wie den Button-Effekt, den wir zuvor erstellt haben. Durch die Animation des Containers und unseres Hauptelements selbst können wir Partikel auf noch interessantere Weise erstellen.

Eine Slide-to-Unlock-Animation löst am Ende explodierende Partikel aus.

Dieser Ansatz hat jedoch seine Grenzen (ebenso wie Disintegrate). Zum Beispiel funktioniert er aufgrund des fehlenden pointer-events-Supports nur bis IE11 (vorausgesetzt, Disintegrate ist zu ES5 kompiliert). Disintegrate unterstützt auch nicht jedes denkbare DOM-Element aufgrund der Einschränkungen von html2canvas. Diejenigen, die ich am einschränkendsten fand, sind die unvollständige CSS-Transform-Unterstützung und der fehlende Clip-Path-Support.

Um Disintegrate zu installieren, können Sie npm install disintegrate verwenden, wenn Sie npm nutzen. Oder Sie können html2canvas.js und disintegrate.js manuell einbinden, bevor Sie disintegrate.init() aufrufen.

Disintegrate ist noch sehr neu und könnte einige Verbesserungen gebrauchen. Wenn Sie dazu beitragen möchten, sind sowohl Disintegrate als auch html2canvas Open Source und Vorschläge sind willkommen!

Wie könnten Sie diese Funktionalität in Ihren Projekten einsetzen? Wie sehen Sie, wie dieser Ansatz die Möglichkeiten des Webs erweitert? Lassen Sie es mich in den Kommentaren wissen.