Anfang 2018, als ich mich tiefer mit CSS-Gradientenmasken beschäftigte, um interessante visuelle Effekte zu erzielen, die man sonst nicht mit einem einzigen Element und ein wenig CSS für möglich halten würde, lernte ich eine Eigenschaft kennen, die mir vorher völlig unbekannt war: mask-composite.
Da dies keine weit verbreitete Eigenschaft ist, konnte ich keine umfassenden Ressourcen zu diesem Thema finden. Als ich begann, sie mehr zu nutzen und mehr darüber zu lernen (manche erinnern sich vielleicht, dass ich sie schon in einigen anderen Artikeln erwähnt habe), beschloss ich, selbst eine solche Ressource zu erstellen, und so entstand dieser Artikel! Hier behandle ich, wie mask-composite funktioniert, warum sie nützlich ist, welche Werte sie annehmen kann, was jeder von ihnen bewirkt, wie es mit der Unterstützung aussieht und welche Alternativen wir in nicht unterstützenden Browsern haben.
Was Mask Compositing leistet
Mask Compositing ermöglicht es uns, verschiedene mask-Ebenen mithilfe verschiedener Operationen zu einer einzigen zu kombinieren. Kombinieren wie? Nun, Pixel für Pixel! Betrachten wir zwei mask-Ebenen. Wir nehmen jedes Paar entsprechender Pixel, wenden eine bestimmte Compositing-Operation (wir werden jede mögliche Operation später im Detail besprechen) auf ihre Kanäle an und erhalten ein drittes Pixel für die resultierende Ebene.

Beim Compositing zweier Ebenen wird die obere Ebene als Quelle bezeichnet, während die untere Ebene als Ziel bezeichnet wird. Das ergibt für mich nicht wirklich Sinn, da Quelle wie eine Eingabe und Ziel wie eine Ausgabe klingt, aber in diesem Fall sind beide Eingaben und die Ausgabe ist die Ebene, die wir als Ergebnis der Compositing-Operation erhalten.

Wenn wir mehr als zwei Ebenen haben, erfolgt das Compositing 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 werden zusammengefügt und das Ergebnis wird zum Ziel für die zweite Stufe, in der die dritte Ebene von unten die Quelle ist. Das Zusammenfügen der dritten Ebene mit dem Ergebnis des Zusammenfügens der ersten beiden ergibt das Ziel für die dritte Stufe, in der die vierte Ebene von unten die Quelle ist.

Und so weiter, bis wir zur letzten Stufe gelangen, in der die oberste Ebene mit dem Ergebnis des Zusammenfügens aller darunter liegenden Ebenen zusammengefügt wird.
Warum Mask Compositing nützlich ist
Sowohl CSS- als auch SVG-Masken haben ihre Grenzen, ihre Vor- und Nachteile. Wir können die Einschränkungen von SVG-Masken umgehen, indem wir CSS-Masken verwenden, aber da CSS-Masken anders funktionieren als SVG-Masken, können wir mit dem CSS-Weg ohne Compositing bestimmte Ergebnisse nicht erzielen.
Um all dies besser zu verstehen, betrachten wir das folgende Bild eines fabelhaften sibirischen Tigerbabys

Und sagen wir, wir wollen diesen Maskierungseffekt darauf erzielen

