Wie man die Zeilenhöhe in CSS zähmt

Avatar of Caleb Williams
Caleb Williams am

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

In CSS ist line-height wahrscheinlich eines der am meisten missverstandenen und doch am häufigsten verwendeten Attribute. Als Designer und Entwickler denken wir bei line-height vielleicht an das Konzept des Durchschusses aus dem Printdesign – ein Begriff, der interessanterweise vom buchstäblichen Einlegen von Bleistücken zwischen die Schriftzeilen stammt. 

Durchschuss und line-height, so ähnlich sie auch sein mögen, haben einige wichtige Unterschiede. Um diese Unterschiede zu verstehen, müssen wir zuerst ein wenig mehr über Typografie erfahren. 

Ein Überblick über typografische Begriffe

In der traditionellen westlichen Schriftgestaltung besteht eine Textzeile aus mehreren Teilen: 

  • Grundlinie: Dies ist die imaginäre Linie, auf der die Schrift sitzt. Wenn Sie in einem Linealheft schreiben, ist die Grundlinie die Linie, auf der Sie schreiben.
  • Unterlänge: Diese Linie liegt knapp unter der Grundlinie. Sie ist die Linie, die einige Zeichen – wie Kleinbuchstaben g, j, q, y und p – unterhalb der Grundlinie berühren. 
  • x-Höhe: Dies ist (wenig überraschend) die Höhe eines normalen Kleinbuchstabens x in einer Textzeile. Im Allgemeinen ist dies die Höhe anderer Kleinbuchstaben, obwohl einige Teile ihrer Zeichen die x-Höhe überschreiten können. Für alle praktischen Zwecke dient sie als wahrgenommene Höhe von Kleinbuchstaben.
  • Versalhöhe: Dies ist die Höhe der meisten Großbuchstaben in einer gegebenen Textzeile.
  • Oberlänge: Eine Linie, die oft knapp über der Versalhöhe erscheint, wo einige Zeichen wie ein Kleinbuchstabe h oder b die normale Versalhöhe überschreiten können.
Illustrating the ascender, cap height, x-height, baseline and descender of the Lato font with The quick fox as sample text.

Jeder der oben beschriebenen Textteile ist intrinsisch für die Schriftart selbst. Eine Schriftart wird mit jedem dieser Teile im Hinterkopf entworfen. Es gibt jedoch einige Teile der Typografie, die dem Setzer (wie Ihnen und mir!) und nicht dem Designer überlassen werden. Einer davon ist der Durchschuss.

Durchschuss ist definiert als der Abstand zwischen zwei Grundlinien in einem Schriftsatz.

Two lines of text with an order box around the second line ofd text indicating the leading.

Ein CSS-Entwickler könnte denken: „OK, Durchschuss ist line-height, machen wir weiter.“ Während die beiden zusammenhängen, unterscheiden sie sich auch auf sehr wichtige Weise.

Nehmen wir ein leeres Dokument und fügen ihm einen klassischen „CSS-Reset“ hinzu.

* {
  margin: 0;
  padding: 0;
}

Dies entfernt den Rand (margin) und den Abstand (padding) von jedem einzelnen Element.

Wir werden auch Lato von Google Fonts als unsere font-family verwenden.

Wir brauchen etwas Inhalt, also erstellen wir einen <h1>-Tag mit etwas Text und setzen die line-height auf etwas Unverschämtes, wie 300px. Das Ergebnis ist eine einzelne Textzeile mit überraschend viel Platz sowohl oberhalb als auch unterhalb der einzelnen Textzeile.

Wenn ein Browser auf die Eigenschaft line-height stößt, nimmt er tatsächlich die Textzeile und platziert sie in die Mitte einer „Zeilenbox“, die eine Höhe hat, die der line-height des Elements entspricht. Anstatt den Durchschuss für eine Schriftart festzulegen, erhalten wir etwas Ähnliches wie ein Padding auf beiden Seiten der Zeilenbox.

Two lines of text with orange borders around each line of text, indicating the line box for each line. The bottom border of the first line and the top border of the second line are touching.

Wie oben dargestellt, umgibt die Zeilenbox eine Textzeile, wobei der Durchschuss durch den Abstand unter einer Textzeile und über der nächsten erzeugt wird. Das bedeutet, dass für jedes Textelement auf einer Seite die Hälfte des Durchschusses oberhalb der ersten Textzeile und nach der letzten Textzeile in einem bestimmten Textblock vorhanden ist.

Was vielleicht überraschender ist, ist, dass die explizite Einstellung von line-height und font-size auf einem Element mit demselben Wert zusätzlichen Raum oberhalb und unterhalb des Textes lässt. Dies können wir sehen, indem wir unseren Elementen eine Hintergrundfarbe hinzufügen.

