DRY State Switching With CSS Variables: Fallbacks und ungültige Werte

Avatar of Ana Tudor
Ana Tudor am

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

Dies ist der zweite Teil einer zweiteiligen Serie, die untersucht, wie CSS-Variablen verwendet werden können, um den Code für komplexe Layouts und Interaktionen einfacher zu schreiben und viel einfacher zu warten. Der erste Teil behandelt verschiedene Anwendungsfälle, auf die diese Technik zutrifft. Dieser Teil behandelt die Verwendung von Fallbacks und ungültigen Werten, um die Technik auf nicht-numerische Werte auszuweiten.

Die Strategie, CSS-Variablen zur Steuerung des Wechsels von Layouts und Interaktionen zu verwenden, die wir im ersten Teil dieser Serie behandelt haben, birgt eine wesentliche Einschränkung: Sie funktioniert nur mit numerischen Werten – Längen, Prozentsätze, Winkel, Dauern, Frequenzen, einheitenlose Zahlenwerte und so weiter. Folglich kann es sehr frustrierend sein zu wissen, dass Sie die berechneten Werte von mehr als zehn Eigenschaften mit einer einzigen CSS-Variable umschalten können, aber dann müssen Sie explizit die nicht-numerischen Werte von Eigenschaften wie flex-direction oder text-align von row auf column oder von left auf right oder umgekehrt umschalten.

Ein Beispiel dafür ist das Folgende, bei dem die Eigenschaft text-align von der Parität abhängt und flex-direction davon abhängt, ob wir das Frontend im Wide-Screen-Szenario betrachten oder nicht.

Screenshot collage. On the left, we have the wide screen scenario, with four paragraphs as the four horizontal, offset based on parity slices of a disc. The slice numbering position is either to the right or left of the actual text content, depending on parity. The text alignment also depends on parity. In the middle, we have the normal screen case. The paragraphs are now full width rectangular elements. On the right, we have the narrow screen case. The paragraph numbering is always above the actual text content in this case.
Screenshot-Collage.

Ich habe mich darüber beschwert und als Antwort einen sehr interessanten Vorschlag erhalten, der CSS-Variable-Fallbacks und ungültige Werte nutzt. Es war interessant und gibt uns etwas Neues zum Arbeiten, also beginnen wir mit einer kurzen Zusammenfassung dessen, was dies ist, und gehen von dort aus weiter!

Fallback-Werte

Der Fallback-Wert einer CSS-Variable ist das zweite und optionale Argument der Funktion var(). Betrachten wir zum Beispiel einige .box-Elemente, deren Hintergrund auf eine Variable --c gesetzt ist.

