Bis 2020 hatte ich Mischmodi (blend modes) kaum genutzt, da ich selten eine Vorstellung davon hatte, welches Ergebnis sie erzielen würden, ohne sie auszuprobieren. Und der Ansatz „ausprobieren und sehen, was passiert“ schien mich immer wieder mit dem visuellen Müll, den ich auf dem Bildschirm erzeugt hatte, zu entsetzen.
Das Problem lag darin, dass ich nicht wirklich verstand, wie sie im Hintergrund funktionieren. So ziemlich jeder Artikel, den ich zu diesem Thema gelesen habe, basiert auf Beispielen, Vergleichen mit Photoshop oder ausschweifenden künstlerischen Beschreibungen. Ich finde Beispiele toll, aber wenn man keine Ahnung hat, wie die Dinge im Hintergrund funktionieren, wird die Anpassung einer gut aussehenden Demo an eine andere Idee, die man im Kopf hat, zu einem wirklich zeitaufwändigen, frustrierenden und letztendlich vergeblichen Abenteuer. Und Vergleiche mit Photoshop sind für jemanden mit technischem Hintergrund ziemlich nutzlos. Ausschweifende künstlerische Beschreibungen fühlen sich für mich wie Pinguinsprache an.
So hatte ich eine Erleuchtung, als ich auf die Spezifikation stieß und feststellte, dass sie auch mathematische Formeln enthält, nach denen Mischmodi funktionieren. Das bedeutete, dass ich endlich verstehen konnte, wie dieser Kram im Hintergrund funktioniert und wo er wirklich nützlich sein kann. Und jetzt, wo ich es besser weiß, werde ich dieses Wissen in einer Artikelreihe teilen.
Heute konzentrieren wir uns darauf, wie das Mischen im Allgemeinen funktioniert, dann werfen wir einen genaueren Blick auf zwei etwas ähnliche Mischmodi – difference und exclusion – und schließlich kommen wir zum Kern dieses Artikels, wo wir einige coole Anwendungsfälle wie die folgenden zerlegen.
Lassen Sie uns über das „Wie“ der Mischmodi sprechen
Mischen bedeutet, zwei Ebenen (die übereinander gestapelt sind) zu kombinieren und eine einzige Ebene zu erhalten. Diese beiden Ebenen können zwei Geschwister sein, in diesem Fall ist die von uns verwendete CSS-Eigenschaft mix-blend-mode. Es können auch zwei background-Ebenen sein, in diesem Fall ist die von uns verwendete CSS-Eigenschaft background-blend-mode. Beachten Sie, dass ich, wenn ich über das Mischen von „Geschwistern“ spreche, auch das Mischen eines Elements mit Pseudo-Elementen oder mit dem Textinhalt oder dem background seines Elternelements einschließe. Und wenn es um background-Ebenen geht, spreche ich nicht nur von den background-image-Ebenen – die background-color ist ebenfalls eine Ebene.
Beim Mischen zweier Ebenen wird die obere Ebene als Quelle bezeichnet, während die untere Ebene als Ziel bezeichnet wird. Dies nehme ich so hin, wie es ist, da diese Namen zumindest für mich nicht viel Sinn ergeben. Ich würde erwarten, dass das Ziel eine Ausgabe ist, aber stattdessen sind beide Eingaben und die resultierende Ebene die Ausgabe.

Wie genau wir die beiden Ebenen kombinieren, hängt vom verwendeten Mischmodus ab, aber es geschieht immer pro Pixel. Die folgende Abbildung verwendet beispielsweise den multiply-Mischmodus, um die beiden Ebenen zu kombinieren, die als Pixelgitter dargestellt werden.

Okay, aber was passiert, wenn wir *mehr* als zwei Ebenen haben? Nun, in diesem Fall geschieht der Mischvorgang in Stufen, beginnend von unten.
In einer ersten Stufe ist die zweite Ebene von unten unsere Quelle und die erste Ebene von unten unser Ziel. Diese beiden Ebenen mischen sich und das Ergebnis wird zum Ziel für die zweite Stufe, bei der die dritte Ebene von unten die Quelle ist. Das Mischen der dritten Ebene mit dem Ergebnis der ersten beiden ergibt unser Ziel für die dritte Stufe, bei der die vierte Ebene von unten die Quelle ist.