Das liegt daran, dass, obwohl die font-size auf 32px eingestellt ist, die tatsächliche Textgröße aufgrund des erzeugten Abstands etwas weniger als dieser Wert beträgt.

Damit CSS line-height wie Durchschuss behandelt

Wenn wir möchten, dass CSS einen traditionelleren typografischen Stil anstelle der Zeilenbox verwendet, möchten wir, dass eine einzelne Textzeile keinen Abstand oberhalb oder unterhalb hat – aber dass mehrzeilige Elemente ihren gesamten line-height-Wert beibehalten. 

Es ist möglich, CSS mit etwas Aufwand zum Thema Durchschuss zu bringen. Michael Taranto hat ein Tool namens Basekick veröffentlicht, das genau dieses Problem löst. Es tut dies, indem es einen negativen oberen Rand für das ::before-Pseudoelement und ein translateY für das Element selbst anwendet. Das Endergebnis ist eine Textzeile ohne zusätzlichen Abstand um sie herum.

Die aktuellste Version von Basekicks Formel finden Sie im Quellcode für das Braid Design System von SEEK. Im folgenden Beispiel schreiben wir ein Sass-Mixin, das die Hauptarbeit für uns erledigt, aber dieselbe Formel kann mit JavaScript, Less, PostCSS-Mixins oder allem anderen verwendet werden, das diese Art von mathematischen Funktionen bietet.

@function calculateTypeOffset($lh, $fontSize, $descenderHeightScale) {
  $lineHeightScale: $lh / $fontSize;
  @return ($lineHeightScale - 1) / 2 + $descenderHeightScale;
}


