Als ich Chris' Artikel über eingekerbte Boxen sah, fiel mir ein, dass ich vor einiger Zeit die Herausforderung hatte, ein Design wie das untenstehende browserübergreifend mit CSS umzusetzen.

Es sieht dem Konzept der eingekerbten Boxen ziemlich ähnlich, nur dass die Ecken jetzt abgerundet sind und wir uns nur um eine Ecke pro Box kümmern müssen. Mal sehen, wie wir das machen können, wie wir die Technik auf mehrere Ecken ausweiten können, auf welche Probleme wir stoßen und wie wir sie mit oder ohne Kompromisse bei der Browserunterstützung umgehen können.
Die erste Idee: box-shadow!
Wir beginnen mit einem Box-Element
<div class='box'></div>
Wir können diesem einige Abmessungen geben oder die Abmessungen vom Inhalt bestimmen lassen – das spielt keine Rolle. Der Einfachheit halber setzen wir nur eine max-width und eine min-height dafür. Wir geben ihm auch eine outline, damit wir seine Grenzen sehen können.
.box {
outline: solid 2px;
max-width: 15em;
min-height: 10em;
}
Als Nächstes positionieren wir absolut ein quadratisches ::before Pseudo-Element, dessen Kantenlänge gleich dem Durchmesser (oder dem doppelten Radius $r) der Ausstanzung in der Ecke ist. Wir geben diesem Pseudo-Element auch einen rötlichen box-shadow und einen Dummy-background (den wir später entfernen werden), nur damit wir es besser sehen können.
$r: 2em;
.box {
position: relative;
/* same styles as before */
&:before {
position: absolute;
padding: $r;
box-shadow: 0 0 7px #b53;
background: #95a;
content: ''
}
}
Und das ist es, was wir bisher haben
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Nun, es sieht noch nicht allzu aufregend aus… noch nicht! Also machen wir weiter und verwandeln dieses Quadrat in eine Scheibe, indem wir ihm border-radius: 50% geben und einen negativen margin gleich seinem Radius $r hinzufügen, damit sein Mittelpunkt mit dem Punkt (0,0) (obere linke Ecke) seiner Eltern-Box übereinstimmt. Wir setzen auch overflow: hidden auf der Eltern-Box, damit alles von diesem Pseudo-Element, das sich außerhalb der .box befindet, abgeschnitten wird.
$r: 2em;
.box {
overflow: hidden;
/* same styles as before */
&:before {
/* same styles as before */
margin: -$r;
border-radius: 50%
}
}
Jetzt fangen wir an, die Form zu sehen, die wir angestrebt haben
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Aber es ist immer noch nicht ganz das, was wir wollen. Um dorthin zu gelangen, verwenden wir den vierten Längenwert für die box-shadow-Eigenschaft: den **Spread-Radius**. Wenn Sie eine Auffrischung benötigen, wie box-shadow mit diesen vier Werten funktioniert, können Sie sich die interaktive Demo unten ansehen.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Sie haben vielleicht schon erraten, was wir als Nächstes tun. Wir entfernen den Dummy-background, setzen die ersten drei box-shadow-Werte (die x- und y-Offsets und den Weichzeichnungsradius) auf Null und verwenden eine ziemlich große Zahl für den letzten Wert (den Spread-Radius).
box-shadow: 0 0 0 300px;
Die interaktive Demo unten zeigt, wie die Erhöhung des Spread-Radius mehr und mehr seiner Eltern-.box abdeckt.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Der Trick hier ist also, einen Spread-Radius zu haben, der ausreichend groß ist, um den Rest des Elternelements abzudecken. Das Coole daran ist, dass wir den box-shadow halbtransparent machen oder abgerundete Ecken an der Eltern-.box haben können.
.box {
/* same styles as before */
border-radius: 1em;
&:before {
/* same styles as before */
box-shadow: 0 0 0 300px rgba(#95a, .75);
}
}
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Natürlich können wir, genau wie Chris im Artikel über eingekerbte Boxen darauf hingewiesen hat, den Radius der Ausstanzung zu einer CSS-Variable machen und diese dann einfach aus JavaScript modifizieren. Dann aktualisiert sich alles schön, selbst mit Textinhalt in unserer Box.
:root { --r: 50px }
.box {
/* same styles as before */
padding: var(--r);
&:before {
/* same styles as before */
margin: calc(-1*var(--r));
padding: inherit;
}
Beachten Sie, dass wir, wenn wir auch Textinhalt haben, einen negativen z-index auf dem ::before Pseudo-Element setzen und es explizit in der Ecke positionieren müssen, da wir jetzt auch einen padding auf der .box haben, um die Ausstanzung zu kompensieren.
.box {
/* same styles as before */
&:before {
/* same styles as before */
z-index: -1;
top: 0;
left: 0
}
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Anwendung dieser Technik
Nun wollen wir weitergehen und sehen, wie wir dieses Konzept anwenden können, um das Design zu reproduzieren, das ich am Anfang gezeigt habe. In diesem speziellen Fall stimmen die Mittelpunkte der Pseudo-Element-Scheiben nicht mit den Ecken der Box überein, sondern liegen außerhalb, in der Mitte des Raumes zwischen den Boxen.
Die verwendete Struktur ist ziemlich geradlinig, nur ein <header>-Element, gefolgt von vier <article>-Elementen, die ich in einer Pug-Schleife generiert habe.
while n--
article
h3 #{data[n].name}
section
p #{data[n].quote}
a(href='#') go
Wir verwenden ein umgebendes Flexbox-Layout für den <body> mit dem <header> sehr breit und mit einem oder zwei <article>-Elementen pro Zeile, je nachdem, wie breit der Viewport ist.

Wenn wir nur einen <article> pro Zeile haben, haben wir keine abgerundeten Ecken, also ist ihr Radius 0px. Andernfalls geben wir diesem Radius --r einen von Null verschiedenen Wert.
$min-w: 15rem; /* min width of an article element */
$m: 1rem; /* margin of such an element */
html { --r: 0px; }
article {
margin: $m;
min-width: $min-w;
width: 21em;
}
@media (min-width: 2*($min-w + 2*$m) /* enough for 2 per row */) {
html { --r: 4rem; }
article { width: 40%; }
}
Betrachten wir nun nur die Situation, in der wir zwei <article>-Elemente pro Zeile haben (und natürlich eine abgerundete Ecke für jedes, weil das unser Interesse ist).
Im Fall des ersten beginnen wir mit der äußersten linken Grenze der Scheibe entlang der rechten Kante ihres Elternelements. Das ist bisher left: 100%. Um die x-Koordinate des Mittelpunkts der Scheibe am rechten Rand ihres Elternelements zu verschieben, subtrahieren wir den Radius der Scheibe, was uns zu left: calc(100% - var(--r)) bringt. Aber wir wollen sie nicht am rechten Rand, wir wollen sie um den <article>-Margin $m nach rechts versetzt, was uns zum endgültigen Wert bringt.
left: calc(100% - var(--r) + #{$m});
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Entlang der y-Achse beginnen wir mit der obersten Grenze der Scheibe entlang der unteren Kante ihres Elternelements – das ist top: 100%. Um den Mittelpunkt der Scheibe auf den unteren Rand der Eltern-Box zu legen, verschieben wir ihn um einen Radius nach oben, was uns top: calc(100% - var(--r)) ergibt. Schließlich wollen wir, dass dieser Mittelpunkt $m unter dem unteren Rand des Elternelements liegt, was uns zum endgültigen vertikalen Offset von ergibt.
top: calc(100% - var(--r) + #{$m});
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Für den zweiten <article> (zweiter in derselben Zeile) haben wir den gleichen Wert für den vertikalen Offset.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Horizontal beginnen wir jedoch damit, dass die linke Grenze der Scheibe entlang der linken Kante ihres Elternelements liegt – das ist left: 0%. Um den Mittelpunkt der Scheibe auf die linke Kante ihres Elternelements zu legen, verschieben wir ihn um einen Radius --r nach links, wodurch wir left: calc(0% - var(--r)) erhalten. Die endgültige Position liegt jedoch $m links von der linken Kante des Elternelements.
left: calc(0% - var(--r) - #{$m});
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Für den dritten <article> (erster in der letzten Zeile) haben wir den gleichen Wert für den Offset entlang der x-Achse wie im Fall des ersten.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Vertikal beginnen wir damit, dass die obere Grenze der Scheibe entlang der oberen Kante ihres Elternelements liegt – das ist top: 0%. Um den Mittelpunkt der Scheibe auf die obere Kante des Elternelements zu legen, verschieben wir ihn um einen Radius --r nach oben, wodurch wir top: calc(0% - var(--r)) erhalten. Aber wir wollen, dass sie $m über der oberen Kante des Elternelements liegt, also ist der endgültige obere Offset.
top: calc(0% - var(--r) - #{$m});
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Für das letzte (zweite in der letzten Zeile) haben wir den gleichen horizontalen Offset wie im Fall des darüberliegenden und den gleichen vertikalen Offset wie für das links daneben in derselben Zeile.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Also können unsere Offsets geschrieben werden
article:nth-of-type(1) { /* 1st */
left: calc(100%/* 2*50% = (1 + 1)*50% = (1 + i)*50% */ - var(--r) + /* i=+1 */#{$m});
top: calc(100%/* 2*50% = (1 + 1)*50% = (1 + j)*50% */ - var(--r) + /* j=+1 */#{$m});
}
article:nth-of-type(2) { /* 2nd */
left: calc( 0%/* 0*50% = (1 - 1)*50% = (1 + i)*50% */ - var(--r) - /* i=-1 */#{$m});
top: calc(100%/* 2*50% = (1 + 1)*50% = (1 + j)*50% */ - var(--r) + /* j=+1 */#{$m});
}
article:nth-of-type(3) { /* 3rd */
left: calc(100%/* 2*50% = (1 + 1)*50% = (1 + i)*50% */ - var(--r) + /* i=+1 */#{$m});
top: calc( 0%/* 0*50% = (1 - 1)*50% = (1 + j)*50% */ - var(--r) - /* j=-1 */#{$m});
}
article:nth-of-type(4) { /* 4th */
left: calc( 0%/* 0*50% = (1 - 1)*50% = (1 + i)*50% */ - var(--r) - /* i=-1 */#{$m});
top: calc( 0%/* 0*50% = (1 - 1)*50% = (1 + j)*50% */ - var(--r) - /* j=-1 */#{$m});
}
Das bedeutet, dass die Positionen der Mittelpunkte der Scheiben vom Abstand zwischen unseren <article>-Elementen (dieser Abstand ist doppelt so groß wie der margin: $m, den wir ihnen geben), vom Radius r der Scheibe und von ein paar horizontalen und vertikalen Multiplikatoren (jeweils --i und --j) abhängen. Beide Multiplikatoren sind anfangs -1.
Für die ersten beiden <article>-Elemente (in der ersten Zeile des 2x2-Gitters) ändern wir den vertikalen Multiplikator --j auf 1, da wir wollen, dass die y-Koordinate der Mittelpunkte der Scheiben unterhalb der unteren Kante liegt, während wir für die ungeraden (in der ersten Spalte) den horizontalen Multiplikator --i auf 1 ändern, da wir wollen, dass die x-Koordinate rechts von der rechten Kante liegt.
html { --i: -1; --j: -1 } /* multipliers initially set to -1 */
h3, section {
&:before {
/* set generic offsets */
top: calc((1 + var(--j))*50% - var(--r) + var(--j)*#{$m});
left: calc((1 + var(--i))*50% - var(--r) + var(--i)*#{$m});
}
}
@media (min-width: 2*($min-w + 2*$m)) {
article {
/* change vertical multiplier for first two (on 1st row of 2x2 grid) */
&:nth-of-type(-n + 2) { --j: 1 }
/* change horizontal multiplier for odd ones (on 1st column) */
&:nth-of-type(odd) { --i: 1 }
}
Beachten Sie, dass wir nur sichtbare Scheibenausschnitte auf dem <section>-Element für die ersten beiden <article>-Elemente haben und nur auf der <h3> für die letzten beiden. Bei den ersten beiden <article>-Elementen ist der Radius --r auf dem ::before Pseudo-Element der Überschrift 0, während bei den letzten beiden dieser Radius für das ::before Pseudo-Element des Abschnitts 0 ist.
@media (min-width: 2*($min-w + 2*$m)) {
article {
&:nth-of-type(-n + 2) h3,
&:nth-of-type(n + 3) section { &:before { --r: 0 ; } }
}
}
Auf ähnliche Weise fügen wir differenzierte Puffer zu den Kindern der <article>-Elemente hinzu.
$p: .5rem;
h3, section { padding: $p; }
@media (min-width: 2*($min-w + 2*$m)) {
article {
&:nth-of-type(-n + 2) section,
&:nth-of-type(n + 3) h3 {
padding-right: calc(.5*(1 + var(--i))*(var(--r) - #{$m}) + #{$p});
padding-left: calc(.5*(1 - var(--i))*(var(--r) - #{$m}) + #{$p});
}
}
}
Dies hilft uns, das gewünschte Ergebnis zu erzielen.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Die obige Demo funktioniert in aktuellen Versionen aller gängigen Browser und, wenn wir mit etwas Wiederholung statt CSS-Variablen auskommen können, können wir die Unterstützung bis zurück zu IE9 erweitern.
Potenzielle Probleme mit der obigen Methode
Während dies eine schnelle und einfache cross-browser-Methode war, um das gewünschte Ergebnis zu erzielen, sind wir bei diesem Ansatz vielleicht nicht immer so glücklich.
Zuerst brauchen wir ein Pseudo-Element für jede ausgeschnittene Ecke. Wenn wir diesen Effekt also für alle Ecken wollen, brauchen wir ein zusätzliches Element. Trauriger Panda.
Zweitens möchten wir vielleicht nicht immer einen durchgehenden background. Wir möchten vielleicht einen halbtransparenten (was mühsam zu bekommen ist, wenn wir mehr als eine ausgeschnittene Ecke haben wollen), einen mit Gradienten (während wir einige radiale Gradienten mit box-shadow emulieren können, ist es eine unterdurchschnittliche Lösung) oder sogar ein Bild als background (kaum machbar, da die einzige Lösung die Verwendung von mix-blend-mode ist, was Edge-Unterstützung ohne elegantes Fallback ausschneidet).
Und was ist mit wirklich großen Boxen, für die der von uns gesetzte Spread nicht ausreicht? Ugh.
Also, lass uns andere, zuverlässigere Ansätze mit unterschiedlichem Grad an Browserunterstützung erkunden.
Flexibilität *und* gute Browserunterstützung? SVG it!
Das ist wahrscheinlich keine Überraschung, aber die vollständige SVG-Lösung ist die beste, wenn wir heute etwas Flexibles und Zuverlässig Browserübergreifendes wollen. Es ist eine Lösung, die die Verwendung eines SVG-Elements vor dem Inhalt unserer Box beinhaltet. Dieses SVG enthält einen <circle>, auf den wir ein r-Attribut gesetzt haben.
<div class='box'>
<svg>
<circle r='50'/>
</svg>
TEXT CONTENT OF BOX GOES HERE
</div>
Wir positionieren dieses SVG absolut innerhalb der Box und skalieren es so, dass es sein Elternelement vollständig abdeckt.
.box { position: relative; }
svg {
position: absolute;
width: 100%;
height: 100%;
}
Bis hierhin noch nichts allzu Interessantes, also geben wir dem <circle> eine id und klonen es in die anderen Ecken.
<circle id='c' r='50'/>
<use xlink:href='#c' x='100%'/>
<use xlink:href='#c' y='100%'/>
<use xlink:href='#c' x='100%' y='100%'/>
Beachten Sie, dass, wenn wir eine oder mehrere Ecken ausschließen wollen, wir sie einfach nicht dort klonen.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Okay, aber was wir hier getan haben, ist, Kreise in den Ecken zu erzeugen, und was wir eigentlich wollen, ist… das genaue Gegenteil! Was wir als Nächstes tun, ist, diese Kreise in eine <mask> zu setzen, über ein white, vollformatiges (die gesamte SVG abdeckendes) Rechteck, und dann verwenden wir diese mask für ein weiteres vollformatiges Rechteck.
<mask id='m' fill='#fff'>
<rect id='r' width='100%' height='100%'/>
<circle id='c' r='50' fill='#000'/>
<use xlink:href='#c' x='100%'/>
<use xlink:href='#c' y='100%'/>
<use xlink:href='#c' x='100%' y='100%'/>
</mask>
<use xlink:href='#r' fill='#f90' mask='url(#m)'/>
Das Ergebnis ist unten zu sehen.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Wenn wir Text haben, müssen wir die padding der Box an den Radius der Ecke anpassen und diesen mit demselben Wert wie den Radius des SVG-Kreises einstellen und ihn mit JavaScript synchron halten.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Natürlich muss die fill unseres Hintergrundrechtecks nicht einheitlich sein. Es kann auch halbtransparent sein (wie in der obigen Demo) oder wir können einen SVG-Gradienten oder ein Muster dafür verwenden. Letzteres würde uns auch ermöglichen, ein oder mehrere Hintergrundbilder zu verwenden.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Aber ich bin wegen *CSS*-Leckereien hierher gekommen!
Nun, gut, dass Sie fragen! Es gibt eine Reihe von Dingen, die wir hier tun können, um das Gewicht der Maskierungsmethode von SVG auf CSS zu verlagern.
Leider ist keine davon browserübergreifend, aber sie vereinfachen die Dinge und es lohnt sich auf jeden Fall, sie in naher oder ferner Zukunft im Auge zu behalten.
CSS-Maskierung auf HTML-Elementen stattdessen verwenden
Was wir hier tun, ist, alles außerhalb der mask aus dem SVG zu entfernen. Dann setzen wir aus dem CSS einen background (der halbtransparent sein kann, ein CSS-Gradient, ein Bild, eine Kombination aus mehreren Hintergründen… alles, was CSS zu bieten hat) und die mask-Eigenschaft auf das .box-Element.
.box {
/* any kind of background we wish */
mask: url(#m);
}
Beachten Sie, dass das Setzen einer Inline-SVG-Maske auf einem HTML-Element derzeit nur in Firefox funktioniert!

.box (Live-Demo, nur Firefox).Setzen Sie den Kreisradius aus dem CSS
Das bedeutet, das r-Attribut aus unserem <circle> zu entfernen und es im CSS auf dieselbe Variable wie den Box-Padding zu setzen.
.box { padding: var(--r); }
[id='c'] { r: var(--r); }
Auf diese Weise werden beim Ändern des Werts von --r sowohl der Radius der Ausstanzung als auch der padding um den Inhalt der .box aktualisiert!
Beachten Sie, dass das Setzen von Geometrieeigenschaften für SVG-Elemente aus dem CSS derzeit nur in Blink-Browsern funktioniert!

<circle> (Live-Demo, nur Blink).Kombinieren Sie die beiden vorherigen Methoden
Obwohl das cool wäre, ist es leider derzeit in keinem Browser praktisch möglich. Aber die gute Nachricht ist, dass wir noch besser darin werden können!
Verwenden Sie CSS-Gradienten für die Maskierung
Beachten Sie, dass CSS-Maskierung auf HTML-Elementen derzeit überhaupt nicht in Edge funktioniert, obwohl sie als „In Entwicklung“ aufgeführt ist und ein Flag dafür (das derzeit nichts tut) bereits in about:flags aufgetaucht ist.
Wir verwerfen den SVG-Teil vollständig und beginnen mit dem Erstellen unserer CSS-Gradienten-mask. Wir erstellen die Kreise an den Ecken mit radialen Gradienten. Der folgende CSS-Code erstellt einen Kreis mit dem Radius --r in der oberen linken Ecke einer Box.
.box {
background: radial-gradient(circle at 0 0, #000 var(--r, 50px), transparent 0);
}
Dies ist live in der folgenden Demo zu sehen, in der wir der Box auch eine rote Umrandung gegeben haben, nur damit wir ihre Grenzen sehen können.
Siehe das Pen von thebabydino (@thebabydino) auf CodePen.
Wir verwenden denselben Gradienten für unsere mask.
.box {
/* same as before */
/* any CSS background we wish */
mask: radial-gradient(circle at 0 0, #000 var(--r, 50px), transparent 0);
}
Beachten Sie, dass WebKit-Browser für die mask-Eigenschaften immer noch das -webkit--Präfix benötigen.
Wir fügen dann die Kreise an den anderen Ecken hinzu.
$grad-list: radial-gradient(circle at 0 0 , #000 var(--r, 50px), transparent 0),
radial-gradient(circle at 100% 0 , #000 var(--r, 50px), transparent 0),
radial-gradient(circle at 0 100%, #000 var(--r, 50px), transparent 0),
radial-gradient(circle at 100% 100%, #000 var(--r, 50px), transparent 0);
.box {
/* same as before */
/* any CSS background we wish */
mask: $grad-list
}
Das ist wahnsinnig repetitiv, entweder viel Schreibarbeit oder viel Kopieren und Einfügen, also sehen wir mal, was wir dagegen tun können.
Zuerst verwenden wir eine CSS-Variable für die Stopp-Liste. Dies eliminiert Wiederholungen im generierten CSS.
$grad-list: radial-gradient(circle at 0 0 , var(--stop-list)),
radial-gradient(circle at 100% 0 , var(--stop-list)),
radial-gradient(circle at 0 100%, var(--stop-list)),
radial-gradient(circle at 100% 100%, var(--stop-list));
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: #000 var(--r, 50px), transparent 0;
mask: $grad-list;
}
Aber es ist immer noch nicht viel besser, also lassen Sie uns die Ecken innerhalb einer Schleife generieren.
$grad-list: ();
@for $i from 0 to 4 {
$grad-list: $grad-list,
radial-gradient(circle at ($i%2)*100% floor($i/2)*100%, var(--stop-list));
}
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: #000 var(--r, 50px), transparent 0;
mask: $grad-list;
}
Viel besser, was den Code angeht, denn jetzt müssen wir nichts mehr mehrmals schreiben und riskieren, es später nicht überall zu aktualisieren. Aber das bisherige Ergebnis ist nicht das, was wir wollten.

Hier schneiden wir alles bis auf die Ecken aus, was das Gegenteil von dem ist, was wir wollen.
Eine Sache, die wir tun können, ist, die Gradienten umzukehren, die Eckkreise transparent und den Rest black zu machen mit
--stop-list: transparent var(--r, 50px), #000 0;
Dies erledigt die Aufgabe, wenn wir nur einen Gradienten für nur eine Ecke verwenden.

Wenn wir jedoch alle vier (oder auch nur zwei) davon stapeln, erhalten wir ein black-Rechteck in der Größe unserer Box für die mask, was bedeutet, dass nichts mehr maskiert wird.

Also beschränken wir jeden dieser Gradienten auf ein Viertel unserer Box – 50% der width und 50% der height, wodurch wir 25% (ein Viertel) der Fläche für jeden erhalten.
Das bedeutet, dass wir auch eine mask-size von 50% 50%, eine mask-repeat von no-repeat setzen und jede mask-image in der gewünschten Ecke positionieren müssen.
$grad-list: ();
@for $i from 0 to 4 {
$x: ($i%2)*100%;
$y: floor($i/2)*100%;
$grad-list: $grad-list
radial-gradient(circle at $x $y, var(--stop-list)) /* mask image */
$x $y; /* mask position */
}
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: transparent var(--r, 50px), #000 0;
mask: $grad-list;
mask-size: 50% 50%;
mask-repeat: no-repeat;
}
Beachten Sie, dass WebKit-Browser für mask-Eigenschaften immer noch das -webkit--Präfix benötigen.
Aber das große Problem hier ist… das Problem mit Division und Rundung im Allgemeinen – unsere vier Viertel ergeben zusammengesetzt nicht immer wieder ein Ganzes, sodass wir Lücken dazwischen bekommen.

Na ja, wir können diese Lücken nicht mit dünnen linear-gradient()-Streifen abdecken oder die mask-size auf sagen wir 51% erhöhen.

mask-size für jede Gradientenebene behebt das Problem der Lücken (Live-Demo).Aber gibt es nicht einen eleganteren Weg?
Nun, es gibt eine mask-composite-Eigenschaft, die uns helfen kann, wenn wir sie auf intersect setzen, wenn wir zu den vollformatigen Gradientenebenen zurückkehren.
$grad-list: ();
@for $i from 0 to 4 {
$grad-list: $grad-list,
radial-gradient(circle at ($i%2)*100% floor($i/2)*100%, var(--stop-list));
}
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: transparent var(--r, 50px), #000 0;
mask: $grad-list;
mask-composite: intersect;
}
Das ist extrem cool, weil es eine reine CSS-Lösung ohne SVG ist, aber die nicht so gute Nachricht ist, dass die Unterstützung hier auf Firefox 53+ beschränkt ist.

mask-composite: intersect (Live-Demo).Wir haben jedoch zumindest die nicht standardmäßige -webkit-mask-composite-Alternative (mit anderen Werten!) für WebKit-Browser. Das ist also viel besser als die Unterstützung für die letzte Option, die wir haben, wenn es um ausgeschnittene Ecken geht.
Die Option corner-shape
Lea Verou hatte diese Idee vor etwa fünf Jahren und hat sogar eine Vorschauseite dafür erstellt. Leider wird sie von keinem Browser umgesetzt, und die Spezifikation ist in der Zwischenzeit kaum fortgeschritten. Es ist immer noch etwas, das man für die Zukunft im Auge behalten sollte, da sie viel Flexibilität mit sehr wenig Code bietet – die Reproduktion unseres Effekts würde nur Folgendes erfordern.
padding: var(--r);
corner-shape: scoop;
border-radius: var(--r);
Kein Markup-Chaos, keine langen Gradientenlisten, nur dieses sehr einfache Stück CSS. Das heißt… wenn es endlich von Browsern unterstützt wird!
Wenn wir über die Option
corner-shapesprechen, wird die CSS Houdini Paint API helfen, sie zu polyfillen, wie hier demonstriert https://lab.iamvdo.me/houdini/corner-shapeDer negative Teil ist, dass es eine JS-Lösung sein wird.
Es gibt eine **viel** einfachere Möglichkeit, diese Box-Shadow-Positionen in der Box-Shadow-Technik zu machen. Anstatt dieser super komplizierten Positionsberechnungen können Sie die Translate-Transformation verwenden.
So würden Sie die untere rechte Ecke platzieren: (enthält nur den Positionierungscode)
Nun… erlauben Sie mir zu widersprechen.
Erstens, sie sind nicht in den Ecken platziert. Sie sind außerhalb der Ecken in einem Abstand
$mplatziert, der nicht von den Abmessungen der Box abhängt (so dass Sie ihn nicht in%ausdrücken und in die Offsets einmischen können) oder von denen des Radius der Ausstanzung (so dass Sie ihn nicht in%ausdrücken und in die Transformationsbeträge einmischen können). Sie müssten also entweder sowieso einecalc()verwenden oder einenmarginhinzufügen (was bedeuten würde, dass Sie 4 Eigenschaften anstelle von nur 2 verwenden, um dasselbe Ergebnis zu erzielen).Zweitens, die Verwendung von Transforms für die anfängliche 2D-Positionierung ist etwas, das am besten vermieden wird, wann immer möglich (es gibt gültige Anwendungsfälle dafür, aber dies ist definitiv keiner), selbst jetzt, wo CSS-Variablen browserübergreifend unterstützt werden und sie dies weniger problematisch machen können. Die Sache ist, Sie möchten vielleicht nicht, dass diese Ausstanzungen kreisförmig sind, sondern ihre Form mit etwas wie einem Scheren verändern. Aber wenn Sie den
transformbereits für die Positionierung verwenden, dann müssen Sie das Scheren an jede einzelne verdammte Keyframe anhängen. Und wenn Sie die Schere animieren möchten, dann müssen Sie diese Translation in jedem einzelnen verdammten Keyframe anhängen – bäh!Drittens bieten Sie nur ein Beispiel für eine Ecke. Was ist mit den anderen? Wenn Sie wartbaren Code schreiben wollen, der nicht durcheinander gerät, wenn Sie Änderungen vornehmen, müssen Sie immer noch eine Formel finden, die für alle vier Ecken gültig ist, so dass Sie wieder bei diesen Berechnungen landen, egal ob Sie alles in die Offsets packen und den
transformfür den Fall aufheben, dass Sie ihn *wirklich* brauchen, oder ihn töricht verschwenden. Wenn Sie eine einheitliche Formel für alle Ecken haben, müssen Sie später nur eine Änderung an einer Stelle vornehmen. Wenn Sie denken, es ist zu kompliziert, sich hinzusetzen und ein wenig nachzudenken, um zu einer einheitlichen Formel zu gelangen, dann viel Glück, wenn Sie niemals Fehler machen, wenn Sie dasselbe viermal an vier verschiedenen Stellen ändern. Ich persönlich mag es nicht, faulen und fehleranfälligen Code zu schreiben. Ich sitze lieber da und denke ein wenig nach, bevor ich den Code schreibe, und entwickle eine robuste und effiziente Lösung.PS – Nochmals, verketten Sie keine Transforms, wenn Sie nicht müssen.
translate(-50%, -50%)macht genau dasselbe wietranslateX(-50%) translateY(-50%).Obwohl ein alberner Versuch, aber ich denke, dies kann auch durch die Verwendung von
radial-gradientzusammen mitlinear-gradientdarüber erreicht werden. Mit diesem Ansatz ist es auch möglich, einigelinear-gradientals Hintergrund zu verwenden.Es gibt auch einen anderen, vielleicht eleganteren Ansatz.
.box {
box-sizing: border-box;
width: 30rem;
min-height: 20rem;
margin: 5rem auto 0;
padding: 2rem;
–rad-col: #0000ff;
text-align: center;
color: #f1f1f1;
background-color: #0000ff00;
background: radial-gradient(circle at top left, transparent 10%, red 0), radial-gradient(circle at top right, transparent 10%, var(–rad-col) 0), radial-gradient(circle at bottom right, transparent 10%, var(–rad-col) 0), radial-gradient(circle at bottom left, transparent 10%, var(–rad-col) 0);
background-position: top left, top right, bottom right, bottom left;
background-size: 50% 50%;
background-repeat: no-repeat;
}
Oh ja, das ist definitiv möglich, aber ich habe mich entschieden, das nicht in den Artikel aufzunehmen.
Erstens, weil dies ein Ansatz ist, über den bereits geschrieben wurde (Lea hat einen Artikel darüber geschrieben, ich glaube, 2010?), und ich ihn bereits an verschiedenen Stellen verwendet gesehen habe und mich auf Techniken konzentrieren wollte, die ich noch nicht erklärt gesehen habe (da ich dazu neige, sehr ins Detail zu gehen, mache ich mir immer Sorgen, dass Artikel zu lang werden, als dass sie jemand lesen könnte).
Zweitens wegen Rendering-Problemen mit Gradienten. Und mit Rendering-Problemen meine ich sowohl dieses (verdammt, Chrome, ich kann nicht glauben, dass das immer noch nicht behoben ist) als auch die Lücken, die ich auch für die Maskierungsgradienten habe. Und obwohl es im Fall der Maskierungsgradienten nicht so schlimm ist, weil ich immer den Trick
background-size: 51% 51%verwenden kann… dieser Trick kann bei halbtransparenten Hintergründen zu Überlappungen führen.Aber es ist definitiv eine Möglichkeit, wenn man den Trick mit dem nicht-scharfen Übergang für die Kugeln verwendet und einen soliden
backgroundhat oder die Abmessungen des Elements kennt. Denn dann kann man einfach ein einzigesradial-gradient()mit einer geeignetenbackground-positionverwenden, die horizontal die Hälfte der bekanntenwidthund vertikal die Hälfte der bekanntenheightbeträgt.Etwas wie das
Live-Ergebnis.