Hinweis der Redaktion: Dieser Beitrag ist nur ein Experiment, um mit neuen CSS-Eigenschaften zu spielen. Der untenstehende Code sollte daher nicht ohne ernsthafte Verbesserungen der Barrierefreiheit verwendet werden.
Ich habe eine eigenartige Obsession mit Diagrammen und aus irgendeinem Grund möchte ich herausfinden, auf alle möglichen Arten, sie mit CSS zu erstellen. Ich schätze, das hat zwei Gründe. Erstens finde ich es interessant, dass es unzählige Möglichkeiten gibt, Diagramme und Daten im Web zu gestalten. Zweitens ist es großartig, um neue und unbekannte Technologien zu lernen. In diesem Fall: CSS Grid!
Diese Diagramm-Obsession brachte mich also zum Nachdenken: Wie würde man ein ganz normales, responsives Balkendiagramm mit CSS Grid erstellen, so etwas wie dieses?
Schauen wir uns an, wie ich dorthin gelangt bin!
Der schnelle und einfache Ansatz
Da Grid auf den ersten Blick verwirrend und seltsam sein kann, konzentrieren wir uns zunächst darauf, einen wirklich provisorischen Prototyp zu erstellen. Um loszulegen, benötigen wir die Markup für unser Diagramm
<div class="chart">
<div class="bar-1"></div>
<div class="bar-2"></div>
<div class="bar-3"></div>
<div class="bar-4"></div>
<!-- all the way up to bar-12 -->
</div>
Jede dieser bar- Klassen wird einen ganzen Balken in unserem Diagramm ausmachen und, so übel das auch erscheinen mag, wir werden uns vorerst keine Sorgen um Semantik oder Beschriftungen des Grids oder der Daten machen. Das kommt später – konzentrieren wir uns auf das CSS, damit wir mehr über Grid lernen.
Okay, damit können wir jetzt mit dem Styling beginnen! Wir benötigen 12 Balken in unserem Diagramm mit einem Abstand von 5 Pixeln dazwischen, also können wir unserer Elternklasse .chart die entsprechenden CSS Grid-Eigenschaften zuweisen
.chart {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 5px;
}
Das ist ziemlich geradlinig, wenn Sie mit Grid vertraut sind, aber es beschreibt effektiv: „Ich möchte 12 Spalten, wobei jedes der Kindelemente eine gleiche Breite (1 Fraktion) mit einem Abstand von 5 Pixeln dazwischen hat“.
Aber jetzt kommt der knifflige Teil: Mit Grid können wir die grid-template-rows-Eigenschaft verwenden, um die Höhe jedes unserer Diagrammbalken festzulegen
.chart {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: repeat(100, 1fr);
grid-column-gap: 5px;
}
Wir können diese nette neue Eigenschaft verwenden, um 100 Zeilen in unserem Grid zu erstellen, und so können wir jeden unserer Balken auf einen Prozentsatz dieser Höhe setzen und die Rechnung wird für uns einfach. Wieder verwenden wir diese repeat()-Funktion, damit jede unserer Zeilen die gleiche Höhe hat.
Bevor ich das alles im Detail erkläre, geben wir unserem Diagramm eine maximale Breite und zentrieren es mit Flexbox
* { box-sizing: border-box; }
html, body {
margin: 0;
background-color: #eee;
display: flex;
justify-content: center;
}
.chart {
height: 100vh;
width: 70vw;
/* other chart styles go here */
}
An diesem Punkt ist unser Diagramm noch leer, da wir unseren Kindelementen noch keinen Platz im Grid zugewiesen haben. Also beheben wir das! Wir wählen jede Klasse aus, die bar enthält, und verwenden die Eigenschaften grid-row-start und grid-row-end, damit sie den vertikalen Raum im Grid ausfüllen. Später werden wir eine dieser Eigenschaften ändern, um die benutzerdefinierte Höhe jedes Balkens zu definieren.
[class*="bar"] {
grid-row-start: 1;
grid-row-end: 101;
border-radius: 5px 5px 0 0;
background-color: #ff4136;
}
Wenn Sie also von diesen grid-row-Eigenschaften verwirrt sind, ist das in Ordnung! Wir sagen jedem unserer Balken, dass er ganz oben im Grid (1) beginnen und ganz unten (101) enden soll. Aber warum verwenden wir 101 als Wert für diese Eigenschaft, wenn wir unserem Grid nur 100 Zeilen zugewiesen haben? Lassen Sie uns das kurz untersuchen, bevor wir weitermachen!
Grid-Linien
Eine der eigentümlichen Dinge an Grid, die ich vor dieser Demo nicht bedacht hatte, war das Konzept der Grid-Linien, was für das Verständnis dieses neuen Layout-Tools super wichtig ist. Hier ist eine Veranschaulichung, wie Grid-Linien in einem Raster mit vier Spalten und vier Zeilen gezeichnet werden