@mixin basekick($typeSizeModifier, $baseFontSize, $descenderHeightScale, $typeRowSpan, $gridRowHeight, $capHeight) {
  $fontSize: $typeSizeModifier * $baseFontSize;
  $lineHeight: $typeRowSpan * $gridRowHeight;
  $typeOffset: calculateTypeOffset($lineHeight, $fontSize, $descenderHeightScale);
  $topSpace: $lineHeight - $capHeight * $fontSize;
  $heightCorrection: 0;
  
  @if $topSpace > $gridRowHeight {
    $heightCorrection: $topSpace - ($topSpace % $gridRowHeight);
  }
  
  $preventCollapse: 1;
  
  font-size: #{$fontSize}px;
  line-height: #{$lineHeight}px;
  transform: translateY(#{$typeOffset}em);
  padding-top: $preventCollapse;


  &::before {
    content: "";
    margin-top: #{-($heightCorrection + $preventCollapse)}px;
    display: block;
    height: 0;
  }
}

Auf den ersten Blick sieht dieser Code definitiv nach vielen magischen Zahlen aus, die zusammengewürfelt wurden. Aber er kann erheblich aufgeschlüsselt werden, wenn man ihn im Kontext eines bestimmten Systems betrachtet. Sehen wir uns an, was wir wissen müssen.

  • $baseFontSize: Dies ist die normale font-size für unser System, um das herum alles andere verwaltet wird. Wir verwenden 16px als Standardwert.
  • $typeSizeModifier: Dies ist ein Multiplikator, der in Verbindung mit der Basis-Schriftgröße verwendet wird, um die Regel für font-size zu bestimmen. Zum Beispiel ergibt ein Wert von 2, gekoppelt mit unserer Basis-Schriftgröße von 16px, font-size: 32px.
  • $descenderHeightScale: Dies ist die Höhe des Durchschusses der Schriftart, ausgedrückt als Verhältnis. Für Lato liegt dieser Wert bei etwa 0,11.
  • $capHeight: Dies ist die spezifische Versalhöhe der Schriftart, ausgedrückt als Verhältnis. Für Lato liegt dieser Wert bei etwa 0,75.
  • $gridRowHeight: Layouts verlassen sich im Allgemeinen auf einen standardmäßigen vertikalen Rhythmus, um ein schönes und konsistent geordnetes Leseerlebnis zu erzielen. Zum Beispiel können alle Elemente auf einer Seite in Vielfachen von vier oder fünf Pixeln beabstandet sein. Wir verwenden 4 als Wert, da er sich leicht in unsere $baseFontSize von 16px teilen lässt.
  • $typeRowSpan: Ähnlich wie $typeSizeModifier dient diese Variable als Multiplikator, der mit der Rasterzeilenhöhe verwendet wird, um den Wert der line-height für die Regel zu bestimmen. Wenn unsere Standard-Rasterzeilenhöhe 4 beträgt und unser Zeilenzeilenabstand 8, erhalten wir line-height: 32px.

Nun können wir diese Zahlen in die obige Basekick-Formel einsetzen (mit Hilfe von SCSS-Funktionen und Mixins), und das ergibt das folgende Ergebnis.

Das ist genau das, was wir wollen. Bei jeder Gruppe von Textblockelementen ohne Ränder sollten sich die beiden Elemente aneinander stoßen. Auf diese Weise sind alle zwischen den beiden Elementen gesetzten Ränder pixelgenau, da sie nicht mit dem Zeilenbox-Abstand kämpfen.

Verfeinern unseres Codes

Anstatt unseren gesamten Code in ein einziges SCSS-Mixin zu packen, organisieren wir ihn ein wenig besser. Wenn wir in Systemen denken, werden wir feststellen, dass es drei Arten von Variablen gibt, mit denen wir arbeiten.

VariablentypBeschreibungMixin-Variablen
SystemebeneDiese Werte sind Eigenschaften des Designsystems, mit dem wir arbeiten.$baseFontSize
$gridRowHeight
SchriftartebeneDiese Werte sind intrinsisch für die verwendete Schriftart. Es kann zu Schätzungen und Anpassungen kommen, um die perfekten Zahlen zu ermitteln.$descenderHeightScale
$capHeight
Regel-EbeneDiese Werte sind spezifisch für die CSS-Regel, die wir erstellen.$typeSizeMultiplier
$typeRowSpan

Wenn wir so denken, wird es uns helfen, unser System einfacher zu skalieren. Sehen wir uns jede Gruppe der Reihe nach an.

Zunächst können die Variablen auf Systemebene global gesetzt werden, da diese im Laufe unseres Projekts wahrscheinlich nicht geändert werden. Das reduziert die Anzahl der Variablen in unserem Haupt-Mixin auf vier.

$baseFontSize: 16;
$gridRowHeight: 4;

@mixin basekick($typeSizeModifier, $typeRowSpan, $descenderHeightScale, $capHeight) {
  /* Same as above */
}

Wir wissen auch, dass die Variablen auf Schriftartebene spezifisch für ihre jeweilige Schriftfamilie sind. Das bedeutet, es wäre einfach genug, ein übergeordnetes Mixin zu erstellen, das diese als Konstanten festlegt.

@mixin Lato($typeSizeModifier, $typeRowSpan) {
  $latoDescenderHeightScale: 0.11;
  $latoCapHeight: 0.75;
  
  @include basekick($typeSizeModifier, $typeRowSpan, $latoDescenderHeightScale, $latoCapHeight);
  font-family: Lato;
}

Nun können wir auf Regelbasis das Lato-Mixin mit wenig Aufwand aufrufen.

.heading--medium {
  @include Lato(2, 10);
}

Diese Ausgabe liefert uns eine Regel, die die Lato-Schriftart mit einer font-size von 32px und einer line-height von 40px mit allen relevanten Transformationen und Rändern verwendet. Dies ermöglicht es uns, einfache Stilregeln zu schreiben und die Gitterkonsistenz zu nutzen, an die sich Designer bei der Verwendung von Tools wie Sketch und Figma gewöhnt sind.

Als Ergebnis können wir einfach pixelgenaue Designs ohne viel Aufwand erstellen. Sehen Sie, wie gut das Beispiel mit unserem Basis-4px-Raster unten übereinstimmt. (Sie müssen wahrscheinlich hineinzoomen, um das Raster zu sehen.)

Dies gibt uns eine einzigartige Superkraft, wenn es darum geht, Layouts auf unseren Websites zu erstellen: Wir können zum ersten Mal in der Geschichte tatsächlich pixelgenaue Seiten erstellen. Kombinieren Sie diese Technik mit einigen grundlegenden Layoutkomponenten, und wir können Seiten genauso erstellen, wie wir es in einem Designtool tun würden.

Hin zu einem Standard

Während die Vermittlung von CSS, sich mehr wie unsere Designtools zu verhalten, etwas Mühe kostet, gibt es möglicherweise gute Nachrichten am Horizont. Eine Ergänzung zur CSS-Spezifikation wurde vorgeschlagen, um dieses Verhalten nativ umzuschalten. Der Vorschlag würde, wie er derzeit aussieht, eine zusätzliche Eigenschaft für Textelemente hinzufügen, ähnlich wie line-height-trim oder leading-trim

Eines der erstaunlichen Dinge an Web-Sprachen ist, dass wir alle die Möglichkeit haben, uns zu beteiligen. Wenn dies eine Funktion ist, die Sie als Teil von CSS sehen möchten, können Sie in diesem Thread einen Kommentar hinterlassen, um Ihre Stimme Gehör zu verschaffen.