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.
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.

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.

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.
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.

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.

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.
Schöne Demo!
Hallo! Vielen Dank für den Beitrag. Es war genau das, was ich für eine App gesucht habe, an der ich gerade arbeite!
Ich habe jedoch einen Fehler in der
update-Funktion bemerkt. Sie haben[i]inparticles.animationDurationvergessen. Die nachfolgende If-Anweisung endet nie!Danke für den Hinweis!
Das sollte jetzt behoben sein :) Danke für die Benachrichtigung.
Ich bin absolut begeistert!
Genau wie diese Buttons.
Coole Demo, aber es gibt ein Problem, das einen kleinen Teil der Benutzer heute, und wahrscheinlich noch viel mehr in Zukunft, betreffen wird.
Die Animation ist bildfrequenzabhängig, da rAF verwendet wird, was bedeutet, dass bei Bildfrequenzen weit über 60 Hz die Bewegung in der Animation stark beschleunigt wird. Auf meinem Rechner mit einem 240-Hz-Gaming-Monitor bewegen sich die Partikel mit ziemlich lächerlicher Geschwindigkeit, was es schwer macht, die Animation zu würdigen. Während 240-Hz-Gaming-Monitore heute einen Ausnahmefall darstellen, sind 120-Hz- und 144-Hz-Displays zunehmend verbreitet. Besonders hervorzuheben ist, dass das iPad Pro 120-Hz-Displays erstmals in einem großen Markt für Nicht-Gamer einführt, und wir können erwarten, dass 120 Hz in Zukunft auf Consumer-Handys, Tablets und Laptops immer üblicher werden.
Relevanter Thread: https://stackoverflow.com/questions/19764018/controlling-fps-with-requestanimationframe
Guter Fang, danke! Ich muss die Demos und Disintegrate aktualisieren, um das zu korrigieren.
Es funktioniert bei mir nicht, auch nachdem ich den gleichen Code eingefügt habe! Bitte helfen Sie.
Wenn nur ein Partikel feuert, erzeugt das Klicken am rechten Rand des Buttons keinen Partikel mit der gleichen Farbe, sondern eine Farbe, die mehr den mittleren Bereich des Button-Gradients darstellt. Wissen Sie, was das verursacht?