In meinem vorherigen Artikel habe ich einen Fragmentierungseffekt mit CSS-Masken und benutzerdefinierten Eigenschaften erstellt. Es war ein schöner Effekt, hatte aber einen Nachteil: Er erforderte viel CSS-Code (generiert mit Sass). Diesmal werde ich denselben Effekt neu erstellen, mich aber auf die neue Paint API verlassen. Dies reduziert die Menge an CSS drastisch und macht Sass komplett überflüssig.
Erkundung der CSS Paint API-Reihe
- Teil 1: Image Fragmentation Effect (Sie sind hier!)
- Teil 2: Blob Animation
- Teil 3: Polygon-Rand
- Teil 4: Abrundende Formen
Hier ist, was wir machen. Wie im vorherigen Artikel unterstützen derzeit nur Chrome und Edge dies.
Siehst du das? Nicht mehr als fünf CSS-Deklarationen und doch erhalten wir eine ziemlich coole Hover-Animation.
Was ist die Paint API?
Die Paint API ist Teil des Houdini-Projekts. Ja, „Houdini“, der seltsame Begriff, über den alle reden. Viele Artikel behandeln bereits den theoretischen Aspekt davon, also werde ich Sie nicht mit mehr belästigen. Wenn ich es in wenigen Worten zusammenfassen müsste, würde ich einfach sagen: Es ist die Zukunft von CSS. Die Paint API (und die anderen APIs, die unter die Houdini-Schirmherrschaft fallen) ermöglichen es uns, CSS mit eigenen Funktionalitäten zu erweitern. Wir müssen nicht mehr auf die Veröffentlichung neuer Features warten, weil wir sie selbst machen können!
Eine API, die es Webentwicklern ermöglicht, ein benutzerdefiniertes CSS
<image>mit JavaScript zu definieren, das auf Stil- und Größenänderungen reagiert.
Und aus dem Explainer
Die CSS Paint API wird entwickelt, um die Erweiterbarkeit von CSS zu verbessern. Insbesondere ermöglicht sie es Entwicklern, eine Paint-Funktion zu schreiben, mit der wir direkt in den Hintergrund, den Rand oder den Inhalt eines Elements zeichnen können.
Ich denke, die Idee ist ziemlich klar. Wir können zeichnen, was wir wollen. Beginnen wir mit einer sehr einfachen Demo zur Hintergrundfärbung.
- Wir fügen den Paint Worklet mit
CSS.paintWorklet.addModule('your_js_file')hinzu. - Wir registrieren eine neue Paint-Methode namens
draw. - Darin erstellen wir eine
paint()-Funktion, in der wir die gesamte Arbeit erledigen. Und was soll ich sagen? Alles ist wie bei der Arbeit mit<canvas>. Dieserctxist der 2D-Kontext, und ich habe einfach einige bekannte Funktionen verwendet, um ein rotes Rechteck zu zeichnen, das den gesamten Bereich abdeckt.
Das mag auf den ersten Blick unintuitiv erscheinen, aber beachte, dass die Hauptstruktur immer dieselbe ist: Die drei obigen Schritte sind der "Kopier-/Einfüge"-Teil, den Sie für jedes Projekt wiederholen. Die eigentliche Arbeit ist der Code, den wir in die paint()-Funktion schreiben.
Fügen wir eine Variable hinzu
Wie Sie sehen, ist die Logik ziemlich einfach. Wir definieren den Getter inputProperties mit unseren Variablen als Array. Wir fügen properties als dritten Parameter zu paint() hinzu und erhalten später unsere Variable über properties.get().
Das war's! Jetzt haben wir alles, was wir brauchen, um unseren komplexen Fragmentierungseffekt zu erstellen.
Aufbau der Maske
Sie fragen sich vielleicht, warum die Paint API verwendet wird, um einen Fragmentierungseffekt zu erstellen. Wir sagten, es sei ein Werkzeug zum Zeichnen von Bildern, also wie wird es uns ermöglichen, ein Bild zu fragmentieren?
Im vorherigen Artikel habe ich den Effekt mit verschiedenen Maskenlayern realisiert, wobei jeder eine durch einen Gradienten definierte Quadrat war (denken Sie daran, dass ein Gradient ein Bild ist), so dass wir eine Art Matrix erhielten, und der Trick bestand darin, den Alphakanal jedes einzelnen individuell anzupassen.
Diesmal werden wir anstelle vieler Gradienten nur ein benutzerdefiniertes Bild für unsere Maske definieren, und dieses benutzerdefinierte Bild wird von unserer Paint API verarbeitet.
Ein Beispiel bitte!
Oben habe ich ein Bild erstellt, das auf der linken Seite eine opake Farbe und auf der rechten Seite eine halbtransparente Farbe hat. Das Anwenden dieses Bildes als Maske ergibt das logische Ergebnis eines halbtransparenten Bildes.
Nun müssen wir nur noch unser Bild in mehr Teile aufteilen. Definieren wir zwei Variablen und aktualisieren unseren Code.
Der relevante Teil des Codes ist der folgende.
const n = properties.get('--f-n');
const m = properties.get('--f-m');
const w = size.width/n;
const h = size.height/m;
for(var i=0;i<n;i++) {
for(var j=0;j<m;j++) {
ctx.fillStyle = 'rgba(0,0,0,'+(Math.random())+')';
ctx.fillRect(i*w, j*h, w, h);
}
}
N und M definieren die Abmessungen unserer Rechteckmatrix. W und H sind die Größe jedes Rechtecks. Dann haben wir eine grundlegende FOR-Schleife, um jedes Rechteck mit einer zufälligen transparenten Farbe zu füllen.
Mit ein wenig JavaScript erhalten wir eine benutzerdefinierte Maske, die wir durch Anpassen der CSS-Variablen einfach steuern können.
Jetzt müssen wir den Alphakanal steuern, um den Fading-Effekt jedes Rechtecks zu erzeugen und den Fragmentierungseffekt aufzubauen.
Führen wir eine dritte Variable für den Alphakanal ein, die wir ebenfalls beim Hovern ändern.
Wir haben eine benutzerdefinierte CSS-Eigenschaft als <number> definiert, die wir von 1 auf 0 überblenden, und dieselbe Eigenschaft wird verwendet, um den Alphakanal unserer Rechtecke zu definieren. Beim Hovern passiert nichts Besonderes, da alle Rechtecke auf die gleiche Weise verblassen.
Wir brauchen einen Trick, um zu verhindern, dass alle Rechtecke gleichzeitig verblassen, stattdessen einen Verzögerung zwischen ihnen zu erzeugen. Hier ist eine Illustration, um die Idee zu erklären, die ich verwenden werde.