Natürlich können wir in jeder Stufe einen anderen Mischmodus verwenden. Zum Beispiel können wir difference verwenden, um die ersten beiden Ebenen von unten zu mischen, und dann multiply verwenden, um das Ergebnis mit der dritten Ebene von unten zu mischen. Aber das ist etwas, das wir in zukünftigen Artikeln noch genauer behandeln werden.
Das von den beiden hier besprochenen Mischmodi erzeugte Ergebnis hängt nicht davon ab, welche der beiden Ebenen oben liegt. Beachten Sie, dass dies nicht für alle möglichen Mischmodi gilt, aber für die, die wir in diesem Artikel betrachten.
Es sind auch trennbare Mischmodi, was bedeutet, dass die Mischoperation auf jedem Kanal einzeln durchgeführt wird. Auch dies gilt nicht für alle möglichen Mischmodi, aber für difference und exclusion.
Genauer gesagt, hängt der resultierende Rotkanal nur vom Rotkanal der Quelle und dem Rotkanal des Ziels ab; der resultierende Grünkanal hängt nur vom Grünkanal der Quelle und dem Grünkanal des Ziels ab; und schließlich hängt der resultierende Blaukanal nur vom Blaukanal der Quelle und dem Blaukanal des Ziels ab.
R = fB(Rs, Rd)
G = fB(Gs, Gd)
B = fB(Bs, Bd)
Für einen generischen Kanal, ohne anzugeben, ob es Rot, Grün oder Blau ist, gilt, dass er eine Funktion der beiden entsprechenden Kanäle in der Quell- (oberen) Ebene und in der Ziel- (unteren) Ebene ist
Ch = fB(Chs, Chd)
Man muss bedenken, dass RGB-Werte entweder im Intervall [0, 255] oder als Prozentsatz im Intervall [0%, 100%] dargestellt werden können, und was wir tatsächlich in unseren Formeln verwenden, sind die Prozentwerte als Dezimalzahlen. Zum Beispiel kann crimson entweder als rgb(220, 20, 60) oder als rgb(86,3%, 7,8%, 23,5%) geschrieben werden – beides ist gültig. Die Kanalwerte, die wir für Berechnungen verwenden, wenn ein Pixel crimson ist, sind die Prozentwerte als Dezimalzahlen, also .863, .078, .235.
Wenn ein Pixel black ist, sind die Kanalwerte, die wir für Berechnungen verwenden, alle 0, da black als rgb(0, 0, 0) oder als rgb(0%, 0%, 0%) geschrieben werden kann. Wenn ein Pixel white ist, sind die Kanalwerte, die wir für Berechnungen verwenden, alle 1, da white als rgb(255, 255, 255) oder als rgb(100%, 100%, 100%) geschrieben werden kann.
Beachten Sie, dass überall dort, wo wir volle Transparenz haben (ein Alpha-Wert gleich 0), das Ergebnis identisch mit der anderen Ebene ist.
difference
Der Name dieses Mischmodus könnte einen Hinweis darauf geben, was die Mischfunktion fB() tut. Das Ergebnis ist der Absolutwert der Differenz zwischen den entsprechenden Kanalwerten der beiden Ebenen.
Ch = fB(Chs, Chd) = |Chs - Chd|
Erstens bedeutet dies, dass wenn die entsprechenden Pixel in den beiden Ebenen identische RGB-Werte haben (d.h. Chs = Chd für jeden der drei Kanäle), dann ist das Pixel der resultierenden Ebene black, da die Differenzen für alle drei Kanäle 0 sind.
Chs = Chd
Ch = fB(Chs, Chd) = |Chs - Chd| = 0
Zweitens, da der Absolutwert der Differenz zwischen einer beliebigen positiven Zahl und 0 diese Zahl unverändert lässt, hat das entsprechende Ergebnis-Pixel den gleichen RGB-Wert wie das Pixel der anderen Ebene, wenn ein Pixel einer Ebene black ist (alle Kanäle gleich 0).
Wenn das black-Pixel in der oberen (Quell-) Ebene liegt, erhalten wir durch Ersetzen seiner Kanalwerte durch 0 in unserer Formel
Ch = fB(0, Chd) = |0 - Chd| = |-Chd| = Chd
Wenn das black-Pixel in der unteren (Ziel-) Ebene liegt, erhalten wir durch Ersetzen seiner Kanalwerte durch 0 in unserer Formel
Ch = fB(Chs, 0) = |Chs - 0| = |Chs| = Chs
Schließlich, da der Absolutwert der Differenz zwischen einer beliebigen positiven subuniteren Zahl und 1 das Komplement dieser Zahl ergibt, ist das Ergebnis, dass wenn ein Pixel einer Ebene white ist (alle Kanäle 1 hat), das entsprechende Ergebnis-Pixel das invertierte Pixel der anderen Ebene ist (was filter: invert(1) damit tun würde).
Wenn das white-Pixel in der oberen (Quell-) Ebene liegt, erhalten wir durch Ersetzen seiner Kanalwerte durch 1 in unserer Formel
Ch = fB(1, Chd) = |1 - Chd| = 1 - Chd
Wenn das white-Pixel in der unteren (Ziel-) Ebene liegt, erhalten wir durch Ersetzen seiner Kanalwerte durch 1 in unserer Formel
Ch = fB(Chs, 1) = |Chs - 1| = 1 - Chs
Dies kann im interaktiven Pen unten in Aktion gesehen werden, wo Sie zwischen der Ansicht der getrennten Ebenen und der Ansicht der überlappenden und gemischten Ebenen umschalten können. Das Hovern über die drei Spalten im überlappenden Fall enthüllt auch, was für jede einzelne passiert.
exclusion
Für den zweiten und letzten Mischmodus, den wir uns heute ansehen, ist das Ergebnis das Zweifache des Produkts der beiden Kanalwerte, subtrahiert von ihrer Summe
Ch = fB(Chs, Chd) = Chs + Chd - 2·Chs·Chd
Da beide Werte im Intervall [0, 1] liegen, ist ihr Produkt immer höchstens gleich dem kleinsten von ihnen, sodass das Zweifache des Produkts immer höchstens gleich ihrer Summe ist.
Wenn wir ein black-Pixel in der oberen (Quell-) Ebene betrachten, dann erhalten wir durch Ersetzen von Chs durch 0 in der obigen Formel das folgende Ergebnis für die Kanäle des entsprechenden Ergebnis-Pixels
Ch = fB(0, Chd) = 0 + Chd - 2·0·Chd = Chd - 0 = Chd
Wenn wir ein black-Pixel in der unteren (Ziel-) Ebene betrachten, dann erhalten wir durch Ersetzen von Chd durch 0 in der obigen Formel das folgende Ergebnis für die Kanäle des entsprechenden Ergebnis-Pixels
Ch = fB(Chs, 0) = Chs + 0 - 2·Chs·0 = Chs - 0 = Chs
Wenn also ein Pixel einer Ebene black ist, ist das entsprechende Ergebnis-Pixel identisch mit dem Pixel der anderen Ebene.
Wenn wir ein white-Pixel in der oberen (Quell-) Ebene betrachten, dann erhalten wir durch Ersetzen von Chs durch 1 in der obigen Formel das folgende Ergebnis für die Kanäle des entsprechenden Ergebnis-Pixels
Ch = fB(1, Chd) = 1 + Chd - 2·1·Chd = 1 + Chd - 2·Chd = 1 - Chd
Wenn wir ein white-Pixel in der unteren (Ziel-) Ebene betrachten, dann erhalten wir durch Ersetzen von Chd durch 1 in der obigen Formel das folgende Ergebnis für die Kanäle des entsprechenden Ergebnis-Pixels
Ch = fB(Chs, 1) = Chs + 1 - 2·Chs·1 = Chs + 1 - 2·Chs = 1 - Chs
Wenn also ein Pixel einer Ebene white ist, ist das entsprechende Ergebnis-Pixel das invertierte Pixel der anderen Ebene.
Dies alles wird in der folgenden interaktiven Demo gezeigt
Beachten Sie, dass, solange mindestens eine der Ebenen nur black und white Pixel hat, difference und exclusion exakt das gleiche Ergebnis liefern.
Kommen wir nun zum „Was“ der Mischmodi
Jetzt kommt der interessante Teil – die Beispiele!
Text-Zustandsänderungseffekt
Nehmen wir an, wir haben einen Absatz mit einem Link
<p>Hello, <a href='#'>World</a>!</div>
Wir beginnen mit einigen grundlegenden Stilen, um unseren Text in die Mitte des Bildschirms zu bringen, die font-size zu erhöhen, einen background für das body und eine color für den Absatz und den Link festzulegen.
body {
display: grid;
place-content: center;
height: 100vh;
background: #222;
color: #ddd;
font-size: clamp(1.25em, 15vw, 7em);
}
a { color: gold; }
Sieht bisher nicht nach viel aus, aber das werden wir bald ändern!

Der nächste Schritt ist die Erstellung eines absolut positionierten Pseudo-Elements, das den gesamten Link abdeckt und dessen background auf currentColor gesetzt ist.
a {
position: relative;
color: gold;
&::after {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: currentColor;
content: '';
}
}

