Zum millionsten Mal: Wir brauchen Container-Abfragen in CSS! Und raten Sie mal, es sieht so aus, als ob wir in diese Richtung gehen.
Beim Erstellen von Komponenten für eine Website weiß man nicht immer, wie diese Komponente verwendet wird. Vielleicht wird sie so breit dargestellt wie das Browserfenster. Vielleicht sitzen zwei davon nebeneinander. Vielleicht befindet sie sich in einer schmalen Spalte. Die Breite davon korreliert nicht immer mit der Breite des Browserfensters.
Es ist üblich, an einen Punkt zu gelangen, an dem containerbasierte Abfragen für das CSS der Komponente sehr nützlich wären. Wenn Sie im Web nach Lösungen dafür suchen, werden Sie wahrscheinlich mehrere JavaScript-basierte Lösungen finden. Aber diese haben ihren Preis: zusätzliche Abhängigkeiten, Styling, das JavaScript erfordert, und vermischte Anwendungslogik und Designlogik.
Ich bin ein starker Verfechter der Trennung von Belangen, und Layout ist eine CSS-Angelegenheit. Zum Beispiel, so schön die API von IntersectionObserver auch ist, ich möchte Dinge wie :in-viewport in CSS haben! Also habe ich weiter nach einer reinen CSS-Lösung gesucht und bin auf Heydon Pickerings The Flexbox Holy Albatross gestoßen. Es ist eine gute Lösung für Spalten, aber ich wollte mehr. Es gibt einige Verfeinerungen des ursprünglichen Albatros (wie The Unholy Albatross), aber immer noch sind sie ein wenig hackelig und alles, was passiert, ist ein Wechsel von Zeilen zu Spalten.
Ich will immer noch mehr! Ich möchte näher an echte Container-Abfragen herankommen! Was hat CSS zu bieten, das ich nutzen könnte? Ich habe einen mathematischen Hintergrund, daher sind Funktionen wie calc(), min(), max() und clamp() Dinge, die ich mag und verstehe.
Nächster Schritt: eine Container-Query-ähnliche Lösung damit erstellen.
Inhaltsverzeichnis
- Warum „Rabe“?
- Mathematische Funktionen in CSS
- Schritt 1: Konfigurationsvariablen erstellen
- Schritt 2: Indikatorvariablen erstellen
- Schritt 3: Indikatorvariablen verwenden, um Intervallwerte auszuwählen
- Schritt 4:
min()und eine absurd große Ganzzahl verwenden, um Werte beliebiger Länge auszuwählen - Schritt 5: Alles zusammenfügen
- Noch etwas?
- Was ist mit Höhen?
- Was ist mit dem Anzeigen und Verbergen von Dingen?
- Fazit
- Boni
- Abschließende Gedanken
Möchten Sie sehen, was möglich ist, bevor Sie weiterlesen? Hier ist eine CodePen-Sammlung, die zeigt, was mit den in diesem Artikel besprochenen Ideen möglich ist.
Warum „Rabe“?
Diese Arbeit ist von Heydons Albatros inspiriert, aber die Technik kann mehr Tricks, also habe ich einen Raben gewählt, da Raben sehr kluge Vögel sind.
Rückblick: Mathematische Funktionen in CSS
Die Funktion calc() ermöglicht mathematische Operationen in CSS. Als Bonus kann man Einheiten kombinieren, so dass Dinge wie calc(100vw - 300px) möglich sind.
Die Funktionen min() und max() nehmen zwei oder mehr Argumente und geben das kleinste bzw. größte Argument zurück.
Die Funktion clamp() ist auf sehr nützliche Weise wie eine Kombination aus min() und max(). Die Funktion clamp(a, x, b) gibt zurück
- a, wenn x kleiner als a ist
- b, wenn x größer als b ist, und
- x, wenn x zwischen a und b liegt
Es ist also ein bisschen wie clamp(kleinste, relativ, größte). Man kann es als Kurzform für min(max(a,x),b) betrachten. Hier gibt es mehr Informationen dazu, wenn Sie mehr lesen möchten.
Wir werden in diesem Artikel auch ein weiteres CSS-Werkzeug stark nutzen: CSS-Benutzerdefinierte Eigenschaften. Das sind Dinge wie --color: red; oder --distance: 20px. Im Grunde Variablen. Wir werden sie verwenden, um das CSS sauberer zu halten, z. B. um nicht zu viel zu wiederholen.
Beginnen wir mit dieser Raven-Technik.
Schritt 1: Konfigurationsvariablen erstellen
Erstellen wir einige benutzerdefinierte CSS-Eigenschaften, um die Dinge einzurichten.
Was ist die Basisgröße, auf der unsere Abfragen basieren sollen? Da wir auf Container-Query-Verhalten abzielen, wäre dies 100% — die Verwendung von 100vw würde dies wie eine Media Query wirken lassen, da dies die Breite des Browserfensters ist, nicht des Containers!
--base_size: 100%;
Jetzt denken wir über die Breakpoints nach. Buchstäblich Container-Breiten, bei denen wir einen Bruch wünschen, um neue Stile anzuwenden.
--breakpoint_wide: 1500px;
/* Wider than 1500px will be considered wide */
--breakpoint_medium: 800px;
/* From 801px to 1500px will be considered medium */
/* Smaller than or exact 800px will be small */
Im laufenden Beispiel verwenden wir drei Intervalle, aber es gibt keine Grenze für diese Technik.
Definieren wir nun einige (CSS-Längen-)Werte, die wir für die durch die Breakpoints definierten Intervalle zurückgeben möchten. Dies sind wörtliche Werte
--length_4_small: calc((100% / 1) - 10px); /* Change to your needs */
--length_4_medium: calc((100% / 2) - 10px); /* Change to your needs */
--length_4_wide: calc((100% / 3) - 10px); /* Change to your needs */
Das ist die Konfiguration. Nutzen wir sie!
Schritt 2: Indikatorvariablen erstellen
Wir erstellen einige Indikatorvariablen für die Intervalle. Sie verhalten sich ein wenig wie boolesche Werte, aber mit einer Längeneinheit (0px und 1px). Wenn wir diese Längen als Minimum und Maximum begrenzen, dienen sie als eine Art „wahr“ und „falsch“-Indikator.
Wenn also --base_size größer als --breakpoint_wide ist, wollen wir eine Variable, die 1px ist. Andernfalls wollen wir 0px. Dies kann mit clamp() erreicht werden
--is_wide: clamp(0px,
var(--base_size) - var(--breakpoint_wide),
1px
);
Wenn var(--base_size) - var(--breakpoint_wide) negativ ist, dann ist --base_size kleiner als --breakpoint_wide, sodass clamp() in diesem Fall 0px zurückgibt.
Umgekehrt, wenn --base_size größer als --breakpoint_wide ist, ergibt die Berechnung eine positive Länge, die größer oder gleich 1px ist. Das bedeutet, clamp() gibt 1px zurück.
Bingo! Wir haben eine Indikatorvariable für „breit“.
Machen wir das für das „mittlere“ Intervall
--is_medium: clamp(0px,
var(--base_size) - var(--breakpoint_medium),
1px
); /* DO NOT USE, SEE BELOW! */
Dies ergibt 0px für das kleine Intervall, aber 1px für das mittlere und das breite Intervall. Was wir jedoch wollen, ist 0px für das breite Intervall und 1px ausschließlich für das mittlere Intervall.
Dies können wir lösen, indem wir den Wert --is_wide subtrahieren. Im breiten Intervall ist 1px - 1px gleich 0px; im mittleren Intervall ist 1px - 0px gleich 1px; und für das kleine Intervall ergibt 0px - 0px gleich 0px. Perfekt.
Also erhalten wir
--is_medium: calc(
clamp(0px,
var(--base_size) - var(--breakpoint_medium),
1px)
- var(--is_wide)
);
Sehen Sie die Idee? Um eine Indikatorvariable zu berechnen, verwenden Sie clamp() mit 0px und 1px als Grenzen und der Differenz von --base_width und --breakpoint_whatever als geklemmten Wert. Subtrahieren Sie dann die Summe aller Indikatoren für größere Intervalle. Diese Logik ergibt Folgendes für den kleinsten Intervallindikator
--is_small: calc(
clamp(0px,
(var(--base_size) - 0px,
1px)
- (var(--is_medium) + var(--is_wide))
);
Hier können wir den Clamp überspringen, da der Breakpoint für klein 0px ist und --base_size positiv ist, sodass --base_size - 0px immer größer als 1px ist und clamp() immer 1px zurückgibt. Daher kann die Berechnung von --is_small vereinfacht werden zu
--is_small: calc(1px - (var(--is_medium) + var(--is_wide)));
Schritt 3: Indikatorvariablen verwenden, um Intervallwerte auszuwählen
Jetzt müssen wir von diesen „Indikatorvariablen“ zu etwas Nützlichem gelangen. Nehmen wir an, wir arbeiten mit einem pixelbasierten Layout. Keine Panik, wir werden später andere Einheiten behandeln.
Hier ist eine Frage. Was gibt das zurück?
calc(var(--is_small) * 100);
Wenn --is_small 1px ist, gibt es 100px zurück, und wenn --is_small 0px ist, gibt es 0px zurück.
Wie ist das nützlich? Sehen Sie das
calc(
(var(--is_small) * 100)
+
(var(--is_medium) * 200)
);
Dies gibt 100px + 0px = 100px im kleinen Intervall zurück (wo --is_small 1px und --is_medium 0px ist). Im mittleren Intervall (wo --is_medium 1px und --is_small 0px ist) gibt es 0px + 200px = 200px zurück.
Verstehen Sie die Idee? Sehen Sie sich Roman Komarovs Artikel an, um tiefer einzudringen, was hier vor sich geht, denn es kann komplex sein, es zu erfassen.
Sie multiplizieren einen Pixelwert (ohne Einheit) mit der entsprechenden Indikatorvariable und summieren alle diese Terme. Also, für ein pixelbasiertes Layout ist etwas wie dieses ausreichend
width: calc(
(var(--is_small) * 100)
+ (var(--is_medium) * 200)
+ (var(--is_wide) * 500)
);
Aber meistens wollen wir keine pixelbasierten Werte. Wir wollen Konzepte, wie „volle Breite“ oder „Drittelbreite“ oder vielleicht sogar andere Einheiten, wie 2rem, 65ch und ähnliches. Wir müssen hier weitergehen für diese.
Schritt 4: min() und eine absurd große Ganzzahl verwenden, um Werte beliebiger Länge auszuwählen
Im ersten Schritt haben wir etwas wie das hier anstelle eines statischen Pixelwerts definiert
--length_4_medium: calc((100% / 2) - 10px);
Wie können wir sie dann verwenden? Die Funktion min() zur Rettung!
Definieren wir eine Hilfsvariable
--very_big_int: 9999;
/* Pure, unitless number. Must be bigger than any length appearing elsewhere. */
Wenn Sie diesen Wert mit einer Indikatorvariable multiplizieren, erhalten Sie entweder 0px oder 9999px. Wie groß dieser Wert sein sollte, hängt von Ihrem Browser ab. Chrome nimmt 999999, aber Firefox akzeptiert keine so hohe Zahl, daher ist 9999 ein Wert, der in beiden funktioniert. Es gibt nur sehr wenige Viewports, die größer als 9999px sind, daher sollten wir in Ordnung sein.
Was passiert dann, wenn wir diesen Wert mit jedem Wert kleiner als 9999px, aber größer als 0px, mit min() vergleichen?
min(
var(--length_4_small),
var(--is_small) * var(--very_big_int)
);
Wenn und nur wenn --is_small 0px ist, gibt es 0px zurück. Wenn --is_small 1px ist, gibt die Multiplikation 9999px zurück (was größer als --length_4_small ist), und min gibt zurück: --length_4_small.
So können wir jede Länge (d. h. kleiner als 9999px, aber größer als 0px) basierend auf Indikatorvariablen auswählen.
Wenn Sie mit Viewports größer als 9999px arbeiten, müssen Sie die Variable --very_big_int anpassen. Das ist ein bisschen hässlich, aber wir können das beheben, sobald reines CSS die Einheit von einem Wert entfernen kann, um die Einheiten bei unseren Indikatorvariablen zu entfernen (und sie direkt mit jeder Länge multiplizieren). Vorerst funktioniert das.
Wir werden nun alle Teile kombinieren und die Krähe fliegen lassen!
Schritt 5: Alles zusammenfügen
Wir können nun unseren dynamischen, Container-Breiten-basierten, Breakpoint-gesteuerten Wert wie folgt berechnen
--dyn_length: calc(
min(var(--is_wide) * var(--very_big_int), var(--length_4_wide))
+ min(var(--is_medium) * var(--very_big_int), var(--length_4_medium))
+ min(var(--is_small) * var(--very_big_int), var(--length_4_small))
);
Jede Zeile ist ein min() aus Schritt 4. Alle Zeilen werden wie in Schritt 3 addiert, die Indikatorvariablen stammen aus Schritt 2 und alles basiert auf der Konfiguration, die wir in Schritt 1 vorgenommen haben – sie funktionieren alle zusammen in einer großen Formel!
Möchten Sie es ausprobieren? Hier ist ein Pen zum Spielen (siehe die Notizen im CSS).
Dieser Pen verwendet kein Flexbox, kein Grid, keine Floats. Nur ein paar Divs. Dies soll zeigen, dass Helfer bei dieser Art von Layout unnötig sind. Aber fühlen Sie sich frei, die Krähe auch mit diesen Layouts zu verwenden, da sie Ihnen helfen wird, komplexere Layouts zu erstellen.
Sonst noch was?
Bisher haben wir feste Pixelwerte als Breakpoints verwendet, aber vielleicht möchten wir das Layout ändern, wenn der Container größer oder kleiner als die Hälfte des Viewports ist, abzüglich 10px? Kein Problem
--breakpoint_wide: calc(50vw - 10px);
Das funktioniert einfach! Andere Formeln funktionieren ebenfalls. Um seltsames Verhalten zu vermeiden, wollen wir etwas wie dieses verwenden
--breakpoint_medium: min(var(--breakpoint_wide), 500px);
…um einen zweiten Breakpoint bei 500px Breite festzulegen. Die Berechnungen in Schritt 2 hängen davon ab, dass --breakpoint_wide nicht kleiner als --breakpoint_medium ist. Behalten Sie einfach Ihre Breakpoints in der richtigen Reihenfolge: min() und/oder max() sind hier sehr nützlich!
Was ist mit Höhen?
Die Auswertungen aller Berechnungen erfolgen verzögert. Das heißt, wenn --dyn_length einer beliebigen Eigenschaft zugewiesen wird, basiert die Berechnung auf dem, was --base_size an dieser Stelle ergibt. Wenn also eine Höhe eingestellt wird, basieren die Breakpoints auf 100% Höhe, wenn --base_size 100% ist.
Ich habe (noch) keinen Weg gefunden, eine Höhe basierend auf der Breite eines Containers einzustellen. Sie können also padding-top verwenden, da 100% für Padding mit der Breite ausgewertet wird.
Was ist mit dem Anzeigen und Verbergen von Dingen?
Die einfachste Art, Dinge im Raven-Stil anzuzeigen und zu verbergen, ist, die Breite auf 100px (oder eine andere geeignete Breite) bei der entsprechenden Indikatorvariable einzustellen
.show_if_small {
width: calc(var(--is_small) * 100);
}
.show_if_medium {
width: calc(var(--is_medium) * 100);
}
.show_if_wide {
width: calc(var(--is_wide) * 100);
}
Sie müssen
overflow: hidden;
display: inline-block; /* to avoid ugly empty lines */
…oder eine andere Methode, um Dinge innerhalb einer Box von width: 0px zu verbergen. Das vollständige Verbergen der Box erfordert das Einstellen zusätzlicher Box-Modell-Eigenschaften, einschließlich margin, padding und border-width, auf 0px. Die Krähe kann dies für einige Eigenschaften tun, aber es ist genauso effektiv, sie auf 0px festzulegen.
Eine andere Alternative ist die Verwendung von position: absolute; und das Element außerhalb des Bildschirms durch left: calc(var(--is_???) * 9999); zu ziehen.
Fazit
Wir brauchen vielleicht gar kein JavaScript, auch nicht für Container-Query-Verhalten! Sicherlich hoffen wir, dass, wenn wir tatsächlich Container-Abfragen in der CSS-Syntax erhalten, diese einfacher zu verwenden und zu verstehen sein werden – aber es ist auch sehr cool, dass Dinge heute in CSS möglich sind.
Während ich daran gearbeitet habe, habe ich einige Meinungen zu anderen Dingen entwickelt, die CSS gebrauchen könnte
- Containerbasierte Einheiten wie
conWundconH, um Höhen basierend auf der Breite einzustellen. Diese Einheiten könnten auf dem Wurzelelement des aktuellen Stapelkontextes basieren. - Eine Art „zu Wert auswerten“-Funktion, um Probleme mit verzögerter Auswertung zu überwinden. Dies würde gut mit einer „Einheit streifen“-Funktion funktionieren, die zur Renderzeit arbeitet.
Hinweis: In einer früheren Version hatte ich cw und ch für die Einheiten verwendet, aber es wurde mir mitgeteilt, dass diese leicht mit CSS-Einheiten mit demselben Namen verwechselt werden können. Dank Mikko Tapionlinna und Gilson Nunes Filho in den Kommentaren für den Tipp!)
Wenn wir die zweite hätten, würde dies ermöglichen, Farben (auf saubere Weise), Ränder, box-shadow, flex-grow, background-position, z-index, scale() und andere Dinge mit der Krähe einzustellen.
Zusammen mit komponenten-basierten Einheiten wäre es sogar möglich, die Dimensionen von Kindern auf das gleiche Seitenverhältnis wie die des Elternteils einzustellen. Eine Division durch einen Wert mit Einheit ist nicht möglich; andernfalls würde --indicator / 1px als „Einheit streifen“ für die Krähe funktionieren.
Bonus: Boolesche Logik
Indikatorvariablen sehen aus wie boolesche Werte, richtig? Der einzige Unterschied ist, dass sie eine „px“-Einheit haben. Was ist mit der logischen Kombination davon? Stellen Sie sich Dinge wie „Container ist breiter als die halbe Leinwand“ und „Layout ist im Zwei-Spalten-Modus“ vor. CSS-Funktionen zur Rettung!
Für den ODER-Operator können wir max() über alle Indikatoren setzen
--a_OR_b: max( var(--indicator_a) , var(--indicator_b) );
Für den NICHT-Operator können wir den Indikator von 1px subtrahieren
--NOT_a: calc(1px - var(--indicator_a));
Logikpuristen mögen hier aufhören, da NOR(a,b) = NICHT(ODER(a,b)) die vollständige boolesche Algebra ist. Aber, nur zum Spaß, hier sind noch ein paar mehr
UND:
--a_AND_b: min(var(--indicator_a), var(--indicator_b));
Dies ergibt 1px, wenn und nur wenn beide Indikatoren 1px sind.
Beachten Sie, dass min() und max() mehr als zwei Argumente annehmen. Sie funktionieren immer noch als UND und ODER für (mehr als zwei) Indikatorvariablen.
XOR:
--a_XOR_b: max(
var(--indicator_a) - var(--indicator_b),
var(--indicator_b) - var(--indicator_a)
);
Wenn (und nur wenn) beide Indikatoren denselben Wert haben, sind beide Differenzen 0px, und max() gibt dies zurück. Wenn die Indikatoren unterschiedliche Werte haben, gibt ein Term -1px und der andere 1px zurück. max() gibt in diesem Fall 1px zurück.
Wenn jemand an dem Fall interessiert ist, dass zwei Indikatoren gleich sind, verwenden Sie dies
--a_EQ_b: calc(1px -
max(
var(--indicator_a) - var(--indicator_b),
var(--indicator_b) - var(--indicator_a)
)
);
Und ja, das ist NICHT(a XOR b). Ich konnte keine „schönere“ Lösung dafür finden.
Gleichheit könnte für CSS-Längenvariablen im Allgemeinen interessant sein, anstatt nur für Indikatorvariablen verwendet zu werden. Durch erneute Verwendung von clamp() könnte dies helfen
--a_EQUALS_b_general: calc(
1px -
clamp(0px,
max(
var(--var_a) - var(--var_b),
var(--var_b) - var(--var_a)
),
1px)
);
Entfernen Sie die px-Einheiten, um allgemeine Gleichheit für einheitenlose Variablen (Ganzzahlen) zu erhalten.
Ich denke, das reicht für die meisten Layouts an boolescher Logik!
Bonus 2: Festlegen der Anzahl von Spalten in einem Grid-Layout
Da die Krähe auf die Rückgabe von CSS-Längenwerten beschränkt ist, kann sie die Anzahl der Spalten für ein Grid nicht direkt wählen (da dies ein Wert ohne Einheit ist). Aber es gibt eine Möglichkeit, dies zu erreichen (vorausgesetzt, wir haben die Indikatorvariablen wie oben deklariert)
--number_of_cols_4_wide: 4;
--number_of_cols_4_medium: 2;
--number_of_cols_4_small: 1;
--grid_gap: 0px;
--grid_columns_width_4_wide: calc(
(100% - (var(--number_of_cols_4_wide) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_wide));
--grid_columns_width_4_medium: calc(
(100% - (var(--number_of_cols_4_medium) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_medium));
--grid_columns_width_4_small: calc(
(100% - (var(--number_of_cols_4_small) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_small));
--raven_grid_columns_width: calc( /* use the Raven to combine the values */
min(var(--is_wide) * var(--very_big_int),var(--grid_columns_width_4_wide))
+ min(var(--is_medium) * var(--very_big_int),var(--grid_columns_width_4_medium))
+ min(var(--is_small) * var(--very_big_int),var(--grid_columns_width_4_small))
);
Und richten Sie Ihr Grid ein mit
.grid_container{
display: grid;
grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));
gap: var(--grid_gap)
};
Wie funktioniert das?
- Definieren Sie die Anzahl der Spalten, die wir für jedes Intervall wünschen (Zeilen 1, 2, 3)
- Berechnen Sie die perfekte Breite der Spalten für jedes Intervall (Zeilen 5, 6, 7).
Was passiert hier?
Zuerst berechnen wir den verfügbaren Platz für unsere Spalten. Dies sind
100%, abzüglich des Platzes, den die Lücken einnehmen werden. FürnSpalten gibt es(n-1)Lücken. Dieser Platz wird dann durch die gewünschte Anzahl von Spalten geteilt. - Verwenden Sie die Krähe, um die richtige Spaltenbreite für die tatsächliche
--base_sizezu berechnen.
Im Grid-Container wählt diese Zeile
grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));
…dann die Anzahl der Spalten aus, um den von der Krähe bereitgestellten Wert aufzunehmen (was zu unseren --number_of_cols_4_??? Variablen von oben führt).
Die Krähe kann die Anzahl der Spalten möglicherweise nicht direkt angeben, aber sie kann eine Länge liefern, um repeat und autofit die gewünschte Anzahl für uns berechnen zu lassen.
Aber auto-fit mit minmax() macht dasselbe, richtig? Nein! Die obige Lösung gibt niemals drei Spalten (oder fünf) zurück, und die Anzahl der Spalten muss nicht mit der Breite des Containers zunehmen. Versuchen Sie, die folgenden Werte in diesem Pen einzustellen, um zu sehen, wie die Krähe zu voller Blüte kommt
--number_of_cols_4_wide: 1;
--number_of_cols_4_medium: 2;
--number_of_cols_4_small: 4;
Bonus 3: Ändern der background-color mit einem linear-gradient()
Das ist etwas kniffliger. Die Krähe dreht sich alles um Längenwerte, wie können wir also eine Farbe daraus bekommen? Nun, lineare Farbverläufe befassen sich mit beidem. Sie definieren Farben in bestimmten Bereichen, die durch Längenwerte definiert sind. Gehen wir dieses Konzept detaillierter durch, bevor wir zum Code kommen.
Um den eigentlichen Farbverlaufsteil zu umgehen, ist es eine bekannte Technik, einen Farbstop zu verdoppeln und den Farbverlaufsteil effektiv innerhalb von 0px stattfinden zu lassen. Sehen Sie sich diesen Code an, um zu sehen, wie das gemacht wird
background-image:linear-gradient(
to right,
red 0%,
red 50%,
blue 50%,
blue 100%
);
Dies färbt Ihren Hintergrund links rot und rechts blau. Beachten Sie das erste Argument „to right“. Dies impliziert, dass Prozentwerte horizontal von links nach rechts ausgewertet werden.
Die Steuerung der Werte von 50% über Raven-Variablen ermöglicht es, den Farbstop nach Belieben zu verschieben. Und wir können weitere Farbstops hinzufügen. Im laufenden Beispiel benötigen wir drei Farben, was zu zwei (doppelten) inneren Farbstops führt.
Mit Variablen für Farbe und Farbstops erhalten wir dies
background-image: linear-gradient(
to right,
var(--color_small) 0px,
var(--color_small) var(--first_lgbreak_value),
var(--color_medium) var(--first_lgbreak_value),
var(--color_medium) var(--second_lgbreak_value),
var(--color_wide) var(--second_lgbreak_value),
var(--color_wide) 100%
);
Aber wie berechnen wir die Werte für --first_lgbreak_value und --second_lgbreak_value? Sehen wir mal.
Der erste Wert steuert, wo --color_small sichtbar ist. Im kleinen Intervall sollte es 100% sein, und 0px in den anderen Intervallen. Wie das mit der Krähe geht, haben wir gesehen. Die zweite Variable steuert die Sichtbarkeit von --color_medium. Sie sollte 100% für das kleine Intervall, 100% für das mittlere Intervall, aber 0px für das breite Intervall sein. Der entsprechende Indikator muss 1px sein, wenn die Containerbreite im kleinen oder im mittleren Intervall liegt.
Da wir boolesche Logik auf Indikatoren anwenden können, ist es
max(--is_small, --is_medium)
…um den richtigen Indikator zu erhalten. Das ergibt
--first_lgbreak_value: min(var(--is_small) * var(--very_big_int), 100%);
--second_lgbreak_value: min(
max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);
Die Zusammenführung der Teile ergibt diesen CSS-Code, um die background-color basierend auf der Breite zu ändern (die Intervallindikatoren werden wie oben gezeigt berechnet)
--first_lgbreak_value: min(
var(--is_small) * var(--very_big_int), 100%);
--second_lgbreak_value: min(
max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);
--color_wide: red;/* change to your needs*/
--color_medium: green;/* change to your needs*/
--color_small: lightblue;/* change to your needs*/
background-image: linear-gradient(
to right,
var(--color_small) 0px,
var(--color_small) var(--first_lgbreak_value),
var(--color_medium) var(--first_lgbreak_value),
var(--color_medium) var(--second_lgbreak_value),
var(--color_wide) var(--second_lgbreak_value),
var(--color_wide) 100%
);
Hier ist ein Pen, um das in Aktion zu sehen.
Bonus 4: Verschachtelte Variablen entfernen
Bei der Arbeit mit der Krähe stieß ich auf ein seltsames Problem: Es gibt eine Grenze für die Anzahl verschachtelter Variablen, die in calc() verwendet werden können. Dies kann zu Problemen führen, wenn zu viele Breakpoints verwendet werden. Soweit ich weiß, dient diese Grenze dazu, Seitenblockaden während der Berechnung der Stile zu verhindern und schnellere Kreisbezugsüberprüfungen zu ermöglichen.
Meiner Meinung nach wäre etwas wie „zu Wert auswerten“ eine großartige Möglichkeit, dies zu überwinden. Dennoch kann diese Grenze Ihnen Kopfzerbrechen bereiten, wenn Sie die Grenzen von CSS ausreizen. Hoffentlich wird dieses Problem in Zukunft angegangen.
Es gibt eine Möglichkeit, die Indikatorvariablen für die Krähe ohne (tief) verschachtelte Variablen zu berechnen. Sehen wir uns die ursprüngliche Berechnung für den Wert --is_medium an
--is_medium:calc(
clamp(0px,
var(--base_size) - var(--breakpoint_medium),
1px)
- var(--is_wide)
);
Das Problem tritt bei der Subtraktion von --is_wide auf. Dies führt dazu, dass der CSS-Parser die Definition der vollständigen Formel von --is_wide einfügt. Die Berechnung von --is_small enthält noch mehr solcher Verweise. (Die Definition für --is_wide wird sogar zweimal eingefügt, da sie in der Definition von --is_medium versteckt ist und auch direkt verwendet wird.)
Glücklicherweise gibt es eine Möglichkeit, Indikatoren zu berechnen, ohne auf Indikatoren für größere Breakpoints zu verweisen.
Der Indikator ist genau dann wahr, wenn --base_size größer als der untere Breakpoint für das Intervall und kleiner oder gleich dem oberen Breakpoint für das Intervall ist. Diese Definition ergibt den folgenden Code:
--is_medium:
min(
clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px),
clamp(0px, 1px + var(--breakpoint_wide) - var(--base_size), 1px)
);
min()wird als logischer UND-Operator verwendet- Das erste
clamp()bedeutet „--base_sizeist größer als--breakpoint_medium“ - Das zweite
clamp()bedeutet „--base_sizeist kleiner oder gleich--breakpoint_wide.“ - Das Hinzufügen von
1pxwechselt von „kleiner als“ zu „kleiner oder gleich als“. Dies funktioniert, da wir mit ganzen (Pixel-)Zahlen arbeiten (a <= bbedeuteta < (b+1)für ganze Zahlen).
Die vollständige Berechnung der Indikatorvariablen kann auf diese Weise erfolgen:
--is_wide: clamp(0px, var(--base_size) - var(--breakpoint_wide), 1px);
--is_medium: min(clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px),
clamp(0px, 1px + var(--breakpoint_wide) - var(--base_size), 1px)
);
--is_small: clamp(0px,1px + var(--breakpoint_medium) - var(--base_size), 1px);
Die Berechnungen für --is_wide und --is_small sind einfacher, da für jeden nur ein bestimmter Breakpoint überprüft werden muss.
Dies funktioniert mit allem, was wir bisher betrachtet haben. Hier ist ein Pen, der Beispiele kombiniert.
Abschließende Gedanken
Der Raven ist nicht in der Lage, all das zu tun, was eine Media Query kann. Aber das brauchen wir auch nicht, da wir Media Queries in CSS haben. Es ist in Ordnung, sie für die „großen“ Designänderungen zu verwenden, wie die Position einer Seitenleiste oder die Neukonfiguration eines Menüs. Diese Dinge geschehen im Kontext des vollständigen Viewports (der Größe des Browserfensters).
Aber für Komponenten sind Media Queries irgendwie falsch, da wir nie wissen, wie Komponenten dimensioniert werden.
Heydon Pickering demonstrierte dieses Problem mit diesem Bild:

