Slice and Dice a Disc with CSS

Avatar of Ana Tudor
Ana Tudor am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Ich bin kürzlich auf ein interessantes Design für eine geschnittene Scheibe gestoßen. Die Scheibe hatte einen diagonalen Farbverlauf und war in horizontale Scheiben unterteilt, die von links nach rechts leicht versetzt waren. Natürlich habe ich angefangen darüber nachzudenken, wie man das am effizientesten mit CSS machen könnte.

Screenshot. Shows a diagonal gradient disc that has been split into eight horizontal slices, one on top of the other, with tiny gaps in between them and slightly offset to the left or right (with respect to the vertical axis of the disc) based on parity.
Geschnittene Farbverlaufsscheibe.

Der erste Gedanke war, dass das mit border-radius machbar sein sollte, oder? Nun, nein! Das Ding mit border-radius ist, dass es eine elliptische Ecke erzeugt, deren Enden tangential zu den verbundenen Kanten sind.

Mein zweiter Gedanke war, eine circle() Clipping-Pfad zu verwenden. Nun, es stellt sich heraus, dass diese Lösung wie ein Zauber funktioniert, also schauen wir sie uns genau an!

Beachten Sie, dass die folgenden Demos in Edge nicht funktionieren, da Edge clip-path auf HTML-Elementen noch nicht unterstützt. Dies könnte alles mit verschachtelten Elementen und overflow: hidden emuliert werden, um browserübergreifende Unterstützung zu haben, aber der Einfachheit halber zerlegen wir die clip-path-Methode in diesem Artikel.

Scheibe in gleiche Teile schneiden

Was die HTML-Struktur angeht, generieren wir sie mit einem Präprozessor, um Wiederholungen zu vermeiden. Zuerst entscheiden wir uns für eine Anzahl von Scheiben n. Dann übergeben wir diese Zahl als benutzerdefinierte Eigenschaft --n an CSS. Schließlich generieren wir die Scheiben in einer Schleife und übergeben den Index jeder einzelnen als weitere benutzerdefinierte Eigenschaft --i an CSS.

- var n = 8;