Das obige sieht aus, als hätten wir die Dinge vermasselt... aber haben wir das wirklich? Was wir hier haben, ist ein gold-Rechteck über gold-Text. Und wenn Sie darauf geachtet haben, wie die beiden oben besprochenen Mischmodi funktionieren, haben Sie wahrscheinlich schon erraten, was als Nächstes kommt – wir mischen die beiden Geschwisterknoten innerhalb des Links (das Pseudo-Element-Rechteck und den Textinhalt) mit difference, und da beide gold sind, wird das, was sie gemeinsam haben – der Text – black.
p { isolation: isolate; }
a {
/* same as before */
&::after {
/* same as before */
mix-blend-mode: difference;
}
}
Beachten Sie, dass wir den Absatz isolate müssen, um das Vermischen mit dem background des Bodys zu verhindern. Während dies nur in Firefox ein Problem ist (und da wir einen sehr dunklen background im body haben, ist es nicht zu bemerkbar) und in Chrome in Ordnung ist, bedenken Sie, dass gemäß der Spezifikation das, was Firefox tut, tatsächlich korrekt ist. Es ist Chrome, das sich hier fehlerhaft verhält, daher sollten wir die isolation-Eigenschaft gesetzt haben, falls der Fehler behoben wird.

mix-blend-mode: difference Effekt (Demo)Okay, aber wir möchten, dass dies nur geschieht, wenn der Link fokussiert oder gehovert wird. Andernfalls ist das Pseudo-Element nicht sichtbar – sagen wir, es wird auf nichts skaliert.
a {
/* same as before */
text-decoration: none;
&::after {
/* same as before */
transform: scale(0);
}
&:focus { outline: none }
&:focus, &:hover { &::after { transform: none; } }
}
Wir haben auch die Link-Unterstreichung und die Fokus-Umrandung entfernt. Unten sehen Sie nun den Unterschiedseffekt bei :hover (der gleiche Effekt tritt bei :focus auf, was Sie in der Live-Demo testen können).

mix-blend-mode: difference Effekt nur bei :hover (Demo)Jetzt haben wir unsere Zustandsänderung, aber sie sieht rau aus, also fügen wir eine transition hinzu!
a {
/* same as before */
&::after {
/* same as before */
transition: transform .25s;
}
}
Viel besser!

mix-blend-mode: difference Effekt nur bei :hover, jetzt geglättet durch eine transition (Demo)Es würde noch besser aussehen, wenn unsere Pseudo-Elemente nicht aus dem Nichts in der Mitte wachsen würden, sondern aus einer dünnen Linie am unteren Rand. Das bedeutet, wir müssen transform-origin am unteren Rand setzen (vertikal bei 100% und horizontal beliebig) und unsere Pseudo-Elemente zunächst entlang der y-Achse auf etwas mehr als nichts skalieren.
a {
/* same as before */
&::after {
/* same as before */
transform-origin: 0 100%;
transform: scaleY(.05);
}
}

mix-blend-mode: difference Effekt nur bei :hover, jetzt geglättet durch eine transition zwischen einer dünnen Unterstreichung und einem Rechteck, das den Linktext enthält (Demo)Etwas anderes, das ich hier gerne machen würde, ist, die font des Absatzes durch eine ästhetisch ansprechendere zu ersetzen, also kümmern wir uns auch darum! Aber jetzt haben wir ein anderes Problem: das Ende des „d“ ragt bei :focus/:hover aus dem Rechteck heraus.

:focus oder :hovern (Demo)Dies können wir mit einem horizontalen padding für unseren Link beheben.
a {
/* same as before */
padding: 0 .25em;
}
Falls Sie sich fragen, warum wir dieses padding sowohl auf der rechten als auch auf der linken Seite setzen, anstatt nur ein padding-right zu setzen, wird der Grund unten illustriert. Wenn unser Linktext „Alien World“ wird, würde der geschwungene Anfang des „A“ außerhalb unseres Rechtecks landen, wenn wir kein padding-left hätten.

Diese Demo mit einem mehrwortigen Link oben beleuchtet auch ein weiteres Problem, wenn wir die Breite des Viewports reduzieren.

Eine schnelle Lösung hier wäre, display: inline-block für den Link zu setzen. Dies ist keine perfekte Lösung. Es bricht auch, wenn der Linktext länger als die Viewport-Breite ist, aber es funktioniert in diesem speziellen Fall, also lassen wir es hier vorerst und kommen später darauf zurück.