Dieses neue Beispiel enthält vier Spalten und vier Zeilen mit den folgenden Stilen
.grid {
grid-gap: 5px;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 1fr);
}
.special-col {
grid-row: 2 / 4;
background-color: #222;
}
grid-row ist eine Kurzschreibweise für grid-row-start und grid-row-end, wobei der erste Wert angibt, wo das Element im Grid beginnen soll, und der letzte Wert, wo es enden soll. Aber! Das bedeutet, dass wir möchten, dass dieses spezielle Element hier an der Grid-Linie 2 beginnt und an der Grid-Linie 4 endet – nicht am Ende von Zeile 4. Wenn wir möchten, dass die schwarze Box alle 4 Zeilen ausfüllt, müsste sie an Linie 5 enden oder grid-row: 2 / 5, was, wenn man darüber nachdenkt, sehr viel Sinn ergibt.
Anders ausgedrückt: Wir sollten nicht davon ausgehen, dass Elemente ganze Zeilen oder Spalten in einem Grid einnehmen, sondern nur zwischen diesen Grid-Linien verlaufen. Das hat mich eine Weile gebraucht, um es konzeptionell zu verstehen und mich daran zu gewöhnen, nachdem ich einem aktuellen Tutorial von Jen Simmons dazu durchgearbeitet hatte.
Jedenfalls!
Zurück zur Demo
Das ist also der Grund, warum wir in unserer Diagramm-Demo alle Spalten an Linie 101 und nicht an 100 enden lassen – weil wir möchten, dass sie die letzte Zeile (100) ausfüllen, also müssen wir sie zu dieser bestimmten Grid-Linie (101) schicken.
Da unsere .chart-Klasse vw/vh-Einheiten verwendet, haben wir auch ein schön responsives Diagramm, ohne viel Arbeit investieren zu müssen. Wenn Sie die unten stehende Grafik vergrößern/verkleinern, werden Sie feststellen, dass sie sich gut anpasst oder dehnt, um immer den gesamten Viewport einzunehmen.
Von hier aus können wir beginnen, jeden einzelnen Balken zu stylen, um ihm die richtigen Daten zu geben, und es gibt eine ganze Reihe verschiedener Möglichkeiten, dies zu tun. Sehen wir uns nur eine davon an.
Stellen wir uns zunächst vor, wir möchten, dass der erste Balken in unserem Diagramm, .bar-1, 50/100 oder die halbe Höhe des Diagramms ist. Wir könnten das folgende CSS schreiben und wären fertig.
[class*="bar"] {
grid-row-end: 101;
}
.bar-1 {
grid-row-start: 50;
}
Das sieht gut aus! Aber hier ist der Haken – was wir eigentlich mit grid-row-start deklarieren, ist, dass der Balken bei „50“ beginnt und bei „101“ endet, aber das ist nicht wirklich das, was wir wollen. Hier ist ein Beispiel: Nehmen wir an, die Daten ändern sich in diesem hypothetischen Beispiel und wir müssen jetzt 20/100 sein. Gehen wir zurück und ändern den Wert.
.bar-1 {
grid-row-start: 20;
}
Das ist nicht richtig! Wir möchten nicht, dass der Balken im Grid bei 30 beginnt, sondern 30 % der Höhe des Diagramms beträgt. Wir könnten unseren Wert auf grid-row-start: 20; ändern oder stattdessen die Eigenschaft grid-row-end verwenden, richtig? Nun, nicht ganz.
.bar-1 {
grid-row-end: 20;
}
Die Größe des Balkens ist korrekt, aber die Position ist falsch, weil wir dem Balken sagen, dass er bei 30/100 enden soll. Wie beheben wir das und machen unseren Code super lesbar? Nun, ein Ansatz ist, Sass die Mathematik für uns erledigen zu lassen. Idealerweise möchten wir Folgendes schreiben:
.bar-1 {
// makes a bar that's 60/100 and positioned at the bottom of our chart
@include chartValue(60);
}
Und egal welchen Wert wir in dieses Mixin eingeben, wir wollen immer die korrekte Höhe und Position des Diagramms im Grid erhalten. Die Mathematik, die dieses Mixin antreibt, ist eigentlich ziemlich einfach: Wir müssen nur unseren Wert nehmen, ihn von der Gesamtzahl der Zeilen abziehen und ihn dann an die Eigenschaft grid-row-start anhängen, so:
@mixin chartValue($data) {
$totalRows: 101;
$result: $totalRows - $data;
grid-row-start: $result;
}
.bar-1 {
@include chartValue(20);
}
Der endgültige Wert, der von unserem Sass-Mixin ausgegeben wird, ist also grid-row-start: 81, aber unser Code ist super lesbar! Wir müssen nicht einmal unser Grid ansehen, um zu wissen, was passieren wird – das Diagrammelement wird am unteren Rand des Grids positioniert und der Wert ist immer korrekt.
Wie erstellen wir all diese Grid-Klassen? Ich denke, ein guter Ansatz ist, Sass einfach all diese Klassen automatisch für uns generieren zu lassen. Mit einer kleinen Änderung unseres Codes könnten wir Folgendes tun:
$totalRows: 101;
@mixin chartValue($data) {
$result: $totalRows - $data;
grid-row-start: $result;
}
@for $i from 1 through $totalRows {
.bar-#{$i} {
@include chartValue($i);
}
}
Dies iteriert über alle Zeilen unseres Diagramms und generiert eine individuelle Klasse für diese Zeilengröße. Und so könnten wir unser Markup wie folgt aktualisieren:
<div class="bar-45"></div>
<div class="bar-100"></div>
<div class="bar-63"></div>
<div class="bar-11"></div>
Und da haben wir es! Wir müssen nicht für jedes unserer Elemente einzelne Klassen von Hand schreiben und können unser Diagramm einfach durch Ändern des Markups aktualisieren. Diese Sass-Schleife gibt viele ungenutzte Klassen aus, aber es gibt genügend Tools, um diese zu entfernen.
Eine letzte Sache, die wir mit unserem Grid tun können, ist, jede Spalte mit einer Farbe nach ungerade/gerade zu gestalten
[class*="bar"]:nth-child(odd) {
background-color: #ff4136;
}
[class*="bar"]:nth-child(even) {
background-color: #0074d9;
}
Und da haben wir es! Ein responsives Diagramm, das mit CSS Grid erstellt wurde. Es gibt viel, was wir tun könnten, um diesen Code aufzuräumen. Das Erste, was wir wahrscheinlich tun sollten, ist sicherzustellen, dass wir semantisches Markup verwenden und ein Tool verwenden, um all diese Klassen zu entfernen, die von unserer Sass-Schleife ausgespuckt werden. Wir könnten uns auch damit beschäftigen, wie dieses Diagramm auf Mobilgeräten gerendert wird, und darüber nachdenken, wie wir jede Spalte und Achse des Diagramms beschriften sollten.
Aber vorerst ist dies nur der Anfang. Die TL;DR dieses Beitrags: CSS Grid kann für alle möglichen Dinge verwendet werden, nicht nur zum nebeneinander Setzen von Text und Bildern. Es eröffnet uns einen ganz neuen Zweig des Webdesigns zum Experimentieren.
Wie wäre es mit benutzerdefinierten Datenattributen und der Verwendung von attr() zum Festlegen der Höhen?
Keine Browserunterstützung.
Diese Idee hatte ich schon mal... leider scheint sie nicht weit verbreitet zu sein. Die Verwendung von attr() scheint am besten in der content-Eigenschaft von Pseudo-Elementen zu funktionieren und das war's dann auch schon. :/
Klasse!
Persönlich mag ich es nicht wirklich, Daten in meine Klassennamen zu packen, aber das ist eine einfache Lösung...
Statt
es wäre ziemlich einfach, einfach zu tun
und die Selektoren entsprechend anzupassen.
Hallo Robin, siehst du diese Methode eher als ein interessantes Experiment mit CSS Grid oder denkst du, sie bietet legitime Vorteile gegenüber der Verwendung von Flexbox für CSS-basierte Balkendiagramme?
Als Referenz, hier ist, wie ich es mit Flexbox machen würde
Ich mochte Ihren Ansatz. Danke fürs Teilen.
Ähnliche Ergebnisse ohne Grid... hier ist, was ich mir ausgedacht habe, nur zum Spaß
Nicht für den tatsächlichen Gebrauch empfohlen... die Abstände zwischen den Balken sind zum Beispiel nicht immer gleichmäßig
Ich habe vor einiger Zeit etwas Ähnliches mit divs gemacht. Könnte vereinfacht und verbessert werden, aber Grid scheint für diese Art von Dingen übertrieben zu sein.
https://codepen.io/Jpburns/full/mApbYk/
Wie Hugo Giraudel auf Twitter darauf hinweist, ist dies ein schöner visueller Effekt, aber so wie er ist, ist das „Diagramm“ völlig unzugänglich. Die Daten werden nur über die CSS-Stile offengelegt, sonst nichts.
Einige Leute haben geantwortet, dass SVG besser wäre. Dem stimme ich nicht zu. Es gibt nichts automatisch Semantisches an SVG. Ein
<rect>hat nicht mehr Bedeutung als ein<div>.Um die Daten in diesem Diagramm semantische Bedeutung zu geben, benötigen Sie
klar anzuzeigen, dass Sie eine Reihe von unterschiedlichen Datenwerten haben
den numerischen Wert als Teil des zugänglichen Namens jedes Elements in dieser Menge zu kommunizieren.
Es gibt Möglichkeiten, diese Informationen zu SVG hinzuzufügen. Aber Sie können sie auch zu HTML-Elementen hinzufügen.
Für den ersten Teil (klarstellen, dass dies eine Liste von Elementen ist) können Sie dies einfach tun, indem Sie die richtigen semantischen HTML-Elemente für eine Liste von Elementen (
<ol>und<li>) verwenden. Für den zweiten Teil benötigen Sie entweder die Daten als Textinhalt des HTML-Elements oder verwenden ARIA, um ein Label hinzuzufügen.Hier ist mein Ansatz
Ich habe ihn von Robins endgültigem Code geändert, um semantisches HTML (eine Liste von Elementen) zu verwenden, den Datenwert von der Klasse zu trennen und sichtbare sowie für Screenreader zugängliche Labels für die Werte hinzuzufügen. Dies bedeutet auch, dass die Daten sichtbar sind, auch wenn Grid nicht unterstützt wird.
Natürlich können Sie dieses Diagramm immer noch nicht kopieren und in eine Website einfügen, da es immer noch keine Labels oder Kontext gibt, die erklären, worum es bei den Daten *geht*. Aber das ist eine separate Angelegenheit. Die Kernänderung ist, dass jeder die gleiche Menge an (kontextfreien) Informationen erhält.
Die Barrierefreiheit ist immer noch nicht ideal. Ich mag es nicht wirklich,
aria-labelfür die Rohzahlen zu verwenden. Ursprünglich habe ich eindata-*-Attribut verwendet. Aber viele Screenreader lesen keine von CSS generierten Inhaltslabels vor. Die Verwendung desaria-label-Attributs erhöht die Wahrscheinlichkeit, dass sie die Werte immer noch als Namen jedes Listenelements lesen.Aber für ein *echtes* Balkendiagramm würden Sie wahrscheinlich auch den *x*-Achsenwert im Label benötigen. Was es dann schwierig macht, das
aria-label-Attribut in CSS-Selektoren und generiertem Inhalt zu verwenden.Grundsätzlich müssten Sie in jeder realen Situation die Werte duplizieren: einmal für das zugängliche Label, formatiert für Menschen, und einmal in einem Attribut oder einer benutzerdefinierten Eigenschaft, die Sie zur Definition von Stil und Größe verwenden.
Ein wirklich zugänglicher Ansatz, denke ich.
Davon ausgehend, werde ich meine Note hinzufügen: Warum nicht den tatsächlichen Inhalt (den Wert) als Inhalt einfügen? Aber was ist dann der Sinn des Labels? Die Bedeutung des Wertes, natürlich!
Das CSS ist im Grunde dasselbe, nur mit hinzugefügtem Inhalt im HTML und ::before- und ::after-Klassen, um die Daten anzuzeigen.
Das ist eine großartige Anmerkung, Amelia! Wir haben auch einen kommenden Beitrag, der sich eingehend mit den Barrierefreiheitsbedenken befasst, die Sie haben, und meine Ideen erheblich weiterentwickelt.
Fantastisch!
Sie können auch einen negativen Wert mit
grid-row-endverwenden; im Grunde bedeutet das „1“ von hinten.Großartiger Beitrag!
Etwas stört mich jedoch, warum gibt es einen Unterschied zwischen
grid-row-end: 101;undgrid-row-end: 102;bei diesen Zeilendefinitionengrid-template-rows: repeat(100, 1fr);?Ich bin gerade zurückgekommen, um dieselbe Frage zu stellen! Ich verstand nicht, warum es eine Lücke von etwa 1 Pixel am unteren Rand der Balken gab, als ich grid-row-end: 101 verwendete (bemerkte es, als ich einen Rand zum Diagramm hinzufügte). Ich ersetzte 101 durch 102, um es zu beheben, aber es stört mich immer noch, warum 101 nicht funktionierte.
Hallo Robin. Danke für die detaillierte Erklärung. Da ich gerade anfange, CSS Grid zu lernen, dachte ich, ich könnte es nicht verstehen, aber es war sehr gut geschrieben, dass selbst ein Anfänger wie ich es verstehen konnte.
Was cool wäre, wenn man die Reihenfolge von grid-auto-flow der Zeilen einfach umkehren könnte, so dass sie von unten beginnen... ähnlich wie flex-direction umgekehrt werden kann.
Eigentlich, wenn ich darüber nachdenke... ein schmutziger Weg, das zu erreichen, wäre einfach transform: scaleY(-1)... aber das könnte problematisch werden, wenn Sie andere Elemente wie Text zum selben Container hinzufügen müssen.
Sie könnten sowohl Zugänglichkeitsvorteile *als auch* automatische Größenanpassung entsprechend den Daten gleichzeitig erzielen, indem Sie
<meter>verwenden, seitlich gedreht, glaube ich. Es hat sogar ein<label>!Mit CSS Grid können Sie negative Werte verwenden, um Ihre Boxen von unten nach oben zu positionieren. Das ist eine nette Sache, die man anstelle der Berechnung des Wertes mit SASS verwenden kann.
Wie wäre es mit kontinuierlichen Werten? 0,9 %, 0,99 %, 0,999 %, 1,3434 %, 2,54354 % und so weiter? CSS-Selektoren wie
.bar-0.9854"erstellen?