.box { background: var(--c, #ccc) }

Wenn wir woanders keinen Wert für die Variable --c explizit angegeben haben, wird der Fallback-Wert #ccc verwendet.

Nehmen wir nun an, einige dieser Boxen haben eine Klasse .special. Hier können wir --c als eine Art Orange angeben.

.special { --c: #f90 }

Auf diese Weise haben die Boxen mit dieser .special-Klasse einen orangen background, während die anderen den hellgrauen Fallback verwenden.

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

Hier gibt es ein paar Dinge zu beachten.

Erstens kann der Fallback eine weitere CSS-Variable sein, die selbst einen CSS-Variable-Fallback haben kann und… wir können auf diese Weise in ein wirklich tiefes Kaninchenloch geraten!

background: var(--c, var(--c0, var(--c1, var(--c2, var(--c3, var(--c4, #ccc))))))

Zweitens ist eine durch Kommas getrennte Liste ein perfekt gültiger Fallback-Wert. Tatsächlich gilt alles, was nach dem ersten Komma innerhalb der Funktion var() angegeben ist, als Fallback-Wert, wie im folgenden Beispiel zu sehen ist.

background: linear-gradient(90deg, var(--stop-list, #ccc, #f90))

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

Und schließlich, aber nicht zuletzt, können wir *unterschiedliche Fallback-Werte für dieselbe Variable an verschiedenen Stellen haben*, wie dieses Beispiel zeigt.

$highlight: #f90;

a {
  border: solid 2px var(--c, #{rgba($highlight, 0)})
  color: var(--c, #ccc);
  
  &:hover, &:focus { --c: #{$highlight} }
}

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

Ungültige Werte

Zunächst möchte ich klarstellen, was ich damit meine. „Ungültige Werte“ ist kürzer und leichter zu merken, aber es bezieht sich tatsächlich auf jeden Wert, der eine Deklaration zur berechneten Wertzeit ungültig macht.

Betrachten Sie zum Beispiel das folgende Code-Stück.

--c: 1em;
background: var(--c)

1em ist ein gültiger Längenwert, aber dies ist kein gültiger Wert für die Eigenschaft background-color, daher nimmt diese Eigenschaft stattdessen ihren anfänglichen Wert (der transparent ist) an.

Alles zusammenfügen

Nehmen wir an, wir haben eine Reihe von Absätzen, bei denen wir die Helligkeit des color-Wertes ändern, um zwischen black und white basierend auf der Parität zu wechseln (wie im vorherigen Teil dieser Serie erklärt).

p {
  --i: 0;
  /* for --i: 0 (odd), the lightness is 0*100% = 0% (black)
   * for --i: 1 (even), the lightness is 1*100% = 100% (white)* /
  color: hsl(0, 0%, calc(var(--i)*100%));

  &:nth-child(2n) { --i: 1 }
}

Wir möchten auch, dass die ungeraden Absätze rechtsbündig ausgerichtet sind, während die geraden linksbündig bleiben. Um dies zu erreichen, führen wir eine Variable --parity ein, die wir im Allgemeinen Fall nicht explizit setzen – nur für gerade Elemente. Was wir im allgemeinen Fall setzen, ist unsere vorherige Variable, --i. Wir setzen sie auf den Wert von --parity mit einem Fallback von 0.

p {
  --i: var(--parity, 0);
  color: hsl(0, 0%, calc(var(--i)*100%));

  &:nth-child(2n) { --parity: 1 }
}

Bisher erreicht dies genau dasselbe wie die vorherige Version unseres Codes. Wenn wir jedoch die Tatsache nutzen, dass wir unterschiedliche Fallback-Werte an verschiedenen Stellen für dieselbe Variable verwenden können, können wir auch text-align auf den Wert von --parity setzen, wobei wir einen Fallback von… right verwenden!

text-align: var(--parity, right)

Im allgemeinen Fall, wo wir --parity nicht explizit setzen; text-align verwendet den Fallback right, der ein gültiger Wert ist, sodass wir eine Rechtsausrichtung haben. Für die geraden Elemente setzen wir jedoch --parity explizit auf 1, was kein gültiger Wert für text-align ist. Das bedeutet, dass text-align zu seinem anfänglichen Wert left zurückkehrt.

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

Jetzt haben wir eine Rechtsausrichtung für die ungeraden Elemente und eine Linksausrichtung für die geraden Elemente, während wir immer noch eine einzige CSS-Variable verwenden!

Zerlegung eines komplexeren Beispiels

Nehmen wir an, wir wollen das folgende Ergebnis erzielen.

Screenshot. Shows a bunch of numbered cards. Odd ones have the numbering on the left, while even ones have it on the right. Odd ones are right-aligned, while even ones are left-aligned. Odd ones are shifted a bit to the right and have a bit of a clockwise rotation, while even ones are shifted and rotated by the same amounts, but in the opposite directions. All have a grey to orange gradient background, but for the odd ones, this gradient goes from left to right, while for the even ones it goes from right to left.
Nummerierte Karten, bei denen gerade Karten symmetrische Stile zu ungeraden Karten aufweisen.

Wir erstellen diese Karten mit einem <p>-Element für jede. Wir schalten deren box-sizing auf border-box, geben ihnen dann eine width, eine max-width, ein padding und einen margin. Wir ändern auch die Standard-font.

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

Wir haben auch einen Dummy-Umriss hinzugefügt, nur um die Grenzen dieser Elemente zu sehen.

Als Nächstes fügen wir die Nummerierung mit CSS-Zählern und einem :before-Pseudoelement hinzu.

p {
  /* same code as before */
  counter-increment: c;
  
  &:before { content: counter(c, decimal-leading-zero) }
}

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

Nun geben wir unseren Absätzen ein flex-Layout und vergrößern die Nummerierung.

p {
  /* same code as before */
  display: flex;
  align-items: center;
  
  &:before {
    font-size: 2em;
    content: counter(c, decimal-leading-zero);
  }
}

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

Nun kommt der interessante Teil!

Wir setzen einen Schalter --i, der seinen Wert mit der Parität ändert – er ist 0 für die ungeraden Elemente und 1 für die geraden.

p {
  /* same code as before */
  --i: 0;
  
  &:nth-child(2n) { --i: 1 }
}

Als Nächstes wollen wir, dass die Nummerierung bei den ungeraden Elementen links und bei den geraden Elementen rechts ist. Wir erreichen dies über die order-Eigenschaft. Der anfängliche Wert für diese Eigenschaft ist 0, sowohl für das :before-Pseudoelement als auch für den Textinhalt des Absatzes. Wenn wir diese order-Eigenschaft für die Nummerierung (das :before-Pseudoelement) der geraden Elemente auf 1 setzen, verschiebt dies die Nummerierung hinter den Inhalt.

p {
  /* same code as before */
  --i: 0;
  
  &:before {
    /* same code as before */
    /* we don't really need to set order explicitly as 0 is the initial value */
    order: 0;
  }
  
  &:nth-child(2n) {
    --i: 1;
    
    &:before { order: 1 }
  }
}

Sie werden vielleicht feststellen, dass in diesem Fall der order-Wert derselbe ist wie der Wert des Schalters --i. Um die Dinge zu vereinfachen, setzen wir die order auf den Schalterwert.

p {
  /* same code as before */
  --i: 0;
  
  &:before {
    /* same code as before */
    order: var(--i)
  }
  
  &:nth-child(2n) { --i: 1 }
}

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

Nun wollen wir etwas Abstand (sagen wir $gap) zwischen den Zahlen und dem Absatztext. Dies kann durch einen seitlichen margin auf dem :before erreicht werden.

Bei den ungeraden Elementen befinden sich die Elementnummern auf der *linken* Seite, daher benötigen wir einen margin-right ungleich Null. Bei den geraden Elementen befinden sich die Elementnummern auf der *rechten* Seite, daher benötigen wir einen margin-left ungleich Null.

Wenn der Paritätsschalterwert für die ungeraden Elemente 0 ist, ist der linke Rand 0 = 0*$gap, während der rechte Rand $gap = 1*$gap = (1 - 0)*$gap ist.

Ähnlich verhält es sich bei den geraden Elementen, wenn der Paritätsschalterwert 1 ist, ist der linke Rand $gap = 1*$gap, während der rechte Rand 0 = 0*$gap = (1 - 1)*$gap ist.

Das Ergebnis ist in beiden Fällen, dass margin-left der Wert des Paritätsschalters mal dem Randwert ($gap) entspricht, während margin-right 1 minus dem Wert des Paritätsschalters ist, alles multipliziert mit dem Randwert.

$gap: .75em;

p {
  /* same code as before */
  --i: 0;
  
  &:before {
    /* same code as before */
    margin: 
      0                            /* top */
      calc((1 - var(--i))*#{$gap}) /* right */
      0                            /* bottom */
      calc(var(--i)*#{$gap})       /* left */;
  }
  
  &:nth-child(2n) { --i: 1 }
}

Wenn wir den komplementären Wert (1 - var(--i)) an mehr als einer Stelle verwenden, ist es wahrscheinlich am besten, ihn einer anderen CSS-Variable --j zuzuweisen.

$gap: .75em;

p {
  /* same code as before */
  --i: 0;
  --j: calc(1 - var(--i));
  
  &:before {
    /* same code as before */
    margin: 
      0                      /* top */
      calc(var(--j)*#{$gap}) /* right */
      0                      /* bottom */
      calc(var(--i)*#{$gap}) /* left */;
  }
  
  &:nth-child(2n) { --i: 1 }
}

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

Als Nächstes wollen wir diesen Elementen einen richtigen background geben. Dies ist ein Farbverlauf von Grau zu Orange, der von links nach rechts (oder entlang eines 90deg-Winkels) im Falle von ungeraden Elementen (Paritätsschalter --i: 0) und von rechts nach links (bei einem -90deg-Winkel) im Falle von geraden Elementen (Paritätsschalter --i: 1) verläuft.

Das bedeutet, der Absolutwert des Winkel des Farbverlaufs ist derselbe (90deg), nur das Vorzeichen ist anders – es ist +1 für die ungeraden Elemente (--i: 0) und -1 für die geraden Elemente (--i: 1).

Um das Vorzeichen zu wechseln, verwenden wir den Ansatz, den wir im ersten Teil behandelt haben.

/*
 * for --i: 0, we have 1 - 2*0 = 1 - 0 = +1
 * for --i: 1, we have 1 - 2*1 = 1 - 2 = -1
 */
--s: calc(1 - 2*var(--i))

Auf diese Weise wird unser Code zu.

p {
  /* same code as before */
  --i: 0;
  --s: calc(1 - 2*var(--i));
  background: linear-gradient(calc(var(--s)*90deg), #ccc, #f90);
  
  &:nth-child(2n) { --i: 1 }
}

Wir können auch den Dummy-Umriss entfernen, da wir ihn zu diesem Zeitpunkt nicht mehr benötigen.

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

Als Nächstes machen wir etwas Ähnliches für die transform-Eigenschaft.

Die ungeraden Elemente werden ein wenig nach rechts verschoben (in positiver Richtung der x-Achse) und ein wenig im Uhrzeigersinn (positiv) gedreht, während die geraden Elemente ein wenig nach links (in negativer Richtung der x-Achse) verschoben und ein wenig in die andere (negative) Richtung gedreht werden.

Die Beträge der Verschiebung und Drehung sind gleich; nur die Vorzeichen unterscheiden sich.

Für die ungeraden Elemente ist die transform-Kette.

translate(10%) rotate(5deg)

Während wir für die geraden Elemente haben.

translate(-10%) rotate(-5deg)

Mit unserer Vorzeichen-Variable --s ist der vereinheitlichte Code.

p {
  /* same code as before */
  --i: 0;
  --s: calc(1 - 2*var(--i));
  transform: translate(calc(var(--s)*10%)) 
             rotate(calc(var(--s)*5deg));
  
  &:nth-child(2n) { --i: 1 }
}

Das fängt jetzt an, nach etwas auszusehen!

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

Der nächste Schritt ist das Abrunden der Kartenecken. Bei den ungeraden Karten wollen wir, dass die linken Ecken mit einem Radius von der halben Höhe abgerundet werden. Bei den geraden Elementen wollen wir, dass die rechten Ecken mit demselben Radius abgerundet werden.

Da wir die Höhen unserer Karten nicht kennen, verwenden wir einfach einen lächerlich großen Wert, sagen wir 50vh, der aufgrund der Funktionsweise von border-radius herunterskaliert wird. In unserem Fall bedeutet dies eine Herunterskalierung auf den kleineren Wert zwischen der halben Höhe des Elements (da vertikal sowohl eine obere als auch eine untere abgerundete Ecke auf derselben Seite vorhanden sind) und der vollen Breite des Elements (da horizontal eine abgerundete Ecke vorhanden ist; entweder links oder rechts, aber nicht sowohl rechts als auch links).

Das bedeutet, wir wollen, dass die linken Ecken diesen Radius ($r: 50vh) für ungerade Elemente (--i: 0) haben und die rechten Ecken denselben Radius für gerade Elemente (--i: 1). Infolgedessen machen wir etwas sehr Ähnliches wie beim Fall der Nummerierungs-margin.

$r: 50vh;

p {
  /* same code as before */
  --i: 0;
  --j: calc(1 - var(--i));
  --r0: calc(var(--j)*#{$r});
  --r1: calc(var(--i)*#{$r});
  /* clockwise from the top left */
  border-radius: var(--r0) /* top left */
                 var(--r1) /* top right */
                 var(--r1) /* bottom right */
                 var(--r0) /* bottom left */;
  
  &:nth-child(2n) { --i: 1 }
}

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

Nun kommt der wirklich interessante Teil – die Textausrichtung! Wir wollen, dass der Text in den ungeraden Elementen rechts ausgerichtet ist, während der Text in den geraden Elementen links ausgerichtet ist. Das einzige Problem ist, dass text-align keinen numerischen Wert akzeptiert, sodass keine Additions- oder Multiplikationstricks uns hier helfen können.

Was helfen kann, ist die Kombination der Verwendung von Fallback- und ungültigen Werten für CSS-Variablen. Um dies zu tun, führen wir eine weitere Paritätsvariable --p ein, und es ist diese Variable, die wir tatsächlich für gerade Elemente auf 1 setzen. Im Gegensatz zu --i zuvor setzen wir --p *nie explizit für den allgemeinen Fall*, da wir für verschiedene Eigenschaften unterschiedliche Fallback-Werte für diese Variable verwenden möchten.

Wie bei --i setzen wir es auf --p mit einem Fallback-Wert von 0. Dieser Fallback-Wert von 0 ist der Wert, der im allgemeinen Fall tatsächlich verwendet wird, da wir --p dort nie explizit setzen. Für den geraden Fall, wo wir --p explizit auf 1 setzen, wird --i ebenfalls 1.

Gleichzeitig setzen wir die Eigenschaft text-align auf --p mit einem Fallback-Wert von right im allgemeinen Fall. Im geraden Fall, wo --p explizit auf 1 gesetzt ist, wird der Wert von text-align ungültig (weil wir text-align auf den Wert von --p gesetzt haben und --p jetzt 1 ist, was kein gültiger Wert für text-align ist), sodass der Text wieder nach links ausgerichtet wird.

p {
  /* same code as before */
  --i: var(--p, 0);
  text-align: var(--p, right);
  
  &:nth-child(2n) { --p: 1 }
}

Dies ergibt das gewünschte Ergebnis.

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

Umgang mit Responsivität

Während unser Kartenbeispiel auf breiteren Bildschirmen großartig aussieht, kann dasselbe nicht gesagt werden, wenn wir die Dinge verkleinern.

Screenshot collage. Since the width of the cards depends on the viewport width, the viewport may get too narrow to allow for displaying the numbering and the paragraph text side by side and the right one of the two overflows in this case.
Das Ergebnis für breite Bildschirme (links) vs. das Ergebnis für schmale Bildschirme (rechts).

Um dies zu beheben, führen wir zwei weitere benutzerdefinierte Eigenschaften ein, --wide und --k, um zwischen den breiten und schmalen Fällen zu wechseln. Wir setzen --k auf --wide mit einem Fallback-Wert von 0 im allgemeinen Fall und setzen dann --wide auf 1, wenn die Viewport-Breite 340px und mehr beträgt.

p {
  /* same code as before */
  --k: var(--wide, 0);
  
  @media (min-width: 340px) { --wide: 1 }
}

Da wir nur wollen, dass unsere Elemente transformiert werden und abgerundete Ecken im breiten Fall haben, multiplizieren wir die Verschiebungs-, Drehungs- und Radiuswerte mit --k (der 0 ist, es sei denn, der Viewport ist breit, was seinen Wert auf 1 umschaltet).

p {
  /* same code as before */
  --k: var(--wide, 0);
  --r0: calc(var(--k)*var(--j)*#{$r});
  --r1: calc(var(--k)*var(--i)*#{$r});
  border-radius: var(--r0) /* top left */
                 var(--r1) /* top right */
                 var(--r1) /* bottom right */
                 var(--r0) /* bottom left */;
  transform: translate(calc(var(--k)*var(--s)*10%)) 
             rotate(calc(var(--k)*var(--s)*5deg));

  @media (min-width: 340px) { --wide: 1 }
}

Dies ist etwas besser, aber unser Inhalt überläuft immer noch in schmalen Viewports. Wir können dies beheben, indem wir die Nummerierung (das :before-Pseudoelement) nur im breiten Fall auf der linken oder rechten Seite platzieren und sie dann im schmalen Fall über die Karte verschieben.

Um dies zu tun, multiplizieren wir sowohl den order als auch den seitlichen margin-Wert mit --k (der im breiten Fall 1 und ansonsten 0 ist).

Wir setzen auch flex-direction auf --wide mit einem Fallback-Wert von column.

Das bedeutet, der Wert von flex-direction ist im allgemeinen Fall column (da wir --wide nicht explizit woanders gesetzt haben). Wenn der Viewport jedoch breit ist (min-width: 340px), wird unsere Variable --wide auf 1 gesetzt. Aber 1 ist ein ungültiger Wert für flex-direction, daher kehrt diese Eigenschaft zu ihrem anfänglichen Wert row zurück.

p {
  /* same code as before */
  --k: var(--wide, 0);
  flex-direction: var(--wide, column);
  
  &:before {
    /* same code as before */
    order: calc(var(--k)*var(--i));
    margin: 
      0                               /* top */
      calc(var(--k)*var(--j)*#{$gap}) /* right */
      0                               /* bottom */
      calc(var(--k)*var(--i)*#{$gap}) /* left */;
  }
  
  @media (min-width: 340px) { --wide: 1 }
}

Gekoppelt mit der Festlegung einer min-width von 160px für den body haben wir nun das Überlaufproblem behoben.

Responsives Kartenlayout ohne Überlauf (live Demo: live demo).

Eine weitere Sache, die wir tun können, ist, die font-size anzupassen, damit sie auch von --k abhängt.

p {
  /* same code as before */
  --k: var(--wide, 0);
  font: 900 calc(var(--k)*.5em + .75em) cursive;

  @media (min-width: 340px) { --wide: 1 }
}

Und das war's, unser Demo ist jetzt schön responsiv!

Responsives Kartenlayout, Schriftgröße kleiner für schmale Bildschirme und ohne Überlauf (live Demo: live demo).

Ein paar weitere schnelle Beispiele!

Lassen Sie uns ein paar weitere Demos betrachten, die die gleiche Technik verwenden, aber schnell, ohne sie von Grund auf neu zu erstellen. Wir werden lediglich die grundlegenden Ideen dahinter durchgehen.

Scheibensegmente

Geschnittene Scheibe (live Demo: live demo).

Genau wie im Kartenbeispiel, das wir zusammen abgeschlossen haben, können wir ein :before-Pseudoelement für die Nummerierung und ein flex-Layout auf den Absätzen verwenden. Der Effekt der geschnittenen Scheibe wird mit clip-path erzielt.

Die Absatz-Elemente selbst – die horizontalen Versätze, die Position und Intensität des radial-gradient(), der den Schatteneffekt erzeugt, die Richtung des linear-gradient() und die Sättigung seiner Stopps, die color und die Textausrichtung – hängen alle von der Variable --parity ab.

p {
  /* other styles not relevant here */
  --p: var(--parity, 1);
  --q: calc(1 - var(--p));
  --s: calc(1 - 2*var(--p)); /* sign depending on parity */
  transform: translate((calc(var(--i)*var(--s)*#{-$x})));
  background: 
    radial-gradient(at calc(var(--q)*100%) 0, 
      rgba(0, 0, 0, calc(.5 + var(--p)*.5)), transparent 63%) 
      calc(var(--q)*100%) 0/ 65% 65% no-repeat, 
    linear-gradient(calc(var(--s)*-90deg), 
      hsl(23, calc(var(--q)*98%), calc(27% + var(--q)*20%)), 
      hsl(44, calc(var(--q)*92%), 52%));
  color: HSL(0, 0%, calc(var(--p)*100%));
  text-align: var(--parity, right);
	
  &:nth-child(odd) { --parity: 0 }
}

Für die Nummerierung (die :before-Pseudoelemente der Absätze) hängen sowohl der margin als auch die order von --parity auf genau dieselbe Weise ab wie im Kartenbeispiel.

Wenn die Viewport-width kleiner ist als der Scheibendurchmesser $d plus das Doppelte des absoluten Wertes des horizontalen Slice-Offsets $x, dann sind wir nicht mehr im --wide-Fall. Dies beeinflusst die width, padding und margin unserer Absätze sowie ihren horizontalen Versatz und ihre Form (da wir sie nicht beschneiden, um den Effekt der geschnittenen Scheibe zu erzielen).

body {
  /* other styles not relevant here */
  --i: var(--wide, 1);
  --j: calc(1 - var(--i));
	
  @media (max-width: $d + 2*$x) { --wide: 0 }
}

p {
  /* other styles not relevant here */
  margin: calc(var(--j)*.25em) 0;
  padding: 
    calc(var(--i)*#{.5*$r}/var(--n) + var(--j)*5vw) /* vertical */
    calc(var(--i)*#{.5*$r} + var(--j)*2vw) /* horizontal */;
  width: calc(var(--i)*#{$d} /* wide */ + 
              var(--j)*100% /* not wide */);
  transform: translate((calc(var(--i)*var(--s)*#{-$x})));
  clip-path: 
    var(--wide, 
        
      /* fallback, used in the wide case only */
      circle($r at 50% calc((.5*var(--n) - var(--idx))*#{$d}/var(--n))));
}

Wir befinden uns im schmalen Fall unter 270px und haben eine flex-direction von column auf unseren Absätzen. Wir setzen auch die seitlichen Ränder und die order für die Nummerierung auf Null.

body {
  /* other styles not relevant here */
  --k: calc(1 - var(--narr, 1));
	
  @media (min-width: 270px) { --narr: 0 }
}

p {
  /* other styles not relevant here */
  flex-direction: var(--narr, column);

  &:before {
    /* other styles not relevant here */
    margin: 
      0                             /* top */
      calc(var(--k)*var(--q)*.25em) /* right */
      0                             /* bottom */
      calc(var(--k)*var(--p)*.25em) /* left */;
    order: calc(var(--k)*var(--p));
  }
}

Vierstufige Infografik

Screenshot collage. On the left, there's the wide screen scenario. In the middle, there's the normal screen scenario. On the right, there's the narrow screen scenario.
Eine vierstufige Infografik (live Demo: live demo).

Dies funktioniert so ziemlich gleich wie die beiden vorherigen Beispiele. Wir haben ein flex-Layout auf unseren Absätzen, das im schmalen Fall eine column-Richtung verwendet. Wir haben auch eine kleinere font-size in demselben Fall.

body {
  /* other styles not relevant here */
  --k: var(--narr, 1);
  
  @media (min-width: 400px) { --narr: 0 }
}

p {
  /* other styles not relevant here */
  flex-direction: var(--narr, column);
  font-size: calc((1.25 - .375*var(--k))*1em);
}

Die Parität bestimmt die Textausrichtung jedes Absatzes, welcher seitliche border einen Wert ungleich Null erhält und die Position und Richtung des border-Farbverlaufs. Sowohl die Parität als auch die Frage, ob wir uns im breiten Bildschirmfall befinden oder nicht, bestimmen die seitlichen Ränder und Abstände.

body {
  /* other styles not relevant here */
  --i: var(--wide, 1);
  --j: calc(1 - var(--i));
  
  @media (max-width: $bar-w + .5*$bar-h) { --wide: 0 }
}

p {
  /* other styles not relevant here */
  margin: 
    .5em                                 /* top */
    calc(var(--i)*var(--p)*#{.5*$bar-h}) /* right */
    0                                    /* bottom */
    calc(var(--i)*var(--q)*#{.5*$bar-h}) /* left */;
  border-width: 
    0                        /* top */
    calc(var(--q)*#{$bar-b}) /* right */
    0                        /* bottom */
    calc(var(--p)*#{$bar-b}) /* left */;
  padding: 
    $bar-p                                         /* top */
    calc((var(--j) + var(--i)*var(--q))*#{$bar-p}) /* right */
    $bar-p                                         /* bottom */
    calc((var(--j) + var(--i)*var(--p))*#{$bar-p}) /* left */;
  background: 
    linear-gradient(#fcfcfc, gainsboro) padding-box, 
    linear-gradient(calc(var(--s)*90deg), var(--c0), var(--c1)) 
      calc(var(--q)*100%) /* background-position */ / 
      #{$bar-b} 100% /* background-size */;
  text-align: var(--parity, right);
}

Das Symbol wird mit dem :before-Pseudoelement erstellt, und seine order hängt von der Parität ab, aber nur, wenn wir uns nicht im schmalen Bildschirmszenario befinden – in diesem Fall steht es immer vor dem eigentlichen Textinhalt des Absatzes. Sein seitlicher margin hängt sowohl von der Parität als auch davon ab, ob wir uns im breiten Bildschirmszenario befinden oder nicht. Die Komponente mit großem Wert, die es halb außerhalb seines Elternabsatzes positioniert, ist nur im breiten Bildschirmszenario vorhanden. Die font-size hängt auch davon ab, ob wir uns im schmalen Bildschirmszenario befinden oder nicht (und dies beeinflusst seine em-Dimensionen und Abstände).

order: calc((1 - var(--k))*var(--p));
margin: 
  0                                                          /* top */
  calc(var(--i)*var(--p)*#{-.5*$ico-d} + var(--q)*#{$bar-p}) /* right */
  0                                                          /* bottom */
  calc(var(--i)*var(--q)*#{-.5*$ico-d} + var(--p)*#{$bar-p}) /* left */;
font-size: calc(#{$ico-s}/(1 + var(--k)));

Der Ring wird mit einem absolut positionierten :after-Pseudoelement erstellt (und seine Platzierung hängt von der Parität ab), aber nur für den breiten Bildschirmszenario.

content: var(--wide, '');

Der zweidimensionale Fall

Screenshot collage. On the left, we have the wide screen scenario. Each article is laid out as a 2x2 grid, with the numbering occupying an entire column, either on the right for odd items or on the left for even items. The heading and the actual text occupy the other column. In the middle, we have the normal screen case. Here, we also have a 2x2 grid, but the numbering occupies only the top row on the same column as before, while the actual text content now spans both columns on the second row. On the right, we have the narrow screen case. In this case, we don't have a grid anymore, the numbering, the heading and the actual text are one under the other for each article.
Screenshot-Collage (live Demo: live demo, kein Edge-Support aufgrund von CSS-Variablen- und calc()-Bugs).

Hier haben wir eine Reihe von article-Elementen, jedes mit einer Überschrift. Schauen wir uns die interessantesten Aspekte an, wie dieses responsive Layout funktioniert!

Auf jedem Artikel haben wir ein zweidimensionales Layout (grid) – aber *nur*, wenn wir uns nicht im schmalen Szenario befinden (--narr: 1), in welchem Fall wir auf den normalen Dokumentenfluss zurückgreifen, mit der Nummerierung, die mit einem :before-Pseudoelement erstellt wird, gefolgt von der Überschrift, gefolgt vom eigentlichen Text. In dieser Situation fügen wir auch vertikale Abstände zur Überschrift hinzu, da wir die Gitterabstände nicht mehr haben und nicht wollen, dass die Dinge zu überladen werden.

html {
  --k: var(--narr, 0);
	
  @media (max-width: 250px) { --narr: 1 }
}

article {
  /* other styles irrelevant here */
  display: var(--narr, grid);
}

h3 {
  /* other styles irrelevant here */
  padding: calc(var(--k)*#{$hd3-p-narr}) 0;
}

Für das grid erstellen wir zwei Spalten mit Breiten, die sowohl von der Parität als auch davon abhängen, ob wir uns im breiten Szenario befinden. Wir lassen die Nummerierung (das :before-Pseudoelement) im breiten Szenario zwei Zeilen überspannen, entweder in der zweiten oder ersten Spalte, abhängig von der Parität. Wenn wir uns nicht im breiten Szenario befinden, überspannt der Absatz beide Spalten in der zweiten Zeile.

Wir setzen grid-auto-flow im breiten Szenario auf column dense und lassen es andernfalls auf den anfänglichen Wert row zurückfallen. Da unsere article-Elemente breiter sind als die kombinierten Breiten der Spalten und des Spaltenabstands dazwischen, verwenden wir place-content, um die tatsächlichen Gitterspalten entweder am rechten oder linken Ende zu positionieren, abhängig von der Parität.

Schließlich platzieren wir die Überschrift am Ende oder Anfang der Spalte, abhängig von der Parität, und ebenso die Textausrichtung des Absatzes, wenn wir uns im breiten Szenario befinden.

$col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide});
$col-2-wide: calc(var(--p)*#{$col-a-wide} + var(--q)*#{$col-b-wide});

$col-1-norm: calc(var(--q)*#{$col-a-norm} + var(--p)*#{$col-b-norm});
$col-2-norm: calc(var(--p)*#{$col-a-norm} + var(--q)*#{$col-b-norm});

$col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm});
$col-2: calc(var(--i)*#{$col-2-wide} + var(--j)*#{$col-2-norm});

html {
  --i: var(--wide, 1);
  --j: calc(1 - var(--i));
	
  @media (max-width: $art-w-wide) { --wide: 0 }
}

article {
  /* other styles irrelevant here */
  --p: var(--parity, 1);
  --q: calc(1 - var(--p));
  grid-template-columns: #{$col-1} #{$col-2};
  grid-auto-flow: var(--wide, dense column);
  place-content: var(--parity, center end);
  
  &:before {
    /* other styles irrelevant here */
    grid-row: 1/ span calc(1 + var(--i));
    grid-column: calc(1 + var(--p))/ span 1;
  }
  
  &:nth-child(odd) { --parity: 0 }
}

h3 {
  /* other styles irrelevant here */
  justify-self: var(--parity, self-end);
}

p {
  grid-column-end: span calc(1 + var(--j));
  text-align: var(--wide, var(--parity, right));
}

Wir haben auch numerische Werte wie Gitterabstände, Ränder, Abstände, Schriftgrößen, Farbverlaufsrichtungen, Rotations- und Verschiebungsrichtungen, die von der Parität und/oder davon abhängen, ob wir uns im breiten Szenario befinden oder nicht.

Noch mehr Beispiele!

Wenn Sie mehr davon möchten, habe ich eine ganze Sammlung ähnlicher responsiver Demos für Sie erstellt!

Screenshot of collection page on CodePen, showing the six most recent demos added.
Sammlung responsiver Demos.