inline-block-Lösung (Demo)Betrachten wir nun die Situation eines hellen Themas. Da es keine Möglichkeit gibt, white anstelle von black für den Linktext bei :hover oder :focus zu erhalten, indem zwei identische Hervorhebungsebenen gemischt werden, die beide nicht white sind, benötigen wir einen etwas anderen Ansatz, einen, der keine reinen Mischmodi verwendet.
Was wir in diesem Fall tun, ist zuerst die background, die normale Textfarbe des Absatzes color und die Linktextfarbe color auf *die gewünschten Werte, aber invertiert* zu setzen. Ich habe diese Invertierung anfangs manuell vorgenommen, aber dann erhielt ich den Vorschlag, die Sass-Funktion invert() zu verwenden, was eine sehr coole Idee ist und die Dinge wirklich vereinfacht. Dann, nachdem wir dieses dunkle Thema haben, das im Grunde das invertierte helle Thema ist, das wir wollen, erhalten wir unser gewünschtes Ergebnis, indem wir alles erneut mit Hilfe der CSS-Filterfunktion invert() invertieren.
Kleine Einschränkung: Wir können filter: invert(1) nicht auf die Elemente body oder html anwenden, da dies nicht wie erwartet funktioniert und wir nicht das gewünschte Ergebnis erzielen. Aber wir können sowohl die background als auch den filter auf einem Wrapper um unseren Absatz setzen.
<section>
<p>Hello, <a href='#'>Alien World</a>!</p>
</section>
body {
/* same as before,
without the place-content, background and color declarations,
which we move on the section */
}
section {
display: grid;
place-content: center;
background: invert(#ddd) /* Sass invert(<color>) function */;
color: invert(#222); /* Sass invert<color>) function */;
filter: invert(1); /* CSS filter invert(<number|percentage>) function */
}
a {
/* same as before */
color: invert(purple); /* Sass invert(<color>) function */
}
Hier ist ein Beispiel einer Navigationsleiste, die diesen Effekt nutzt (und eine Reihe anderer cleverer Tricks, aber die sind außerhalb des Rahmens dieses Artikels). Wählen Sie eine andere Option, um sie in Aktion zu sehen
Etwas anderes, mit dem wir vorsichtig sein müssen, ist Folgendes: *Alle* Nachfahren unseres section werden invertiert, wenn wir diese Technik verwenden. Und das ist wahrscheinlich nicht das, was wir im Fall von img-Elementen wollen – ich erwarte sicherlich nicht, dass die Bilder in einem Blogbeitrag invertiert werden, wenn ich vom dunklen zum hellen Thema wechsle. Folglich sollten wir die filter-Invertierung auf jedem img-Nachfahren unseres section umkehren.
section {
/* same as before */
&, & img { filter: invert(1); }
}
Zusammengenommen zeigt die folgende Demo sowohl die dunklen als auch die hellen Themen mit Bildern
Kommen wir nun zum Problem des umschließenden Linktexts zurück und sehen wir, ob wir keine besseren Optionen haben, als die a-Elemente zu inline-block-Elementen zu machen.
Nun, das haben wir! Wir können zwei background-Ebenen mischen, anstatt den Textinhalt und ein Pseudo-Element zu mischen. Eine Ebene wird auf den text zugeschnitten, während die andere auf die border-box zugeschnitten ist und ihre vertikale Größe anfänglich von 5% auf 100% im gehovert und fokussierten Fall animiert.
a {
/* same as before */
-webkit-text-fill-color: transparent;
-moz-text-fill-color: transparent;
--full: linear-gradient(currentColor, currentColor);
background:
var(--full),
var(--full) 0 100%/1% var(--sy, 5%) repeat-x;
-webkit-background-clip: text, border-box;
background-clip: text, border-box;
background-blend-mode: difference;
transition: background-size .25s;
&:focus, &:hover { --sy: 100%; }
}
Beachten Sie, dass wir nicht einmal mehr ein Pseudo-Element haben, also haben wir einige der CSS davon genommen, sie auf den Link selbst verschoben und sie an diese neue Technik angepasst. Wir sind von mix-blend-mode auf background-blend-mode umgestiegen; wir animieren jetzt background-size von transform und in den :focus- und :hover-Zuständen; und wir ändern jetzt nicht den transform, sondern eine benutzerdefinierte Eigenschaft, die die vertikale Komponente der background-size darstellt.

Viel besser, obwohl dies auch keine perfekte Lösung ist.
Das erste Problem ist eines, das Ihnen sicher aufgefallen ist, wenn Sie den Live-Demo-Link der Bildunterschrift in Firefox überprüft haben: er funktioniert überhaupt nicht. Dies liegt an einem Firefox-Bug, den ich anscheinend bereits 2018 gemeldet und dann vergessen hatte, bis ich mit Mischmodi herumspielte und ihn erneut entdeckte.
Das zweite Problem ist eines, das in der Aufnahme auffällt. Die Links scheinen etwas verblasst zu sein. Das liegt daran, dass Chrome aus irgendeinem Grund Inline-Elemente wie Links (beachten Sie, dass dies bei Block-Elementen wie divs nicht der Fall ist) mit dem background ihres nächsten Vorfahren (in diesem Fall der section) mischt, wenn diese Inline-Elemente background-blend-mode auf etwas anderes als normal gesetzt haben.
Noch seltsamer ist, dass das Setzen von isolation: isolate auf dem Link oder seinem übergeordneten Absatz dies nicht stoppt. Ich hatte immer noch das nagende Gefühl, dass es etwas mit Kontext zu tun haben muss, also beschloss ich, mögliche Hacks darauf anzuwenden und hoffte, dass vielleicht etwas funktionieren würde. Nun, ich musste nicht viel Zeit dafür aufwenden. Das Setzen von opacity auf einen subuniteren Wert (aber immer noch nah genug an 1, so dass es nicht auffällt, dass es nicht vollständig opak ist) behebt es.
a {
/* same as before */
opacity: .999; /* hack to fix blending issue ¯_(ツ)_/¯ */
}

Das letzte Problem ist ein weiteres, das in der Aufnahme auffällt. Wenn Sie sich das 'r' am Ende von "Amur" ansehen, können Sie bemerken, dass sein rechtes Ende abgeschnitten ist, da es außerhalb des Hintergrundrechtecks liegt. Dies fällt besonders auf, wenn man es mit dem 'r' in "leopard" vergleicht.
Ich hatte keine hohen Erwartungen, dies zu beheben, aber ich habe die Frage trotzdem an Twitter gestellt. Und was soll ich sagen, es kann behoben werden! Die Verwendung von box-decoration-break in Kombination mit dem bereits gesetzten padding kann uns helfen, den gewünschten Effekt zu erzielen!
a {
/* same as before */
box-decoration-break: clone;
}
Beachten Sie, dass box-decoration-break für alle WebKit-Browser immer noch das -webkit--Präfix benötigt, aber im Gegensatz zu Eigenschaften wie background-clip, bei denen mindestens ein Wert text ist, können Auto-Prefixing-Tools das Problem problemlos handhaben. Deshalb habe ich die geprägte Version im obigen Code nicht aufgenommen.

Ein weiterer Vorschlag, den ich erhielt, war, einen negativen margin hinzuzufügen, um das padding zu kompensieren. Ich schwanke hin und her – ich kann mich nicht entscheiden, ob mir das Ergebnis mit oder ohne ihn besser gefällt. In jedem Fall ist es eine Option, die erwähnenswert ist.
$p: .25em;
a {
/* same as before */
margin: 0 (-$p); /* we put it within parenthesis so Sass doesn't try to perform subtraction */
padding: 0 $p;
}

margin haben, der das padding kompensiert (Demo)Dennoch muss ich zugeben, dass das Animieren nur der background-position oder der background-size eines Gradienten etwas langweilig ist. Aber dank Houdini können wir jetzt kreativ werden und jede Komponente eines Gradienten animieren, auch wenn dies derzeit nur in Chromium unterstützt wird. Zum Beispiel der Radius eines radial-gradient() wie unten oder der Fortschritt eines conic-gradient().

Nur einen Bereich eines Elements (oder einen background) invertieren
Dies ist die Art von Effekt, die ich oft durch entweder die Verwendung von Elementduplizierung erzielt sehe – die beiden Kopien werden übereinander gelegt, wobei eine davon einen invertierten filter hat und clip-path auf die obere angewendet wird, um beide Ebenen anzuzeigen. Ein anderer Weg ist das Übereinanderlegen eines zweiten Elements mit einer so geringen Alpha, dass man nicht einmal merkt, dass es da ist, und einem backdrop-filter.
Beide Ansätze erledigen die Aufgabe, wenn wir einen Teil des gesamten Elements mit all seinen Inhalten und Nachfahren invertieren wollen, aber sie können uns nicht helfen, wenn wir nur einen Teil des background invertieren wollen – sowohl filter als auch backdrop-filter wirken sich auf ganze Elemente aus, nicht nur auf deren Hintergründe. Und während die neue Funktion filter() (bereits von Safari unterstützt) nur auf background-Ebenen wirkt, wirkt sie sich auf den gesamten Bereich des Hintergrunds aus, nicht nur auf einen Teil davon.
Hier kommt das Mischen ins Spiel. Die Technik ist ziemlich einfach: Wir haben eine background-Ebene, deren Teil wir invertieren wollen, und eine oder mehrere Gradientenebenen, die uns einen white-Bereich liefern, wo wir die Invertierung der anderen Ebene wünschen, und ansonsten Transparenz (oder black). Dann mischen wir mit einem der beiden heute besprochenen Mischmodi. Zur Invertierung bevorzuge ich exclusion (es ist ein Zeichen kürzer als difference).
Hier ist ein erstes Beispiel. Wir haben ein quadratisches Element, das einen zweilagigen background hat. Die beiden Ebenen sind ein Bild einer Katze und ein Gradient mit einem scharfen Übergang zwischen white und transparent.
div {
background:
linear-gradient(45deg, white 50%, transparent 0),
url(cat.jpg) 50%/ cover;
}
Dies ergibt folgendes Ergebnis. Wir haben auch Dimensionen, einen border-radius, Schatten und den Text verschönert, aber all das ist in diesem Kontext nicht wirklich wichtig

Als Nächstes benötigen wir nur eine weitere CSS-Deklaration, um die untere linke Hälfte zu invertieren
div {
/* same as before */
background-blend-mode: exclusion; /* or difference, but it's 1 char longer */
}
Beachten Sie, wie der Text nicht von der Invertierung betroffen ist; sie wird nur auf den background angewendet.

Sie kennen wahrscheinlich die interaktiven Vorher-Nachher-Bildschieberegler. Vielleicht haben Sie sogar schon etwas Ähnliches hier auf CSS-Tricks gesehen. Ich habe es auf Compressor.io gesehen, das ich oft zum Komprimieren von Bildern verwende, einschließlich der Bilder, die in diesen Artikeln verwendet werden!
Unser Ziel ist es, etwas Ähnliches mit einem einzigen HTML-Element zu erstellen, mit unter 100 Bytes JavaScript – und nicht einmal viel CSS!
Unser Element wird ein Range-input sein. Wir setzen keine min- oder max-Attribute, also sind sie standardmäßig 0 und 100. Wir setzen auch kein value-Attribut, also ist es standardmäßig 50, was auch der Wert ist, den wir einer benutzerdefinierten Eigenschaft, --k, geben, die im style-Attribut gesetzt ist.
<input type='range' style='--k: 50'/>
Im CSS beginnen wir mit einem einfachen Reset, dann machen wir unseren input zu einem block-Element, das die gesamte Viewporthöhe einnimmt. Wir geben auch Dimensionen und Dummy-Hintergründe für seine Spur und seinen Daumen, damit wir sofort etwas auf dem Bildschirm sehen können.
$thumb-w: 5em;
@mixin track() {
border: none;
width: 100%;
height: 100%;
background: url(flowers.jpg) 50%/ cover;
}
@mixin thumb() {
border: none;
width: $thumb-w;
height: 100%;
background: purple;
}
* {
margin: 0;
padding: 0;
}
[type='range'] {
&, &::-webkit-slider-thumb,
&::-webkit-slider-runnable-track { -webkit-appearance: none; }
display: block;
width: 100vw; height: 100vh;
&::-webkit-slider-runnable-track { @include track; }
&::-moz-range-track { @include track; }
&::-webkit-slider-thumb { @include thumb; }
&::-moz-range-thumb { @include thumb; }
}

Der nächste Schritt ist das Hinzufügen einer weiteren background-Ebene zur Spur, einer linear-gradient-Ebene, bei der die Trennlinie zwischen transparent und white vom aktuellen Wert des Range-input, --k, abhängt, und dann mischen wir die beiden.
@mixin track() {
/* same as before */
background:
url(flowers.jpg) 50%/ cover,
linear-gradient(90deg, transparent var(--p), white 0);
background-blend-mode: exclusion;
}
[type='range'] {
/* same as before */
--p: calc(var(--k) * 1%);
}
Beachten Sie, dass die Reihenfolge der beiden background-Ebenen der Spur keine Rolle spielt, da sowohl exclusion als auch difference kommutativ sind.
Es fängt an, wie etwas auszusehen, aber das Ziehen des Daumens bewegt die Trennlinie nicht automatisch. Das passiert, weil der aktuelle Wert, --k (von dem die Position der Gradiententrennlinie, --p, abhängt), nicht automatisch aktualisiert wird. Lassen Sie uns das mit einem winzigen Stück JavaScript beheben, das den Schiebereglerwert abruft, wenn er sich ändert, und dann --k auf diesen Wert setzt.
addEventListener('input', e => {
let _t = e.target;
_t.style.setProperty('--k', +_t.value)
})
Jetzt scheint alles zu funktionieren!
Aber funktioniert es wirklich? Nehmen wir an, wir machen etwas etwas ausgefallener für den Daumen-background
$thumb-r: .5*$thumb-w;
$thumb-l: 2px;
@mixin thumb() {
/* same as before */
--list: #fff 0% 60deg, transparent 0%;
background:
conic-gradient(from 60deg, var(--list)) 0/ 37.5% /* left arrow */,
conic-gradient(from 240deg, var(--list)) 100%/ 37.5% /* right arrow */,
radial-gradient(circle,
transparent calc(#{$thumb-r} - #{$thumb-l} - 1px) /* inside circle */,
#fff calc(#{$thumb-r} - #{$thumb-l}) calc(#{$thumb-r} - 1px) /* circle line */,
transparent $thumb-r /* outside circle */),
linear-gradient(
#fff calc(50% - #{$thumb-r} + .5*#{$thumb-l}) /* top line */,
transparent 0 calc(50% + #{$thumb-r} - .5*#{$thumb-l}) /* gap behind circle */,
#fff 0 /* bottom line */) 50% 0/ #{$thumb-l};
background-repeat: no-repeat;
}
Der linear-gradient() erstellt die dünne vertikale Trennlinie, der radial-gradient() erstellt den Kreis, und die beiden conic-gradient()-Ebenen erstellen die Pfeile.
Das Problem wird nun offensichtlich, wenn man den Daumen von einem Ende zum anderen zieht: Die Trennlinie bleibt nicht auf der vertikalen Mittellinie des Daumens fixiert.
Wenn wir --p auf calc(var(--k)*1%) setzen, bewegt sich die Trennlinie von 0% auf 100%. Sie sollte sich wirklich von einem Startpunkt bewegen, der halb so breit ist wie der Daumen, $thumb-r, bis zu einer halben Daumenbreite vor 100%. Das heißt, innerhalb eines Bereichs von 100% abzüglich einer Daumenbreite, $thumb-w. Wir subtrahieren jeweils eine Hälfte, also ist das eine ganze Daumenbreite, die subtrahiert wird. Korrigieren wir das!
--p: calc(#{$thumb-r} + var(--k) * (100% - #{$thumb-w}) / 100);
Viel besser!
Aber aufgrund der Funktionsweise von Range-Inputs bewegt sich ihre border-box innerhalb der Grenzen der content-box der Spur (Chrome) oder innerhalb der Grenzen der content-box des eigentlichen Inputs (Firefox)... das fühlt sich immer noch nicht richtig an. Es sähe viel besser aus, wenn die Mittellinie des Daumens (und folglich die Trennlinie) bis zu den Rändern des Viewports reichen würde.
Wir können die Funktionsweise von Range-Inputs nicht ändern, aber wir können den input so erweitern, dass er um eine halbe Daumenbreite nach links und um eine weitere halbe Daumenbreite nach rechts aus dem Viewport herausragt. Dies macht seine width gleich der des Viewports, 100vw, plus einer ganzen Daumenbreite, $thumb-w.
body { overflow: hidden; }
[type='range'] {
/* same as before */
margin-left: -$thumb-r;
width: calc(100vw + #{$thumb-w});
}
Ein paar weitere Verschönerungsarbeiten im Zusammenhang mit dem cursor und das war's!
Eine ausgefallenere Version davon (inspiriert von der Compressor.io-Website) ist, den input in eine Karte zu packen, deren 3D-Rotation sich auch ändert, wenn sich die Maus darüber bewegt.
Wir könnten auch einen vertikalen Schieberegler verwenden. Das ist etwas komplexer, da unsere einzige zuverlässige Cross-Browser-Methode zur Erstellung benutzerdefinierter vertikaler Schieberegler darin besteht, sie zu drehen, aber dies würde auch den background drehen. Was wir tun, ist, den Wert --p und diese Hintergründe auf dem (nicht gedrehten) Schiebereglercontainer zu setzen, dann den input und seine Spur vollständig transparent zu halten.
Dies ist in der Demo unten in Aktion zu sehen, wo ich ein Foto von mir invertiere, wie ich mein geliebtes Kreator-Hoodie zeige.
Wir können natürlich auch einen radial-gradient() für einen coolen Effekt verwenden
background:
radial-gradient(circle at var(--x, 50%) var(--y, 50%),
#000 calc(var(--card-r) - 1px), #fff var(--card-r)) border-box,
$img 50%/ cover;
In diesem Fall wird die durch die benutzerdefinierten Eigenschaften --x und --y gegebene Position aus der Mausbewegung über der Karte berechnet.
Der invertierte Bereich des background muss nicht unbedingt von einem Gradienten erzeugt werden. Es kann auch der Bereich hinter dem Text einer Überschrift sein, wie in diesem älteren Artikel über das Kontrastieren von Text gegen ein Hintergrundbild gezeigt.
Schrittweise Invertierung
Die Mischtechnik zur Invertierung ist in mehrfacher Hinsicht leistungsfähiger als die Verwendung von Filtern. Sie ermöglicht es uns auch, den Effekt schrittweise entlang eines Gradienten anzuwenden. Zum Beispiel wird die linke Seite überhaupt nicht invertiert, aber dann bewegen wir uns nach rechts bis zur vollständigen Invertierung.
Um zu verstehen, wie man diesen Effekt erzielt, müssen wir zuerst verstehen, wie man den invert(p)-Effekt erzielt, wobei p jeder Wert im Intervall [0%, 100%] (oder im Intervall [0, 1], wenn wir die Dezimaldarstellung verwenden) sein kann.
Die erste Methode, die sowohl für difference als auch für exclusion funktioniert, ist das Setzen des Alphakanals unseres white auf p. Dies kann in der folgenden Demo in Aktion gesehen werden, wo das Ziehen des Schiebereglers den Inversionsfortschritt steuert.
Falls Sie sich über die Notation hsl(0, 0%, 100% / 100%) wundern: Dies ist nun eine gültige Methode zur Darstellung eines white mit einem Alpha von 1, gemäß der Spezifikation.
Darüber hinaus, aufgrund der Art und Weise, wie filter: invert(p) im allgemeinen Fall funktioniert (im allgemeinen Fall, d. h. Skalieren jedes Kanalwerts auf ein gequetschtes Intervall [Min(p, q), Max(p, q)]), wobei q das Komplement von p ist (oder q = 1 - p), bevor es invertiert wird (von 1 subtrahiert wird), haben wir für einen generischen Kanal Ch, wenn er teilweise invertiert wird, Folgendes:
1 - (q + Ch·(p - q)) =
= 1 - (1 - p + Ch·(p - (1 - p))) =
= 1 - (1 - p + Ch·(2·p - 1)) =
= 1 - (1 - p + 2·Ch·p - Ch) =
= 1 - 1 + p - 2·Ch·p + Ch =
= Ch + p - 2·Ch·p
Was wir erhalten, ist genau die Formel für exclusion, bei der der andere Kanal p ist! Daher können wir denselben Effekt wie filter: invert(p) für jedes p im Intervall [0%, 100%] erzielen, indem wir den Mischmodus exclusion verwenden, wenn die andere Ebene rgb(p, p, p) ist.
Das bedeutet, wir können eine schrittweise Inversion entlang eines linear-gradient() haben, das von keinerlei Inversion am linken Rand bis zur vollständigen Inversion am rechten Rand reicht, mit dem Folgenden:
background:
url(butterfly_blues.jpg) 50%/ cover,
linear-gradient(90deg,
#000 /* equivalent to rgb(0%, 0%, 0%) and hsl(0, 0%, 0%) */,
#fff /* equivalent to rgb(100%, 100%, 100%) and hsl(0, 0%, 100%) */);
background-blend-mode: exclusion;

Beachten Sie, dass die Verwendung eines Verlaufs von black zu white für die schrittweise Inversion nur mit dem Mischmodus exclusion funktioniert und nicht mit difference. Das von difference in diesem Fall erzeugte Ergebnis ist aufgrund seiner Formel eine pseudo-schrittweise Inversion, die nicht durch das 50%-Grau in der Mitte geht, sondern durch RGB-Werte, bei denen jeder der drei Kanäle an verschiedenen Punkten entlang des Verlaufs auf Null gesetzt wird. Deshalb sieht der Kontrast stärker aus. Es ist vielleicht auch etwas künstlerischer, aber das ist nicht wirklich etwas, zu dem ich eine Meinung abgeben kann.

Unterschiedliche Inversionsstufen über einen background müssen nicht unbedingt von einem Schwarz-Weiß-Verlauf stammen. Sie können auch von einem Schwarz-Weiß-Bild stammen, da die schwarzen Bereiche des Bildes die background-color beibehalten, die weißen Bereiche sie vollständig invertieren und wir bei Verwendung des exclusion-Mischmodus eine teilweise Inversion für alles dazwischen erhalten. difference würde uns wieder ein stärkeres Duoton-Ergebnis liefern.
Dies ist in der folgenden interaktiven Demo zu sehen, in der Sie die background-color ändern und die Trennlinie zwischen den Ergebnissen der beiden Mischmodi ziehen können.
Hollow intersection effect
Die grundlegende Idee hier ist, dass wir zwei Ebenen mit nur black und white Pixeln haben.
Ripples and rays
Betrachten wir ein Element mit zwei Pseudoelementen, die jeweils einen background haben, der ein wiederholender CSS-Verlauf mit scharfen Stopps ist.
$d: 15em;
$u0: 10%;
$u1: 20%;
div {
&::before, &::after {
display: inline-block;
width: $d;
height: $d;
background: repeating-radial-gradient(#000 0 $u0, #fff 0 2*$u0);
content: '';
}
&::after {
background: repeating-conic-gradient(#000 0% $u1, #fff 0% 2*$u1);
}
}
Je nach Browser und Anzeige können die Kanten zwischen black und white gezackt aussehen... oder auch nicht.

Um ganz sicher zu gehen, können wir unsere Verläufe anpassen, um dieses Problem zu beseitigen, indem wir einen winzigen Abstand, $e, zwischen black und white lassen.
$u0: 10%;
$e0: 1px;
$u1: 5%;
$e1: .2%;
div {
&::before {
background:
repeating-radial-gradient(
#000 0 calc(#{$u0} - #{$e0}),
#fff $u0 calc(#{2*$u0} - #{$e0}),
#000 2*$u0);
}
&::after {
background:
repeating-conic-gradient(
#000 0% $u1 - $e1,
#fff $u1 2*$u1 - $e1,
#000 2*$u1);
}
}

Dann können wir sie übereinander legen und mix-blend-mode auf exclusion oder difference setzen, da beide hier dasselbe Ergebnis liefern.
div {
&::before, &::after {
/* same other styles minus the now redundant display */
position: absolute;
mix-blend-mode: exclusion;
}
}
Wo auch immer die obere Ebene black ist, ist das Ergebnis der Mischoperation identisch mit der anderen Ebene, ob diese nun black oder white ist. Also: black über black ergibt black, während black über white white ergibt.
Wo auch immer die obere Ebene white ist, ist das Ergebnis der Mischoperation identisch mit der invertierten anderen Ebene. Also: white über black ergibt white (black invertiert), während white über white black ergibt (white invertiert).
Abhängig vom Browser kann das tatsächliche Ergebnis jedoch wie gewünscht aussehen (Chromium) oder so, als ob das ::before mit dem grauen background gemischt wurde, den wir auf dem body gesetzt haben, und das Ergebnis dann mit dem ::after gemischt wurde (Firefox, Safari).

body-Ebene gemischt wurde (Demo)Die Art und Weise, wie Chromium sich verhält, ist ein Fehler, aber das ist das Ergebnis, das wir wollen. Und wir können es auch in Firefox und Safari bekommen, indem wir entweder die Eigenschaft isolation auf isolate auf dem übergeordneten div setzen (Demo) oder die mix-blend-mode Deklaration vom ::before entfernen (da dies sicherstellt, dass die Mischoperation zwischen ihm und dem body der Standardwert normal bleibt, was keine Mischung bedeutet) und sie nur auf dem ::after setzen (Demo).
Natürlich können wir die Dinge auch vereinfachen und die beiden gemischten Ebenen zu background-Ebenen auf dem Element machen, anstatt zu seinen Pseudoelementen. Dies bedeutet auch, von mix-blend-mode zu background-blend-mode zu wechseln.
$d: 15em;
$u0: 10%;
$e0: 1px;
$u1: 5%;
$e1: .2%;
div {
width: $d;
height: $d;
background:
repeating-radial-gradient(
#000 0 calc(#{$u0} - #{$e0}),
#fff $u0 calc(#{2*$u0} - #{$e0}),
#000 2*$u0),
repeating-conic-gradient(
#000 0% $u1 - $e1,
#fff $u1 2*$u1 - $e1,
#000 2*$u1);;
background-blend-mode: exclusion;
}
Dies ergibt exakt dasselbe visuelle Ergebnis, eliminiert jedoch die Notwendigkeit von Pseudo-Elementen, eliminiert den potenziellen unerwünschten mix-blend-mode-Nebeneffekt in Firefox und Safari und reduziert die Menge an CSS, die wir schreiben müssen.

Split screen
Die grundlegende Idee ist, dass wir eine Szene haben, die halb black und halb white ist, und ein white-Objekt, das sich von einer Seite zur anderen bewegt. Die Objekt-Ebene und die Szenen-Ebene werden dann entweder mit difference oder exclusion gemischt (beide liefern dasselbe Ergebnis).
Wenn das Objekt beispielsweise ein Ball ist, ist der einfachste Weg, dieses Ergebnis zu erzielen, einen radial-gradient dafür und einen linear-gradient für die Szene zu verwenden und dann die background-position zu animieren, um den Ball oszillieren zu lassen.
$d: 15em;
div {
width: $d;
height: $d;
background:
radial-gradient(closest-side, #fff calc(100% - 1px), transparent)
0/ 25% 25% no-repeat,
linear-gradient(90deg, #000 50%, #fff 0);
background-blend-mode: exclusion;
animation: mov 2s ease-in-out infinite alternate;
}
@keyframes mov { to { background-position: 100%; } }

Wir können auch das ::before-Pseudoelement als Szene und das ::after als sich bewegendes Objekt verwenden.
$d: 15em;
div {
display: grid;
width: $d;
height: $d;
&::before, &::after {
grid-area: 1/ 1;
background: linear-gradient(90deg, #000 50%, #fff 0);
content: '';
}
&::after {
place-self: center start;
padding: 12.5%;
border-radius: 50%;
background: #fff;
mix-blend-mode: exclusion;
animation: mov 2s ease-in-out infinite alternate;
}
}
@keyframes mov { to { transform: translate(300%); } }
Das mag so aussehen, als würden wir die Dinge überkomplizieren, da wir dasselbe visuelle Ergebnis erzielen (dasselbe visuelle Ergebnis), aber tatsächlich müssen wir das tun, wenn das bewegte Objekt nicht nur eine Scheibe ist, sondern eine komplexere Form, und die Bewegung nicht nur auf die Oszillation beschränkt ist, sondern auch eine Rotations- und Skalierungskomponente hat.
$d: 15em;
$t: 1s;
div {
/* same as before */
&::after {
/* same as before */
/* creating the shape, not detailed here as
it's outside the scope of this article */
@include poly;
/* the animations */
animation:
t $t ease-in-out infinite alternate,
r 2*$t ease-in-out infinite,
s .5*$t ease-in-out infinite alternate;
}
}
@keyframes t { to { translate: 300% } }
@keyframes r {
50% { rotate: .5turn; }
100% { rotate: 1turn;; }
}
@keyframes s { to { scale: .75 1.25 } }

Beachten Sie, dass, obwohl Safari jetzt Firefox darin unterstützt, die einzelnen Transformationseigenschaften zu unterstützen, die wir hier animieren, diese in Chrome immer noch hinter dem Flag Experimental Web Platform features stehen (das von chrome://flags aktiviert werden kann, wie unten gezeigt).

Weitere Beispiele
Wir werden nicht ins Detail gehen, wie diese Demos funktionieren, da die Grundidee des Mischungseffekts mit exclusion oder difference dieselbe ist wie zuvor und die Geometrie-/Animationsanteile außerhalb des Rahmens dieses Artikels liegen. Für jedes der folgenden Beispiele gibt es jedoch einen Link zu einer CodePen-Demo in der Beschreibung, und viele dieser Pens enthalten auch eine Aufnahme von mir, wie ich sie von Grund auf programmiere.
Hier ist eine Kreuzbalkenanimation, die ich kürzlich nach einem Bees & Bombs GIF erstellt habe.

Und hier ist eine sich wiederholende Mondanimation von vor ein paar Jahren, ebenfalls nach einem Bees & Bombs GIF.

Wir sind nicht unbedingt auf black und white beschränkt. Wenn wir einen Kontrastfilter mit einem sub-unitären Wert (filter: contrast(.65) im folgenden Beispiel) auf einem Wrapper verwenden, können wir das Schwarz in ein dunkles Grau und das Weiß in ein helles Grau verwandeln.

Hier ist ein weiteres Beispiel für dieselbe Technik.

Wenn wir es so aussehen lassen wollen, als hätten wir einen XOR-Effekt zwischen schwarzen Formen auf weißem Hintergrund, können wir filter: invert(1) auf den Wrappern der Formen verwenden, wie im folgenden Beispiel.

Und wenn wir etwas Mildereres wie dunkelgraue Formen auf einem hellgrauen Hintergrund wollen, greifen wir nicht zur vollständigen Inversion, sondern nur zur teilweisen. Das bedeutet, wir verwenden einen sub-unitären Wert für den invertieren filter, wie im folgenden Beispiel, wo wir filter: invert(.85) verwenden.

Es muss nicht unbedingt etwas wie eine Schleifen- oder Ladeanimation sein. Wir können auch einen XOR-Effekt zwischen dem Hintergrund eines Elements und seinem versetzten Rahmen haben. Genau wie in den vorherigen Beispielen verwenden wir die CSS-filter-Inversion, wenn wir möchten, dass der Hintergrund und der Rahmen black sind und ihre Schnittmenge white ist.

Ein weiteres Beispiel wäre ein XOR-Effekt beim Hovern/Fokussieren und Klicken auf eine Schließen-Schaltfläche. Das folgende Beispiel zeigt sowohl Nacht- als auch Tagthema-Fälle.
Bring me to life
Dinge können nur in Schwarz-Weiß ein wenig traurig aussehen, daher gibt es ein paar Dinge, die wir tun können, um solchen Demos etwas Leben einzuhauchen.
Die erste Taktik wäre die Verwendung von Filtern. Wir können uns vom Schwarz-Weiß-Zwang befreien, indem wir sepia() *nachdem* wir den Kontrast reduziert haben (da diese Funktion auf reinem black oder white keine Wirkung hat), verwenden. Wählen Sie den Farbton mit hue-rotate() und verfeinern Sie dann das Ergebnis mit brightness() und saturate() oder contrast().
Zum Beispiel könnten wir, wenn wir eine der vorherigen Schwarz-Weiß-Demos nehmen, die folgende filter-Kette auf dem Wrapper haben:
filter:
contrast(.65) /* turn black and white to greys */
sepia(1) /* retro yellow-brownish tint */
hue-rotate(215deg) /* change hue from yellow-brownish to purple */
blur(.5px) /* keep edges from getting rough/ jagged */
contrast(1.5) /* increase saturation */
brightness(5) /* really brighten background */
contrast(.75); /* make triangles less bright (turn bright white dirty) */

Für noch mehr Kontrolle über das Ergebnis besteht immer die Möglichkeit, SVG-Filter zu verwenden.
Die zweite Taktik wäre, eine weitere Ebene hinzuzufügen, eine, die nicht schwarz und weiß ist. Zum Beispiel habe ich in dieser radioaktiven Torten-Demo, die ich für die erste CodePen-Challenge im März erstellt habe, ein lila ::before-Pseudoelement auf dem body verwendet, das ich mit dem Torten-Wrapper gemischt habe.
body, div { display: grid; }
/* stack up everything in one grid cell */
div, ::before { grid-area: 1/ 1; }
body::before { background: #7a32ce; } /* purple layer */
/* applies to both pie slices and the wrapper */
div { mix-blend-mode: exclusion; }
.a2d { background: #000; } /* black wrapper */
.pie {
background: /* variable size white pie slices */
conic-gradient(from calc(var(--p)*(90deg - .5*var(--sa)) - 1deg),
transparent,
#fff 1deg calc(var(--sa) + var(--q)*(1turn - var(--sa))),
transparent calc(var(--sa) + var(--q)*(1turn - var(--sa)) + 1deg));
}
Dies verwandelt den schwarzen Wrapper in lila und die weißen Teile in grün (was lila invertiert ist).

Eine weitere Option wäre, den gesamten Wrapper wieder mit einer anderen Ebene zu mischen, diesmal mit einem anderen Mischmodus als difference oder exclusion. Dies würde uns mehr Kontrolle über das Ergebnis geben, sodass wir nicht nur auf Komplementäre (wie Schwarz und Weiß oder Lila und Grün) beschränkt sind. Das müssen wir jedoch in einem zukünftigen Artikel behandeln.
Schließlich gibt es die Option, difference (und nicht exclusion) zu verwenden, damit wir black erhalten, wenn sich zwei identische (nicht unbedingt white) Ebenen überlappen. Zum Beispiel ist die Differenz zwischen coral und coral immer 0 auf allen drei Kanälen, was black bedeutet. Das bedeutet, wir können eine Demo wie die mit versetztem und XOR-Rahmen anpassen, um das folgende Ergebnis zu erzielen:

Mit richtig eingestellten transparent-Rändern und Hintergrund-Clipping können wir dies auch für Gradienten-Hintergründe funktionieren lassen.

Ebenso können wir sogar ein Bild anstelle eines Verlaufs haben!

Beachten Sie, dass dies bedeutet, dass wir auch den Bildhintergrund invertieren müssen, wenn wir das Element im zweiten Themaszenario invertieren. Aber das sollte kein Problem sein, denn in diesem Artikel haben wir auch gelernt, wie das geht: indem wir background-color auf white setzen und die Bildschicht damit unter Verwendung von background-blend-mode: exclusion mischen!
Schlussgedanken
Allein diese beiden Mischmodi können uns einige wirklich coole Ergebnisse liefern, ohne auf Canvas, SVG oder duplizierte Ebenen zurückgreifen zu müssen. Aber wir haben hier nur an der Oberfläche gekratzt. In zukünftigen Artikeln werden wir uns damit beschäftigen, wie andere Mischmodi funktionieren und was wir mit ihnen allein oder in Kombination mit früheren oder anderen CSS-visuellen Effekten wie Filtern erreichen können. Und glauben Sie mir, je mehr Tricks Sie auf Lager haben, desto cooler werden die Ergebnisse, die Sie erzielen können!
Toller Artikel und FANTASTISCHE Demos!
Wirklich beeindruckt von diesem Artikel, großartige Arbeit