style :root { --n: #{n} }

- for(var i = 0; i < n; i++)
  .slice(style=`--i: ${i}`)

Weiter geht es mit CSS: Zuerst legen wir einen Durchmesser $d für unsere Scheibe fest. Das ist die width unserer Scheiben. Die height ist der Durchmesser geteilt durch die Anzahl der Elemente calc(#{$d}/var(--n)).

Um sie unterscheiden zu können, geben wir unseren Scheiben Dummy-Hintergründe, die von der Parität bestimmt werden.

$d: 20em;

.slice {
  --parity: 0;
  width: $d;
  height: calc(#{$d}/var(--n));
  background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%));
  
  &:nth-of-type(2n) { --parity: 1 }
}

Wir positionieren unsere Scheiben auch in der Mitte mit einem Spalten-flex-Layout auf ihrem Container (in unserem Fall body).

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Um die Scheibenform zu erhalten, verwenden wir einen circle() Clipping-Pfad mit dem Radius $r, der der Hälfte des Durchmessers .5*$d entspricht, und dem Mittelpunkt genau in der Mitte der Anordnung. Da wir diesen clip-path auf die Scheiben anwenden, ist die Position des Mittelpunkts für jede Scheibe relativ zur Scheibe selbst.

Horizontal ist er immer in der Mitte, bei 50% der Scheibe. Vertikal muss er in der Mitte der Anordnung sein, hier kommen die Gesamtzahl der Elemente und der Index des Elements, die wir als CSS-Variablen vom Präprozessor-Code übergeben haben, ins Spiel.

In der Mitte der Anordnung bedeutet, die halbe Höhe der Anordnung von der Oberseite der Anordnung. Die halbe Höhe der Anordnung ist der halbe Durchmesser .5*$d, was dem Radius $r entspricht. Aber dieser Wert bezieht sich auf die gesamte Anordnung, und wir brauchen einen, der sich auf die aktuelle Scheibe bezieht. Um dies zu erreichen, subtrahieren wir die vertikale Position der aktuellen Scheibe relativ zur Anordnung, d.h. wie weit die Oberseite der aktuellen Scheibe relativ zur Oberseite der Anordnung liegt.

Die erste Scheibe (mit Index --i: 0) befindet sich ganz oben in der Anordnung, daher subtrahieren wir in diesem Fall 0.

Die zweite Scheibe (mit Index --i: 1) befindet sich auf einer Scheibenhöhe von oben in der Anordnung (dem Platz, den die erste Scheibe einnimmt), daher subtrahieren wir in diesem Fall 1 Scheibenhöhe.

Die dritte Scheibe (mit Index --i: 2) befindet sich auf zwei Scheibenhöhen von oben in der Anordnung (dem Platz, den die erste und zweite Scheibe einnehmen), daher subtrahieren wir in diesem Fall 2 Scheibenhöhen.

Im Allgemeinen ist der Betrag, den wir für jede Scheibe subtrahieren, der Index der Scheibe (--i) multipliziert mit einer Scheibenheight.

--h: calc(#{d}/var(--n)); /* slice height */
clip-path: circle($r at 50% calc(#{$r} - var(--i)*var(--h))

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Danach können wir die Scheiben basierend auf der Parität versetzen.

--sign: calc(1 - 2*var(--parity));
transform: translate(calc(var(--sign)*2%))

Jetzt haben wir unsere geschnittene Scheibe!

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Scheibenabstand

Der erste Gedanke, der einem hier in den Sinn kommt, ist die Verwendung von margin für jede Scheibe.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Dies mag in manchen Fällen ein gutes Ergebnis sein, aber was, wenn wir nicht möchten, dass sich unsere Scheibe verlängert?

Nun, wir haben die Möglichkeit, den background auf die content-box zu beschränken und einen vertikalen padding hinzuzufügen.

box-sizing: border-box;
padding: .125em 0;
background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%)) 
            content-box;

Natürlich müssen wir in diesem Fall sicherstellen, dass box-sizing auf border-box gesetzt ist, damit der vertikale padding nicht zur height hinzukommt.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Das einzige kleine Problem in diesem Fall ist, dass auch die Oberseite der ersten Scheibe und die Unterseite der letzten Scheibe abgeschnitten werden. Dies ist in manchen Fällen kein Problem und wir können immer den padding-top für das :first-of-type und den padding-bottom für das :last-of-type auf 0 zurücksetzen.

.slice {
  /* other styles */
  padding: .125em 0;
  
  &:first-of-type { padding-top: 0 }
  &:last-of-type { padding-bottom: 0 }
}

Wir haben jedoch auch eine Einzeiler-Lösung für dieses Problem, Lücken zwischen den Scheiben zu erzeugen: Fügen Sie dem Container eine mask hinzu!

Diese mask ist ein repeating-linear-gradient(), der transparente Streifen mit der Dicke der Lücke $g erzeugt, sich nach einer Scheibenheight wiederholt und horizontal auf den Scheibendurchmesser $d und vertikal auf den Scheibendurchmesser $d minus einer Lücke $g (damit wir nicht ganz oben und ganz unten maskieren, wie wir es anfangs auch mit dem padding-Ansatz gemacht haben) beschränkt.

mask: repeating-linear-gradient(red 0, red calc(var(--h) - #{$g}), 
                                transparent 0, transparent var(--h)) 
        50% calc(50% - #{.5*$g})/ #{$d} calc(#{$d} - #{$g})

Beachten Sie, dass wir in diesem Fall die Scheibenheight-Variable --h auf dem Container setzen müssen, da wir sie für die mask verwenden.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Kontinuierlicher background

Um einen kontinuierlichen Farbverlauf-background zu erhalten, müssen wir diesem background eine Höhe geben, die der der Scheibe entspricht, und seine vertikale Position relativ zu jeder Scheibe so einstellen, dass er immer von der Oberseite der Anordnung ausgeht ... wo auch immer sich diese relativ zur Scheibe befindet.

Die Oberseite der ersten Scheibe (mit Index --i: 0) fällt mit der der Anordnung zusammen, also beginnt unser Hintergrund vertikal bei 0.

Die Oberseite der zweiten Scheibe (mit Index --i: 1) liegt 1 Scheibenhöhe unter der der Anordnung, also beginnt ihr background vertikal 1 Scheibenheight darüber. Da die positive Richtung der y-Achse nach unten zeigt, bedeutet dies, dass unsere background-position entlang der y-Achse in diesem Fall calc(-1*var(--h)) ist.

Die Oberseite der dritten Scheibe (mit Index --i: 2) liegt 2 Scheibenhöhen unter der der Anordnung, also beginnt ihr background vertikal 2 Scheibenhöhen darüber. Das macht unsere background-position entlang der y-Achse calc(-2*var(--h)).

Wir erkennen ein Muster: Im Allgemeinen ist die background-position entlang der y-Achse für eine Scheibe calc(-1*var(--i)*var(--h)).

background: 
  linear-gradient(#eccc05, #c26e4c, #a63959, #4e2255, #333f3d)
 
    /* background-position */
    50% calc(-1*var(--i)*var(--h))/ 

    100% $d /* background-size */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Aber wenn wir einen Farbverlauf von links nach rechts wollen, dann ist unser Hintergrund nicht mehr kontinuierlich, was wirklich offensichtlich wird, wenn wir die Stopppositionen etwas anpassen, um abrupte Änderungen zu erzielen.

background: linear-gradient(90deg, 
       #eccc05 33%, #c26e4c 0, #a63959 67%, #4e2255 0, #333f3d) 
 
    /* background-position */
    50% calc(-1*var(--i)*var(--h))/ 

    100% $d /* background-size */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Um dieses Problem zu beheben, setzen wir den Offset als Sass-Variable $o, setzen die horizontale background-size auf die Scheibenwidth (100% oder $d) plus zweimal den Offset und stellen sicher, dass wir den background für die nach links bewegten Scheiben (in negativer x-Richtung, also um -$o) auf der linken Seite der Scheibe (background-position entlang der x-Achse ist 0%) und für die nach rechts bewegten Scheiben (in positiver x-Richtung, also um $o) auf der rechten Seite der Scheibe (background-position entlang der x-Achse ist 100%) anhängen.

$o: 2%;
transform: translate(calc(var(--sign)*#{$o}));
background: linear-gradient(90deg, 
       #eccc05 33%, #c26e4c 0, #a63959 67%, #4e2255 0, #333f3d) 
 
    /* background-position */
    calc((1 - var(--parity))*100%) calc(-1*var(--i)*var(--h))/ 

    calc(100% + #{2*$o}) $d /* background-size */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Dies funktioniert für Farbverläufe in jedem Winkel, wie in der interaktiven Demo unten zu sehen ist – ziehen Sie, um den Winkel des Farbverlaufs zu ändern.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Es funktioniert auch für Bilder, obwohl wir in diesem Fall den zweiten background-size-Wert entfernen müssen, damit das Bild nicht verzerrt wird, was uns die Einschränkung hinterlässt, vertikale Wiederholungen zu erhalten, wenn das Seitenverhältnis des Bildes größer ist als calc(#{$d} + #{2*$o}) : #{$d}. Dies ist bei dem quadratischen Bild, das wir unten verwenden, nicht der Fall, aber es ist trotzdem etwas zu beachten.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Eine weitere Anmerkung ist, dass oben die Oberseite des Bildes an der Oberseite der Anordnung angebracht ist. Wenn wir möchten, dass die Mitte des Bildes an der Mitte der Anordnung angebracht ist, müssen wir die vertikale Komponente der background-position etwas anpassen.

Zunächst verwenden wir eine background-position von 50%, um die Mitte des Bildes an die Mitte einer Scheibe zu heften. Aber wir wollen nicht die Mitte des Bildes in der Mitte jeder Scheibe, sondern in der Mitte der Anordnung für alle Scheiben. Wir kennen bereits den Abstand von der Oberseite jeder Scheibe bis zum vertikalen Mittelpunkt der gesamten Anordnung – es ist die y-Koordinate des zentralen Punktes des Clipping-Kreises.

--y: calc(#{$r} - var(--i)*var(--h));
clip-path: circle($r at 50% var(--y))

Der Abstand vom vertikalen Mittelpunkt jeder Scheibe zu dem der Anordnung beträgt diesen Wert --y minus die halbe height einer Scheibe. Es ergibt sich also, dass die background-position, die wir entlang der y-Achse benötigen, um den vertikalen Mittelpunkt des Bildes an dem der Anordnung zu befestigen, calc(50% + var(--y) - .5*var(--h)) ist.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Inkrementelle Scheiben

Das bedeutet, unsere Scheiben haben nicht mehr die gleiche height. Zum Beispiel könnte die erste eine height von einer Einheit haben, die zweite die doppelte height, die dritte die dreifache height und so weiter...

Die addierten Höhen all dieser Scheiben sollten dem Scheibendurchmesser entsprechen. Anders ausgedrückt, wir sollten folgende Gleichheit haben:

h + 2*h + 3*h + ... + n*h = d

Dies kann auch so geschrieben werden:

h*(1 + 2 + 3 + ... + n) = d

was es leichter macht, etwas zu bemerken! Innerhalb der Klammern haben wir die Summe der ersten n natürlichen Zahlen, die immer n*(n + 1)/2 ist!

Also wird unsere Gleichheit zu

h*n*(n + 1)/2 = d

Dies ermöglicht uns, die Einheitsheight h zu erhalten.

h = 2*d/n/(n + 1)

Wenn wir dies auf unsere Demo anwenden, erhalten wir

--h: calc(#{2*$d}/var(--n)/(var(--n) + 1));
height: calc((var(--i) + 1)*var(--h));

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Genau wie im Fall gleicher Scheiben ist die y-Koordinate des Mittelpunkts des Clipping-circle() der Scheibenradius $r abzüglich des Abstands von der Oberseite der Anordnung bis zur Oberseite der aktuellen Scheibe. Dies ist die Summe der Höhen aller vorherigen Scheiben.

Im Fall der ersten Scheibe (--i: 0) haben wir keine vorherige Scheibe, also ist diese Summe 0.

Im Fall der zweiten Scheibe (--i: 1) haben wir nur die erste Scheibe davor, und ihre Höhe ist die Einheitsheight (--h).

Im Fall der dritten Scheibe (--i: 2) ist die gesuchte Summe die zwischen der height der ersten Scheibe, die der Einheitsheight entspricht, und der der zweiten Scheibe, die doppelt so hoch ist wie die Einheitsheight. Das ist calc(var(--h) + 2*var(--h)) oder calc(var(--h)*(1 + 2)).

Im Fall der dritten Scheibe (--i: 3) ist die Summe die zwischen der height der ersten Scheibe, die der Einheitsheight entspricht, der der zweiten Scheibe, die doppelt so hoch ist wie die Einheitsheight, und der der dritten Scheibe, die dreimal so hoch ist wie die Einheitsheight. Das ist calc(var(--h) + 2*var(--h) + 3*var(--h)) oder calc(var(--h)*(1 + 2 + 3)).

Jetzt können wir ein Muster erkennen! Für jede Scheibe mit dem Index --i gilt, dass die addierte Höhe ihrer vorherigen Scheiben die Einheitsheight --h mal der Summe der ersten --i natürlichen Zahlen ist (und die Summe der ersten --i natürlichen Zahlen ist calc(var(--i)*(var(--i) + 1)/2)). Das bedeutet, unser clip-path-Wert wird:

circle($r at 50% calc(var(--h)*var(--i)*(var(--i) + 1)/2))

Wir fügen den Offset wieder hinzu und erhalten folgendes Ergebnis:

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Leider können bei inkrementellen Scheiben die repeating-linear-gradient()-Maskenmethode zum Erzeugen von Lücken nicht mehr funktionieren. Was jedoch immer noch gut funktioniert, ist die vertikale padding-Methode, und wir können die Padding-Werte so setzen, dass die obere 0 für die erste Scheibe und die untere 0 für die letzte Scheibe ist.

padding: 
  calc(var(--i)*#{$g}/var(--n)) /* top */
  0 /* lateral */
  calc((var(--n) - 1 - var(--i))*#{$g}/var(--n)) /* bottom */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für einen Farbverlauf-background bleibt die Hauptidee dieselbe wie im Fall gleicher Scheiben. Es gibt nur zwei Dinge, die wir berücksichtigen müssen.

Erstens, die background-position entlang der y-Achse ist der Abstand (im Absolutwert) zwischen der Oberseite der Anordnung und der Oberseite der aktuellen Scheibe. Dieser Abstand ist nicht mehr calc(var(--i)*var(--h)) wie im Fall gleicher Scheiben mit der Höhe --h. Stattdessen ist er, wie vorhin berechnet, calc(var(--i)*(var(--i) + 1)/2*var(--h)). Also ist die background-position entlang der y-Achse calc(-1*var(--i)*(var(--i) + 1)/2*var(--h)).

Und zweitens wollen wir, dass unser background auf die content-box zugeschnitten wird, damit wir die Lücken behalten, aber wir müssen die background-origin auf ihrem ursprünglichen Wert von padding-box belassen, damit unser Farbverlauf kontinuierlich bleibt.

background: 
  linear-gradient(var(--a), 
                #eccc05, #c26e4c, #a63959, #4e2255, #333f3d) 

    /* background-position */
    calc((1 - var(--parity))*100%) /* x component */ 
    calc(-1*var(--i)*(var(--i) + 1)/2*var(--h)) /* y component */ / 

    /* background-size */
    calc(100% + #{2*$o}) $d 

    padding-box /* background-origin */
    content-box /* background-clip */;

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für einen Bild-background, dessen Mittelpunkt an der Mitte unserer Anordnung angebracht ist, müssen wir berücksichtigen, dass eine halbe Scheibenheight nicht mehr bei allen Scheiben der gleiche Wert ist. Jetzt ist die height einer Scheibe calc((var(--i) + 1)*var(--h)), also ist dies der Wert, den wir in der Formel für die y-Komponente der background-position subtrahieren müssen.

--y: calc(#{$r} - .5*var(--i)*(var(--i) + 1)*var(--h));
background: 
  url(/amur_leopard.jpg) 

    /* background-position */
    calc((1 - var(--parity))*100%) /* x component */
    calc(50% + var(--y) - .5*(var(--i) + 1)*var(--h)) /* y component */ / 

    /* background-size */
    calc(100% + #{2*$o}) 

    padding-box /* background-origin */
    content-box /* background-clip */;
clip-path: circle($r at 50% var(--y));

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Vertikale Scheiben

Wir können unsere Scheibe auch in die andere Richtung schneiden. Das bedeutet, die Deklaration flex-direction: column vom Container zu entfernen und die flex-direction auf die ursprüngliche (row) zu setzen, die width und height, die x- und y-Koordinaten des zentralen Punkts des kreisförmigen Clipping-Pfads, die Richtung, entlang der wir die Scheiben verschieben, die Dimensionen und x- und y-Positionen des Maskierungs-Farbverlaufs, den wir auch drehen müssen, damit er entlang der x-Achse verläuft.

body {
  /* same as before */
  --w: calc(#{$d}/var(--n));
  mask: repeating-linear-gradient(90deg, 
                                  red 0, red calc(var(--w) - #{$g}), 
                                  transparent 0, transparent var(--w)) 
          calc(50% - #{.5*$g}) 50% / calc(#{$d} - #{$g}) #{$d}
}

.slice {
  /* same as before */
  width: var(--w); height: $d;
  transform: translatey(calc(var(--sign)*2%));
  background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%));
  clip-path: circle($r at calc(#{$r} - var(--i)*var(--w)) 50%)
}

Dies ergibt uns gleiche vertikale Scheiben mit abwechselnden Hintergründen.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für den Farbverlaufsfall müssen wir auch die beiden background-Dimensionen und die background-Positionen entlang der x- und y-Achsen umkehren.

background: 
  linear-gradient(135deg, 
      #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) 

    /* background-position */
    calc(-1*var(--i)*var(--w)) calc((1 - var(--parity))*100%)/ 

    #{$d} calc(100% + #{2*$o}) /* background-size */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für inkrementelle Scheiben kombinieren wir den inkrementellen Fall mit dem vertikalen Fall, was bedeutet, dass wir die Werte, die wir für den vorherigen inkrementellen Fall haben, entlang beider Achsen vertauschen.

--w: calc(#{2*$d}/var(--n)/(var(--n) + 1));
width: calc((var(--i) + 1)*var(--w)); height: $d;
clip-path: circle($r at calc(#{$r} - .5*var(--i)*(var(--i) + 1)*var(--w)) 50%);

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Um die Lücken zu erzeugen, verwenden wir die padding-Methode. Da wir uns aber jetzt im vertikalen Fall befinden, benötigen wir horizontale Paddings, links und rechts, und müssen sicherstellen, dass das padding-left für die erste Scheibe 0 ist und das padding-right für die letzte Scheibe ebenfalls 0 ist.

box-sizing: border-box;
padding: 
  0 /* top */
  calc((var(--n) - 1 - var(--i))*#{$g}/var(--n)) /* right */
  0 /* bottom */
  calc(var(--i)*#{$g}/var(--n)) /* left */;
background: 
  hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%)) 
  content-box

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Schließlich haben wir den Farbverlaufsfall.

background: 
  linear-gradient(135deg, 
      #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%)
    
    /* background-position */ 
    calc(-.5*var(--i)*(var(--i) + 1)*var(--w)) /* x component */
    calc((1 - var(--parity))*100%) /* y component */ / 

    /* background-size */
    #{$d} calc(100% + #{2*$o}) 

    padding-box /* background-origin */
    content-box /* background-clip */;

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

2D-Fall

Auch hier generieren wir dies mit etwas Pug, wobei die Gesamtzahl der Elemente das Produkt aus der Anzahl der Spalten und der Anzahl der Zeilen ist. Der Einfachheit halber halten wir die Anzahl der Zeilen und die Anzahl der Spalten gleich.

- var n = 8, m = Math.pow(n, 2);

style :root { --n: #{n}; --i: 0; --j: 0 }
  - for(var i = 1; i < n; i++) {
    | .tile:nth-of-type(#{n}n + #{i + 1}) { --i: #{i} }
    | .tile:nth-of-type(n + #{n*i + 1}) { --j: #{i} }
  - }
- for(var i = 0; i < m; i++)
  .tile

Wir haben auch die Spalten- und Zeilenindizes (--i und --j) an CSS übergeben.

Da wir uns im 2D-Fall befinden, wechseln wir von der Verwendung eines 1D-Layouts (flex) zu einem 2D-Layout (grid). Wir beginnen auch mit dem Scheibendurchmesser $d und, da die Anzahl der Spalten gleich der Anzahl der Zeilen ist (--n), wird unsere Scheibe in identische Kacheln mit der Kantenlänge --l: calc(#{$d}/var(--n)) unterteilt.

$d: 20em;

body {
  --l: calc(#{$d}/var(--n));
  display: grid;
  place-content: center;
  grid-template: repeat(var(--n), var(--l))/ repeat(var(--n), var(--l))
}

Um die Lücken zwischen den Kacheln zu erzeugen, verwenden wir den padding-Ansatz auf den .tile-Elementen und kombinieren die horizontalen und vertikalen Fälle, so dass das padding-top für die erste Zeile 0 ist, das padding-left für die erste Spalte 0 ist, das padding-bottom für die letzte Zeile 0 ist und das padding-right für die letzte Spalte 0 ist.

padding: 
  calc(var(--j)*#{$g}/var(--n)) /* top */
  calc((var(--n) - 1 - var(--i))*#{$g}/var(--n)) /* right */
  calc((var(--n) - 1 - var(--j))*#{$g}/var(--n)) /* bottom */
  calc(var(--i)*#{$g}/var(--n)) /* left */

Beachten Sie, dass wir den Zeilenindex --j für die Richtung von oben nach unten (vertikale Paddings) und den Spaltenindex --i für die Richtung von links nach rechts (seitliche Paddings) verwendet haben.

Um die Scheibenform zu erhalten, kombinieren wir erneut die horizontalen und vertikalen Fälle und verwenden den Spaltenindex --i, um die x-Koordinate des zentralen Punkts des kreisförmigen Clipping-Pfads zu erhalten, und den Zeilenindex --j, um seine y-Koordinate zu erhalten.

clip-path: 
  circle($r at calc(#{$r} - var(--i)*var(--l)) 
               calc(#{$r} - var(--j)*var(--l)))

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für einen Farbverlauf-background kombiniert dies erneut die horizontalen und vertikalen Fälle und berücksichtigt, dass wir hier an diesem Punkt keinen Offset haben, was bedeutet, dass die background-size der Scheibendurchmesser $d entlang beider Achsen ist.

background: 
  linear-gradient(135deg, 
      #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) 

    /* background-position */
    calc(-1*var(--i)*var(--l)) 
    calc(-1*var(--j)*var(--l)) / 

    #{$d} #{$d} /* background-size */
    padding-box /* background-origin */
    content-box /* background-clip */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für einen Bild-background entfernen wir den zweiten background-size-Wert, um zu verhindern, dass das Bild gestreckt wird, wenn es nicht quadratisch ist. Wir passen auch den Code zum Anbringen des Mittelpunkts des Bildes an den Mittelpunkt des Gitters aus dem 1D-Fall an den 2D-Fall an.

--x: calc(#{$r} - var(--i)*var(--l));
--y: calc(#{$r} - var(--j)*var(--l));
background: url(/amur_leopard.jpg)

    /* background-position */ 
    calc(50% + var(--x) - .5*var(--l)) 
    calc(50% + var(--y) - .5*var(--l)) / 

    #{$d} /* background-size */
    padding-box /* background-origin */ 
    content-box /* background-clip */;
clip-path: circle($r at var(--x) var(--y))

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Im inkrementellen Fall haben wir keine gleichen Abmessungen für alle Kacheln, daher verwenden wir auto-Sizing für grid-template.

body {
  /* same as before */
  grid-template: repeat(var(--n), auto)/ repeat(var(--n), auto)
}

Genau wie im 1D-Fall beginnen wir mit der Berechnung einer einheitlichen Kantenlänge --u.

--u: calc(#{2*$d}/var(--n)/(var(--n) + 1))

Wir setzen dann inkrementelle Abmessungen entlang beider Achsen für unsere Kachelelemente.

width: calc((var(--i) + 1)*var(--u));
height: calc((var(--j) + 1)*var(--u))

Wir müssen auch die Koordinaten des zentralen Punkts des Clipping-Kreises an den inkrementellen Fall anpassen.

clip-path: 
  circle($r at calc(#{$r} - .5*var(--i)*(var(--i) + 1)*var(--u)) 
               calc(#{$r} - .5*var(--j)*(var(--j) + 1)*var(--u)))

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Für einen Farbverlauf-background passen wir die Version mit gleichen Kacheln an den inkrementellen Fall an. Das bedeutet, wir passen die background-position an, wie wir es zuvor für die inkrementellen Scheiben getan haben, nur dass wir es jetzt entlang beider Achsen tun, nicht nur entlang einer.

background: 
  linear-gradient(135deg, 
      #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) 

    /* background-position */
    calc(-.5*var(--i)*(var(--i) + 1)*var(--l)) 
    calc(-.5*var(--j)*(var(--j) + 1)*var(--l)) / 

    #{$d} #{$d} /* background-size */
    padding-box /* background-origin */
    content-box /* background-clip */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Schließlich haben wir die Bild-background-Option für den inkrementellen 2D-Fall.

background: url(/amur_leopard.jpg)  

    /* background-position */
    calc(50% + var(--x) - .5*(var(--i) + 1)*var(--u)) 
    calc(50% + var(--y) - .5*(var(--j) + 1)*var(--u)) / 

    #{$d} /* background-size */
    padding-box /* background-origin */
    content-box /* background-clip */

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Es gibt wahrscheinlich noch mehr Variationen, die wir uns einfallen lassen könnten, aber hier hören wir auf. Wenn Sie weitere Ideen haben, wie Sie dies weiterentwickeln können, würde ich sie gerne hören!