Diese spezielle mask hält die rhomboedrischen Formen sichtbar, während die sie trennenden Linien maskiert werden und wir durch das Bild auf das dahinterliegende Element sehen können.
Wir möchten auch, dass dieser Maskierungseffekt flexibel ist. Wir wollen nicht an die Abmessungen oder das Seitenverhältnis des Bildes gebunden sein und wir wollen in der Lage sein, einfach (durch Ändern eines %-Werts in einen px-Wert) zwischen einer mask, die mit dem Bild skaliert, und einer, die das nicht tut, zu wechseln.
Um dies zu tun, müssen wir zunächst verstehen, wie SVG- und CSS-Masken jeweils funktionieren und was wir mit ihnen tun können und was nicht.
SVG-Maskierung
SVG-Masken sind standardmäßig luminance-Masken. Das bedeutet, dass die Pixel des maskierten Elements, die den weißen mask-Pixeln entsprechen, vollständig opak sind, die Pixel des maskierten Elements, die den schwarzen mask-Pixeln entsprechen, vollständig transparent sind und die Pixel des maskierten Elements, die den mask-Pixeln irgendwo zwischen Schwarz und Weiß in Bezug auf die Leuchtdichte entsprechen (Grau, Pink, Lime), halbtransparent sind.
Die Formel zur Ermittlung der Leuchtdichte aus einem gegebenen RGB-Wert lautet.2126·R + .7152·G + .0722·B
Für unser spezielles Beispiel bedeutet dies, dass wir die rhomboedrischen Bereiche weiß und die sie trennenden Linien schwarz machen müssen, wodurch das unten sichtbare Muster entsteht