Ich hoffe, dass der Raven Ihnen hilft, die Probleme bei der Erstellung responsiver Layouts für Komponenten zu überwinden und die Grenzen dessen, was mit CSS möglich ist, ein wenig weiter zu verschieben.
Indem wir zeigen, was heute möglich ist, können vielleicht „echte“ Container-Queries durch Hinzufügen von etwas Syntaxzucker und einigen sehr kleinen neuen Funktionen (wie conW, conH, „strip-unit“ oder „evaluate-to-pixels“) realisiert werden. Wenn es eine Funktion in CSS gäbe, die es erlaubt, „1px“ durch ein Leerzeichen und „0px“ durch „initial“ zu ersetzen, könnte der Raven mit dem Custom Property Toggle Trick kombiniert werden und jede CSS-Eigenschaft ändern, nicht nur Längenwerte.
Indem JavaScript vermieden wird, rendern Ihre Layouts schneller, da sie nicht von JavaScript abhängig sind, das heruntergeladen oder ausgeführt wird. Es spielt keine Rolle, ob JavaScript deaktiviert ist. Diese Berechnungen blockieren Ihren Hauptthread nicht und Ihre Anwendungslogik ist nicht mit Designlogik überladen.
Vielen Dank an Chris, Andrés Galante, Cathy Dutton, Marko Ilic und David Atanda für ihre großartigen Artikel auf CSS-Tricks. Sie haben mir wirklich geholfen zu erforschen, was mit dem Raven möglich ist.
Das ist wirklich sehr hilfreich, vielen Dank, ich hatte viele Probleme, Elemente auszurichten und sie responsiv zu machen....
Die Methode zum „Verstecken“ von Dingen scheint nicht gut für die Barrierefreiheit zu sein – würden Bildschirmleser die Inhalte dieser Elemente immer noch sehen? Gleiches gilt für die Tastaturnavigation.
Außerdem tut mir das sehr im Kopf weh, und obwohl ich von Ihren CSS-Mathematikkenntnissen sehr beeindruckt bin, möchte ich dadurch nur noch mehr echte Media Queries haben!
OK .... vielleicht den Raven nicht allein zum Verstecken von Dingen verwenden ... zumindest nicht für bildschirmleser-relevante Dinge.
Absolut genial;
Wirklich cool, das eröffnet viele Möglichkeiten!
Eine Sache, die man beim Verstecken von Dingen, wie im Artikel vorgeschlagen, beachten sollte, ist die Barrierefreiheit und Bildschirmleser.
Wenn ich das richtig verstanden habe, verstecken die hier vorgeschlagenen Methoden den Inhalt nicht vor Bildschirmlesern. Außerdem ist der Inhalt per Tabulator erreichbar, sodass, wenn Sie einen Link im versteckten Inhalt haben, dieser immer noch durchgetabbt wird, während er versteckt ist.
Übrigens existieren die Einheiten
chundcwbereits in CSS, aber sie beziehen sich auf die Zeichenhöhe und Zeichenbreite, nicht auf die Containerhöhe und -breite. Das hat mich beim Lesen etwas verwirrt.Das ist großartig! Ich stimme Container-Einheiten absolut zu, aber die Einheit „ch“ existiert bereits. Vielleicht mit etwas wie pw, ph, pmin und pmax, wobei p für parent (Elternteil) steht.
OK .. da habe ich mich bei der Namensgebung vertan. Und ich habe es in conW und conH geändert.
Nur auf den Elternteil zu verweisen, ist beim Arbeiten mit Komponenten etwas seltsam (die HTML-Strukturen können tiefer als eine Ebene sein). Entweder den aktuellen Stapelkontext verwenden oder ein neues Referenzmodell einführen.
Toller Artikel!
Ich habe mit Ihrem Code Pen herumgespielt, versucht, Schriftgrößen hinzuzufügen, und ich frage mich, warum
font-size: calc(var(--is_medium) * 100);nicht zu funktionieren scheint. Gibt es dafür einen Grund?
Font Size funktioniert nicht mit dem Raven, da 100 % (Basisbreite) für die Schriftgröße nicht definiert sind.