Das Obige zeigt die Alpha-Animation für zwei Rechtecke. Zuerst definieren wir eine Variable L, die größer oder gleich 1 sein sollte, und dann führen wir für jedes Rechteck unserer Matrix (d.h. für jeden Alphakanal) einen Übergang zwischen X und Y durch, wobei X - Y = L ist, so dass wir für alle Alphakanäle dieselbe Gesamtdauer haben. X sollte größer oder gleich 1 und Y kleiner oder gleich 0 sein.
Moment, der Alpha-Wert sollte nicht im Bereich [1 0] liegen, oder?
Ja, das sollte er! Und alle Tricks, die wir anwenden, basieren darauf. Oben animiert sich Alpha von 8 bis -2, was bedeutet, dass wir eine opake Farbe im Bereich [8 1], eine transparente im Bereich [0 -2] und eine Animation innerhalb von [1 0] haben. Mit anderen Worten, jeder Wert größer als 1 hat die gleiche Wirkung wie 1, und jeder Wert kleiner als 0 hat die gleiche Wirkung wie 0.
Die Animation innerhalb von [1 0] wird nicht für beide Rechtecke gleichzeitig stattfinden. Rechteck 2 erreicht [1 0] vor Rechteck 1. Wir wenden dies auf alle Alphakanäle an, um unsere verzögerten Animationen zu erhalten.
In unserem Code werden wir Folgendes aktualisieren:
rgba(0,0,0,'+(o)+')
...zu diesem:
rgba(0,0,0,'+((Math.random()*(l-1) + 1) - (1-o)*l)+')
L ist die zuvor illustrierte Variable, und O ist der Wert unserer CSS-Variablen, der von 1 auf 0 übergeht.
Wenn O=1 ist, haben wir (Math.random()*(l-1) + 1). Unter Berücksichtigung der Tatsache, dass die Funktion random() einen Wert im Bereich [0 1] liefert, liegt der Endwert im Bereich [L 1].
Wenn O=0 ist, haben wir (Math.random()*(l-1) + 1 - l) und einen Wert im Bereich [0 1-L].
L ist unsere Variable zur Steuerung der Verzögerung.
Sehen wir uns das in Aktion an.
Wir kommen der Sache näher. Wir haben einen coolen Fragmentierungseffekt, aber nicht den, den wir am Anfang des Artikels gesehen haben. Dieser ist nicht so flüssig.
Das Problem hängt mit der random()-Funktion zusammen. Wir sagten, dass jeder Alphakanal zwischen X und Y animieren muss, also müssen diese Werte logischerweise gleich bleiben. Aber die paint()-Funktion wird während des Übergangs mehrmals aufgerufen, so dass jedes Mal die random()-Funktion unterschiedliche X und Y-Werte für jeden Alphakanal liefert; daher der „zufällige“ Effekt, den wir erzielen.
Um dies zu beheben, müssen wir einen Weg finden, die generierten Werte zu speichern, damit sie für jeden Aufruf der paint()-Funktion immer gleich sind. Betrachten wir eine Pseudozufallsfunktion, eine Funktion, die immer dieselbe Sequenz von Werten generiert. Mit anderen Worten, wir wollen den Seed kontrollieren.
Leider können wir dies mit der integrierten random()-Funktion von JavaScript nicht tun, also greifen wir wie jeder gute Entwickler auf eine aus Stack Overflow zurück.
const mask = 0xffffffff;
const seed = 30; /* update this to change the generated sequence */
let m_w = (123456789 + seed) & mask;
let m_z = (987654321 - seed) & mask;
let random = function() {
m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;
var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}
Und das Ergebnis wird zu:
Wir haben unseren Fragmentierungseffekt ohne komplexen Code.
- eine grundlegende verschachtelte Schleife zur Erstellung von NxM Rechtecken.
- eine clevere Formel für den Kanal-Alpha zur Erzeugung der Übergangsverzögerung.
- eine fertige
random()-Funktion aus dem Netz.
Das war's! Alles, was Sie tun müssen, ist, die mask-Eigenschaft auf jedes Element anzuwenden und die CSS-Variablen anzupassen.
Bekämpfung von Lücken!
Wenn Sie mit den obigen Demos spielen, werden Sie in einigen besonderen Fällen seltsame Lücken zwischen den Rechtecken bemerken.