Um das obige Muster zu erhalten, beginnen wir mit einem weißen SVG-Rechteckelement rect. Dann könnte man denken, wir müssten viele schwarze Linien zeichnen... aber das müssen wir nicht! Stattdessen fügen wir nur einen path hinzu, der aus den beiden Diagonalen dieses Rechtecks besteht, und stellen sicher, dass sein stroke schwarz ist.
Um die erste Diagonale (von oben links nach unten rechts) zu erstellen, verwenden wir einen "move to" (M)-Befehl zur oberen linken Ecke, gefolgt von einem "line to" (L)-Befehl zur unteren rechten Ecke.
Um die zweite Diagonale (von oben rechts nach unten links) zu erstellen, verwenden wir einen "move to" (M)-Befehl zur oberen rechten Ecke, gefolgt von einem "line to" (L)-Befehl zur unteren linken Ecke.
Unser bisheriger Code ist
svg(viewBox=[0, 0, w, h].join(' '))
rect(width=w height=h fill='#fff')
path(d=`M0 0
L${w} ${h}
M${w} 0
L0 ${h}` stroke='#000')
Das bisherige Ergebnis sieht dem gewünschten rhomboedrischen Muster noch nicht ähnlich...
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
...aber das wird sich ändern! Wir erhöhen die Dicke (stroke-width) der schwarzen diagonalen Linien und machen sie gestrichelt, wobei die Lücken zwischen den Strichen (7%) größer sind als die Striche selbst (1%).
svg(viewBox=[0, 0, w, h].join(' '))
rect(width=w height=h fill='#fff')
path(d=`M0 0
L${w} ${h}
M${w} 0
L0 ${h}` stroke='#000'
stroke-width='15%'
stroke-dasharray='1% 7%')
Siehst du jetzt, worauf das hinausläuft?
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wenn wir die Dicke (stroke-width) unserer schwarzen diagonalen Linien auf einen Wert wie 150% erhöhen, bedecken sie schließlich das gesamte Rechteck und ergeben das Muster, das wir angestrebt haben!
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Jetzt können wir unsere rect- und path-Elemente in ein mask-Element einpacken und diese mask auf jedes gewünschte Element anwenden – in unserem Fall das Tigerbild.
svg(viewBox=[0, 0, w, h].join(' '))
mask#m
rect(width=w height=h fill='#fff')
path(d=`M0 0
L${w} ${h}
M${w} 0
L0 ${h}` stroke='#000'
stroke-width='15%'
stroke-dasharray='1% 7%')
img(src='image.jpg' width=w)
img { mask: url(#m) }
Das obige *sollte* funktionieren. Aber leider ist die Praxis nicht perfekt. Zu diesem Zeitpunkt erhalten wir das erwartete Ergebnis nur in Firefox (Live-Demo). Schlimmer noch, dass das gewünschte maskierte Muster in Chrome nicht angezeigt wird, bedeutet nicht, dass unser Element unmaskiert bleibt – die Anwendung dieser mask lässt es vollständig verschwinden! Natürlich benötigt Chrome für die mask-Eigenschaft das -webkit--Präfix (wenn es auf HTML-Elemente angewendet wird), und wenn das Präfix fehlt, versucht es nicht einmal, die mask auf unser Element anzuwenden.
Die einfachste Abhilfe für img-Elemente ist, sie in SVG-image-Elemente umzuwandeln.
svg(viewBox=[0, 0, w, h].join(' ') width=w)
mask#m
rect(width=w height=h fill='#fff')
path(d=`M0 0
L${w} ${h}
M${w} 0
L0 ${h}` stroke='#000'
stroke-width='15%'
stroke-dasharray='1% 7%')
image(xlink:href=url width=w mask='url(#m)')
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Dies liefert uns das gewünschte Ergebnis, aber wenn wir ein anderes HTML-Element als ein img maskieren wollen, wird es etwas komplizierter, da wir es mit foreignObject in das SVG einbeziehen müssten.
Schlimmer noch, mit dieser Lösung sind wir auf feste Abmessungen festgelegt, was sich immer unangenehm anfühlt.
Natürlich können wir die mask absurd groß machen, sodass sie wahrscheinlich kein Bild abdecken könnte. Aber das fühlt sich genauso schlimm an wie feste Abmessungen.
Wir können auch versuchen, das Problem der festen Abmessungen zu lösen, indem wir maskContentUnits auf objectBoundingBox umstellen
svg(viewBox=[0, 0, w, h].join(' '))
mask#m(maskContentUnits='objectBoundingBox')
rect(width=1 height=1 fill='#fff')
path(d=`M0 0
L1 1
M1 0
L0 1` stroke='#000'
stroke-width=1.5
stroke-dasharray='.01 .07')
image(xlink:href=url width='100%' mask='url(#m)')
Aber wir legen die Abmessungen immer noch im viewBox fest, und während ihre tatsächlichen Werte nicht wirklich wichtig sind, ist ihr Seitenverhältnis es. Darüber hinaus wird unser Maskierungsmuster jetzt innerhalb eines 1x1-Quadrats erstellt und dann gedehnt, um das maskierte Element abzudecken.
Formstreckung bedeutet Formverzerrung, weshalb unsere rhomboedrischen Formen nicht mehr so aussehen, wie sie es vorher getan haben.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Ugh.
Wir können die Start- und Endpunkte der beiden Linien, aus denen unser Pfad besteht, anpassen
svg(viewBox=[0, 0, w, h].join(' '))
mask#m
rect(width=1 height=1 fill='#fff')
path(d=`M-.75 0
L1.75 1
M1.75 0
L-.75 1` stroke='#000'
stroke-width=1.5
stroke-dasharray='.01 .07')
image(xlink:href=url width='100%' mask='url(#m)')
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Um jedoch ein bestimmtes rhomboedrisches Muster mit bestimmten Winkeln für unsere rhomboedrischen Formen zu erhalten, müssen wir das Seitenverhältnis des Bildes kennen.
Seufz. Lassen wir es gut sein und sehen wir, was wir mit CSS machen können.
CSS-Maskierung
CSS-Masken sind standardmäßig alpha-Masken. Das bedeutet, dass die Pixel des maskierten Elements, die den vollständig opaken mask-Pixeln entsprechen, vollständig opak sind, die Pixel des maskierten Elements, die den vollständig transparenten mask-Pixeln entsprechen, vollständig transparent sind und die Pixel des maskierten Elements, die halbtransparenten mask-Pixeln entsprechen, halbtransparent sind. Im Grunde erhält jedes einzelne Pixel des maskierten Elements den Alpha-Kanal des entsprechenden mask-Pixels.
Für unseren speziellen Fall bedeutet dies, die *rhomboedrischen Bereiche opak und die sie trennenden Linien transparent zu machen*, also sehen wir, wie wir das mit CSS-Gradienten machen können!
Um das Muster mit weißen rhomboedrischen Bereichen und schwarzen trennenden Linien zu erhalten, können wir zwei wiederholende lineare Gradienten überlagern
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
repeating-linear-gradient(-60deg,
#000 0, #000 5px, transparent 0, transparent 35px),
repeating-linear-gradient(60deg,
#000 0, #000 5px, #fff 0, #fff 35px)
Dies ist das Muster, das funktioniert, *wenn* wir eine luminance-mask haben.
Aber im Fall einer alpha-mask sind es nicht die schwarzen Pixel, die uns volle Transparenz geben, sondern die transparenten. Und es sind nicht die weißen Pixel, die uns volle Opazität geben, sondern die vollständig opaken – rot, schwarz, weiß... sie alle erfüllen ihren Zweck! Ich persönlich benutze gerne rot oder tan, da dies nur drei Buchstaben zum Tippen bedeutet und je weniger Buchstaben zu tippen sind, desto weniger Möglichkeiten für schreckliche Tippfehler, die eine halbe Stunde zum Debuggen dauern können.
Der erste Gedanke ist also, die gleiche Technik anzuwenden, um opake rhomboedrische Bereiche und transparente trennende Linien zu erhalten. Aber dabei stoßen wir auf ein Problem: Die opaken Teile der zweiten Gradientenschicht bedecken Teile der ersten Schicht, die wir immer noch transparent halten möchten, und umgekehrt.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Was wir also bekommen, ist ziemlich weit entfernt von opaken rhomboedrischen Bereichen und transparenten trennenden Linien.
Meine ursprüngliche Idee war, das Muster mit weißen rhomboedrischen Bereichen und schwarzen trennenden Linien zu verwenden, kombiniert mit der Einstellung von mask-mode auf luminance, um das Problem zu lösen, indem die CSS-Maske wie eine SVG-Maske funktioniert.
Diese Eigenschaft wird nur von Firefox unterstützt, obwohl es für WebKit-Browser das nicht standardmäßige mask-source-type gibt. Und leider ist die Unterstützung nicht einmal das größte Problem, da weder die standardmäßige Firefox-Methode noch die nicht standardmäßige WebKit-Methode das gewünschte Ergebnis liefern (Live-Demo).
Glücklicherweise ist mask-composite zur Stelle, um zu helfen! Sehen wir uns also an, welche Werte diese Eigenschaft annehmen kann und welche Auswirkungen sie jeweils hat.
mask-composite-Werte und was sie tun
Zuerst entscheiden wir uns für zwei Gradientenebenen für unsere mask und das zu maskierende Bild.
Die beiden Gradienten-mask-Ebenen, die wir zur Veranschaulichung der Wirkung jedes Wertes dieser Eigenschaft verwenden, sind wie folgt
--l0: repeating-linear-gradient(90deg,
red, red 1em,
transparent 0, transparent 4em);
--l1: linear-gradient(red, transparent);
mask: var(--l1) /* top (source) layer */,
var(--l0) /* bottom (destination) layer */
Diese beiden Ebenen sind im folgenden Pen als background-Gradienten zu sehen (beachten Sie, dass der body einen Hintergrund mit Hashtag hat, damit die transparenten und halbtransparenten Gradientenbereiche deutlicher werden)
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die obere Ebene (--l1) ist die Quelle, während die untere Ebene (--l0) das Ziel ist.
Wir wenden die mask auf dieses Bild eines wunderschönen Amur-Leoparden an.

mask anwenden.Gut, jetzt, da wir das hinter uns haben, sehen wir uns an, welche Wirkung jeder mask-composite-Wert hat!
add
Dies ist der initial-Wert, der uns den gleichen Effekt gibt, als ob wir mask-composite gar nicht angeben würden. Was in diesem Fall passiert, ist, dass die Gradienten übereinander gelegt werden und die resultierende mask angewendet wird.
Beachten Sie, dass im Fall von halbtransparenten mask-Ebenen die Alphas nicht einfach addiert werden, trotz des Namens des Wertes. Stattdessen wird die folgende Formel verwendet, wobei α₁ der Alpha des Pixels in der Quelle (oberen) Ebene und α₀ der Alpha des entsprechenden Pixels in der Ziel (unteren) Ebene ist
α₁ + α₀ – α₁·α₀
Wo immer *mindestens eine* mask-Ebene vollständig opak ist (ihr Alpha ist 1), ist die resultierende mask vollständig opak und die entsprechenden Pixel des maskierten Elements werden vollständig opak dargestellt (mit einem Alpha von 1).
Wenn die Quell-(obere)-Ebene vollständig opak ist, ist α₁ 1, und wenn wir in der Formel ersetzen, haben wir
1 + α₀ - 1·α₀ = 1 + α₀ - α₀ = 1
Wenn die Ziel-(untere)-Ebene vollständig opak ist, ist α₀ 1, und wir haben
α₁ + 1 – α₁·1 = α₁ + 1 – α₁ = 1
Wo immer *beide* mask-Ebenen vollständig transparent sind (ihre Alphas sind 0), ist die resultierende mask vollständig transparent und die entsprechenden Pixel des maskierten Elements sind daher ebenfalls vollständig transparent (mit einem Alpha von 0).
0 + 0 – 0·0 = 0 + 0 + 0 = 0
Unten sehen wir, was das für die mask-Ebenen bedeutet, die wir verwenden – wie die Ebene aussieht, die wir als Ergebnis des Compositing erhalten, und das Endergebnis, das die Anwendung auf unser Amur-Leopardenbild erzeugt.

mask-composite: add für zwei gegebene Ebenen bewirkt.subtract
Der Name bezieht sich auf das "Subtrahieren" des Ziels (untere Ebene) von der Quelle (obere Ebene). Auch hier handelt es sich nicht um eine einfache gekappte Subtraktion, sondern es wird die folgende Formel verwendet
α₁·(1 – α₀)
Die obige Formel bedeutet, dass überall dort, wo die Quelle (obere) Ebene vollständig transparent ist oder wo die Ziel-(untere)-Ebene vollständig opak ist, die resultierende mask ebenfalls vollständig transparent ist und die entsprechenden Pixel des maskierten Elements ebenfalls vollständig transparent sind, da alles, was mit 0 multipliziert wird, 0 ergibt.
Wenn die Quell-(obere)-Ebene vollständig transparent ist, ergibt das Ersetzen ihres Alphas durch 0 in unserer Formel
0·(1 – α₀) = 0
Wenn die Ziel-(untere)-Ebene vollständig opak ist, ergibt das Ersetzen ihres Alphas durch 1 in unserer Formel
α₁·(1 – 1) = α₁·0 = 0
Das bedeutet, dass wir bei Verwendung der zuvor definierten mask und der Einstellung von mask-composite: subtract Folgendes erhalten

mask-composite: subtract für zwei gegebene Ebenen bewirkt.Beachten Sie, dass die Formel in diesem Fall nicht symmetrisch ist. Wenn also α₁ und α₀ nicht gleich sind, *erhalten wir nicht das Gleiche, wenn wir die beiden Maskenebenen vertauschen* (α₁·(1 – α₀) ist nicht dasselbe wie α₀·(1 – α₁)). Das bedeutet, wir haben ein anderes visuelles Ergebnis, wenn wir die Reihenfolge der beiden Ebenen vertauschen!

mask-composite: subtract, wenn die beiden gegebenen Ebenen vertauscht wurden.intersect
In diesem Fall sehen wir nur die Pixel des maskierten Elements dort, wo sich die beiden mask-Ebenen überschneiden. Die verwendete Formel ist das Produkt der Alphas der beiden Ebenen
α₁·α₀
Was aus der obigen Formel resultiert, ist, dass überall dort, wo *entweder* die mask-Ebene vollständig transparent ist (ihr Alpha ist 0), die resultierende mask ebenfalls vollständig transparent ist und die entsprechenden Pixel des maskierten Elements ebenfalls vollständig transparent sind.
Wenn die Quell-(obere)-Ebene vollständig transparent ist, ergibt das Ersetzen ihres Alphas durch 0 in unserer Formel
0·α₀ = 0
Wenn die Ziel-(untere)-Ebene vollständig transparent ist, ergibt das Ersetzen ihres Alphas durch 0 in unserer Formel
α₁·0 = 0
Außerdem ist überall dort, wo *beide* mask-Ebenen vollständig opak sind (ihre Alphas sind 1), die resultierende mask vollständig opak und somit auch die entsprechenden Pixel des maskierten Elements. Dies liegt daran, dass, wenn die Alphas der beiden Ebenen beide 1 sind, wir haben
1·1 = 1
Im besonderen Fall unserer mask bedeutet die Einstellung von mask-composite: intersect, dass wir

mask-composite: intersect für zwei gegebene Ebenen bewirkt.exclude
In diesem Fall wird jede Ebene im Grunde von der anderen ausgeschlossen, wobei die Formel lautet
α₁·(1 – α₀) + α₀·(1 – α₁)
In der Praxis bedeutet diese Formel, dass überall dort, wo *beide* mask-Ebenen vollständig transparent (ihre Alphas sind 0) oder vollständig opak (ihre Alphas sind 1) sind, die resultierende mask ebenfalls vollständig transparent ist und die entsprechenden Pixel des maskierten Elements ebenfalls vollständig transparent sind.
Wenn beide mask-Ebenen vollständig transparent sind, ergibt das Ersetzen ihrer Alphas durch 0 in unserer Formel
0·(1 – 0) + 0·(1 – 0) = 0·1 + 0·1 = 0 + 0 = 0
Wenn beide mask-Ebenen vollständig opak sind, ergibt das Ersetzen ihrer Alphas durch 1 in unserer Formel
1·(1 – 1) + 1·(1 – 1) = 1·0 + 1·0 = 0 + 0 = 0
Es bedeutet auch, dass überall dort, wo eine Ebene vollständig transparent ist (ihr Alpha ist 0), während die andere vollständig opak ist (ihr Alpha ist 1), die resultierende mask vollständig opak ist und somit auch die entsprechenden Pixel des maskierten Elements.
Wenn die Quell-(obere)-Ebene vollständig transparent ist, während die Ziel-(untere)-Ebene vollständig opak ist, ergibt das Ersetzen von α₁ durch 0 und α₀ durch 1
0·(1 – 1) + 1·(1 – 0) = 0·0 + 1·1 = 0 + 1 = 1
Wenn die Quell-(obere)-Ebene vollständig opak ist, während die Ziel-(untere)-Ebene vollständig transparent ist, ergibt das Ersetzen von α₁ durch 1 und α₀ durch 0
1·(1 – 0) + 0·(1 – 1) = 1·1 + 0·0 = 1 + 0 = 1
Mit unserer mask bedeutet die Einstellung von mask-composite: exclude, dass wir

mask-composite: exclude für zwei gegebene Ebenen bewirkt.Anwendung auf unseren Anwendungsfall
Wir kehren zu den beiden Gradienten zurück, mit denen wir versucht haben, das rhomboedrische Muster zu erzielen
--l1: repeating-linear-gradient(-60deg,
transparent 0, transparent 5px,
tan 0, tan 35px);
--l0: repeating-linear-gradient(60deg,
transparent 0, transparent 5px,
tan 0, tan 35px)
Wenn wir die vollständig opaken (in diesem Fall tan) Teile halbtransparent machen (sagen wir rgba(tan, .5)), gibt das visuelle Ergebnis einen Hinweis darauf, wie Compositing hier helfen könnte
$c: rgba(tan, .5);
$sw: 5px;
--l1: repeating-linear-gradient(-60deg,
transparent 0, transparent #{$sw},
#{$c} 0, #{$c} #{7*$sw});
--l0: repeating-linear-gradient(60deg,
transparent 0, transparent #{$sw},
#{$c} 0, #{$c} #{7*$sw})
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die gewünschten rhomboedrischen Bereiche bilden sich an der Schnittstelle zwischen den halbtransparenten Streifen. Das bedeutet, dass die Verwendung von mask-composite: intersect den Trick tun sollte!
$sw: 5px;
--l1: repeating-linear-gradient(-60deg,
transparent 0, transparent #{$sw},
tan 0, tan #{7*$sw});
--l0: repeating-linear-gradient(60deg,
transparent 0, transparent #{$sw},
tan 0, tan #{7*$sw});
mask: var(--l1) intersect, var(--l0)
Beachten Sie, dass wir die Compositing-Operation sogar in die Kurzschreibweise aufnehmen können! Was ich wirklich liebe, denn je weniger Chancen es gibt, mindestens zehn Minuten damit zu verschwenden, nicht zu verstehen, warum masj-composite, msdk-composite, nask-composite, mask-comoisite und ähnliches nicht funktionieren, desto besser!
Dies liefert nicht nur das gewünschte Ergebnis, sondern wenn wir nun die Breite des transparenten Streifens in einer Variablen speichern, ändert sich dieser Wert zu einem %-Wert (sagen wir $sw: .05%), und die Maske skaliert mit dem Bild!
Wenn die Breite des transparenten Streifens ein px-Wert ist, bleiben sowohl die rhomboedrischen Formen als auch die trennenden Linien gleich groß, während sich das Bild mit dem Viewport vergrößert und verkleinert.

transparenten trennenden Linien zwischen den rhomboedrischen Formen eine px-breite haben.Wenn die Breite des transparenten Streifens ein %-Wert ist, sind sowohl die rhomboedrischen Formen als auch die trennenden Linien relativ zur Größe des Bildes und skalieren daher mit diesem.

transparenten trennenden Linien zwischen den rhomboedrischen Formen eine %-breite haben.Zu gut, um wahr zu sein? Wie sieht es mit der Unterstützung dafür aus?
Die schlechte Nachricht ist, dass mask-composite derzeit nur von Firefox unterstützt wird. Die gute Nachricht ist, dass wir eine Alternative für WebKit-Browser haben, sodass wir die Unterstützung erweitern können.
Unterstützung erweitern
WebKit-Browser unterstützen (und unterstützen schon seit sehr langer Zeit) eine nicht standardmäßige Version dieser Eigenschaft, -webkit-mask-composite, die andere Werte benötigt, um zu funktionieren. Diese äquivalenten Werte sind
source-overfüraddsource-outfürsubtractsource-infürintersectxorfürexclude
Um also eine Cross-Browser-Version zu haben, müssen wir nur noch die WebKit-Version hinzufügen, richtig?
Nun, leider ist die Sache nicht so einfach.
Erstens können wir diesen Wert nicht in der -webkit-mask-Kurzschreibweise verwenden, Folgendes funktioniert nicht
-webkit-mask: var(--l1) source-in, var(--l0)
Und wenn wir die Compositing-Operation aus der Kurzschreibweise herausnehmen und die Langform danach schreiben, wie unten gezeigt
-webkit-mask: var(--l1), var(--l0);
-webkit-mask-composite: source-in;
mask: var(--l1) intersect, var(--l0)
...verschwindet das gesamte Bild vollständig!
Und wenn Sie das für seltsam halten, prüfen Sie dies: Bei Verwendung der anderen drei Operationen add/ source-over, subtract/ source-out, exclude/ xor erhalten wir das erwartete Ergebnis sowohl in WebKit-Browsern als auch in Firefox. Nur der Wert source-in macht die Dinge in WebKit-Browsern kaputt!
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Was ist los?!
Warum macht *dieser spezielle Wert* Probleme in WebKit?
Als ich dies zum ersten Mal sah, verbrachte ich ein paar gute Minuten damit, nach einem Tippfehler in source-in zu suchen, dann kopierte ich ihn aus einer Referenz, dann aus einer zweiten, falls die erste Referenz ihn falsch hatte, dann aus einer dritten… und dann hatte ich endlich eine andere Idee!
Es scheint, als ob im Falle der nicht standardmäßigen WebKit-Alternative auch eine Komposition zwischen der untersten Ebene und einer Ebene aus nichts (als völlig transparent betrachtet) darunter angewendet wird.
Für die anderen drei Operationen macht dies absolut keinen Unterschied. In der Tat, das Hinzufügen, Subtrahieren oder Ausschließen von nichts ändert nichts. Wenn wir die Formeln für diese drei Operationen nehmen und α₀ durch 0 ersetzen, erhalten wir immer α₁
add/source-over:α₁ + 0 – α₁·0 = α₁ + 0 - 0 = α₁subtract/source-out:α₁·(1 – 0) = α₁·1 = α₁exclude/xor:α₁·(1 – 0) + 0·(1 – α₁) = α₁·1 + 0 = α₁
Die Schnittmenge mit nichts ist jedoch eine andere Geschichte. Die Schnittmenge mit nichts ist nichts! Das wird auch dadurch veranschaulicht, dass α₀ in der Formel für die intersect/ source-in Operation durch 0 ersetzt wird
α₁·0 = 0
Der Alpha-Wert der resultierenden Ebene ist in diesem Fall 0, also kein Wunder, dass unser Bild komplett maskiert wird!
Die erste Korrektur, die mir einfiel, war also, eine andere Operation zu verwenden (es spielt keine große Rolle, welche der anderen drei, ich wählte xor, weil sie weniger Buchstaben hat und man sie durch doppeltes Klicken vollständig auswählen kann), um die unterste Ebene mit dieser Ebene aus nichts darunter zu komponieren
-webkit-mask: var(--l1), var(--l0);
-webkit-mask-composite: source-in, xor;
mask: var(--l1) intersect, var(--l0)
Und ja, das funktioniert!
Sie können das unten eingebettete Element skalieren, um zu sehen, wie sich die mask verhält, wenn sie mit dem Bild skaliert wird und wenn nicht.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Beachten Sie, dass wir die nicht standardmäßige WebKit-Version *vor* die standardmäßige setzen müssen, damit WebKit-Browser, wenn sie schließlich auch die standardmäßige Version implementieren, diese überschreiben.
Nun, das ist alles! Ich hoffe, Sie haben diesen Artikel genossen und etwas Neues daraus gelernt.
Ein paar weitere Demos
Bevor wir schließen, hier sind zwei weitere Demos, die zeigen, warum mask-composite cool ist.
Die erste Demo zeigt eine Reihe von 1-Element-Regenschirmen. Jeder „Biss“ wird mit einem radial-gradient() erstellt, den wir von der vollen Kreisform excluden. Chrome hat ein kleines Rendering-Problem, aber das Ergebnis sieht in Firefox perfekt aus.

mask-composite (Live-Demo).Die zweite Demo zeigt drei 1-Element-Ladeanzeigen (wobei nur die zweiten beiden mask-composite verwenden). Beachten Sie, dass die animation hier nur in Chrome funktioniert, da sie Houdini benötigt.

mask-composite (Live-Demo).Und Sie – welche anderen Anwendungsfälle fallen Ihnen ein?
Das ist großartig. Macht mich das neugierig, ob das mehr Rechenleistung kostet als ein Bild, das dasselbe tun kann?
Ich wusste nicht, dass ich nicht der Einzige bin, der nicht jede CSS-Eigenschaft kennt. Das gibt mir ein besseres Gefühl, nach ein paar Jahren wieder mit CSS zu arbeiten. Vieles hat sich geändert. Ich bin sehr neugierig auf Flex und Grid, das klingt interessant.
Danke für das Tutorial! Großartiges neues Design, das Sie hier haben!
Ich würde die Benennung von Quelle und Ziel folgendermaßen erklären
Sie haben nur eine „Haupt“-Maske, mit der Sie arbeiten. Diese Maske kann mit anderen Masken modifiziert werden, aber anstatt in jedem Schritt eine neue Instanz einer Maske zu erhalten, modifizieren Sie die vorhandene Maske an Ort und Stelle.
Die (in Ihren Worten) „obere“ Maske ist also die Quelle der Modifikation der „unteren“ Maske, d. h. der Hauptmaske, die am Ende angewendet wird.