Um dies zu vermeiden, können wir den Bereich jedes Rechtecks um einen kleinen Offset erweitern.
Wir aktualisieren dies:
ctx.fillRect(i*w, j*h, w, h);
…mit diesem.
ctx.fillRect(i*w-.5, j*h-.5, w+.5, h+.5);
Dies erzeugt eine kleine Überlappung zwischen den Rechtecken, die die Lücken zwischen ihnen kompensiert. Es gibt keine besondere Logik bei dem von mir verwendeten Wert 0.5. Sie können je nach Anwendungsfall größer oder kleiner gehen.
Wünschen Sie mehr Formen?
Kann das Obige erweitert werden, um mehr als rechteckige Formen zu berücksichtigen? Sicherlich kann es das! Vergessen wir nicht, dass wir Canvas verwenden können, um jede Art von Form zu zeichnen — im Gegensatz zu reinen CSS-Formen, wo wir manchmal hacky Code benötigen. Versuchen wir, diesen dreieckigen Fragmentierungseffekt zu erstellen.
Nachdem ich das Internet durchsucht habe, fand ich etwas namens Delaunay-Triangulation. Ich werde nicht in die tiefe Theorie dahinter eintauchen, aber es ist ein Algorithmus, um aus einer Punktmenge verbundene Dreiecke mit spezifischen Eigenschaften zu zeichnen. Es gibt viele gebrauchsfertige Implementierungen davon, aber wir werden uns für Delaunator entscheiden, da es das schnellste von allen sein soll.
Wir definieren zunächst eine Menge von Punkten (wir werden hier random() verwenden) und führen dann Delaunator aus, um die Dreiecke für uns zu generieren. In diesem Fall benötigen wir nur eine Variable, die die Anzahl der Punkte definiert.
const n = properties.get('--f-n');
const o = properties.get('--f-o');
const w = size.width;
const h = size.height;
const l = 7;
var dots = [[0,0],[0,w],[h,0],[w,h]]; /* we always include the corners */
/* we generate N random points within the area of the element */
for (var i = 0; i < n; i++) {
dots.push([random() * w, random() * h]);
}
/**/
/* We call Delaunator to generate the triangles*/
var delaunay = Delaunator.from(dots);
var triangles = delaunay.triangles;
/**/
for (var i = 0; i < triangles.length; i += 3) { /* we loop the triangles points */
/* we draw the path of the triangles */
ctx.beginPath();
ctx.moveTo(dots[triangles[i]][0] , dots[triangles[i]][1]);
ctx.lineTo(dots[triangles[i + 1]][0], dots[triangles[i + 1]][1]);
ctx.lineTo(dots[triangles[i + 2]][0], dots[triangles[i + 2]][1]);
ctx.closePath();
/**/
var alpha = (random()*(l-1) + 1) - (1-o)*l; /* the alpha value */
/* we fill the area of triangle with the semi-transparent color */
ctx.fillStyle = 'rgba(0,0,0,'+alpha+')';
/* we consider stroke to fight the gaps */
ctx.strokeStyle = 'rgba(0,0,0,'+alpha+')';
ctx.stroke();
ctx.fill();
}
Ich habe nichts mehr zu den Kommentaren im obigen Code hinzuzufügen. Ich habe einfach etwas grundlegendes JavaScript und Canvas-Zeug verwendet und doch haben wir einen ziemlich coolen Effekt.
Wir können noch mehr Formen erstellen! Alles, was wir tun müssen, ist, einen Algorithmus dafür zu finden.
Ich kann nicht weitermachen, ohne das Sechseck zu machen!
Ich habe den Code aus diesem Artikel von Izan Pérez Cosano übernommen. Unsere Variable ist nun R, die die Abmessung eines Sechsecks definiert.
Was kommt als Nächstes?
Nachdem wir nun unseren Fragmentierungseffekt erstellt haben, konzentrieren wir uns auf das CSS. Beachten Sie, dass der Effekt so einfach ist wie das Ändern des opacity-Werts (oder des Werts der jeweiligen Eigenschaft, mit der Sie arbeiten) eines Elements im Hover-Zustand.
Opacity-Animation
img {
opacity:1;
transition:opacity 1s;
}
img:hover {
opacity:0;
}
Fragmentierungseffekt
img {
-webkit-mask: paint(fragmentation);
--f-o:1;
transition:--f-o 1s;
}
img:hover {
--f-o:0;
}
Das bedeutet, dass wir diese Art von Effekt leicht integrieren können, um komplexere Animationen zu erstellen. Hier sind einige Ideen!
Responsiver Bilder-Schieberegler
Eine andere Version desselben Sliders
Rauscheffekt
Ladebildschirm
Karten-Hover-Effekt
Das war's!
Und all dies ist nur die Spitze des Eisbergs dessen, was mit der Paint API erreicht werden kann. Ich werde mit zwei wichtigen Punkten enden:
- Die Paint API ist zu 90%
<canvas>, je mehr Sie also über<canvas>wissen, desto mehr ausgefallene Dinge können Sie tun. Canvas wird häufig verwendet, was bedeutet, dass es eine Menge Dokumentation und Texte darüber gibt, um Sie auf den neuesten Stand zu bringen. Hey, hier ist eine direkt hier auf CSS-Tricks! - Die Paint API entfernt die gesamte Komplexität von der CSS-Seite. Es gibt kein Umgehen mit komplexem und hacky Code, um coole Sachen zu zeichnen. Das macht den CSS-Code viel einfacher zu warten, ganz zu schweigen davon, dass er weniger fehleranfällig ist.
Erkundung der CSS Paint API-Reihe
- Teil 1: Image Fragmentation Effect (Sie sind hier!)
- Teil 2: Blob Animation
- Teil 3: Polygon Border
- Teil 4: Rounding Shapes
Buchstäblich jeder Schritt dieses Tutorials ist dasselbe Bild wie in Schritt 1. Kein Gefühl des Fortschritts wird in diesem Tutorial erreicht – das müssen Sie in Ordnung bringen, lmao.
Ja, das Bild ist dasselbe, aber der Artikel handelt vom Hover-Fragmentierungseffekt – der mit jedem Schritt voranschreitet.
Stellen Sie sicher, dass Sie dies sorgfältig berücksichtigen.
Wenn Sie Firefox verwenden, werden Sie keinen Fortschritt und nichts sehen. Es ist nutzlos, diesen Artikel zu lesen, wenn Sie Chrome oder Edge (in ihrer neuesten Version) nicht verwenden können.