Cómo domesticar la altura de la línea en CSS »Wiki Ùtil Programar Plus

En CSS, line-height es probablemente uno de los atributos más incomprendidos, pero más utilizados. Como diseñadores y desarrolladores, cuando pensamos en line-height, podríamos pensar en el concepto de liderazgo a partir del diseño de impresión, un término, curiosamente, que proviene literalmente de poner trozos de plomo entre líneas de tipografía.

Liderando y line-height, aunque similares, tienen algunas diferencias importantes. Para entender esas diferencias, primero tenemos que entender un poco más sobre tipografía.

Una descripción general de los términos de tipografía

En el diseño tipográfico occidental tradicional, una línea de texto se compone de varias partes:

  • Base: Ésta es la línea imaginaria sobre la que se asienta el tipo. Cuando escribe en un cuaderno rayado, la línea de base es la línea en la que escribe.
  • Descender: Esta línea se encuentra justo debajo de la línea de base. Es la línea que algunos caracteres, como minúsculas g, j, q, y y p – toque debajo de la línea de base.
  • Altura X: Esta es (como era de esperar) la altura de una minúscula normal x en una línea de texto. Generalmente, esta es la altura de otras letras minúsculas, aunque algunas pueden tener partes de sus caracteres que excederán la altura de la x. A todos los efectos, sirve como la altura percibida de las letras minúsculas.
  • Altura de la tapa: Esta es la altura de la mayoría de las letras mayúsculas en una línea de texto determinada.
  • Ascendente: Una línea que a menudo aparece justo encima de la altura del límite donde algunos caracteres como minúsculas h o b puede exceder la altura normal de la tapa.

Ilustrando el ascendente, la altura de la tapa, la altura de la x, la línea de base y el descendente de la fuente Lato con The quick fox como texto de muestra.

Cada una de las partes del texto descritas anteriormente son intrínsecas a la fuente en sí. Una fuente se diseña teniendo en cuenta cada una de estas partes; sin embargo, hay algunas partes de la tipografía que se dejan en manos del autor de la tipografía (¡como tú y yo!) en lugar del diseñador. Uno de ellos es el líder.

El adelanto se define como la distancia entre dos líneas de base en un conjunto de tipos.

Dos líneas de texto con un cuadro de orden alrededor de la segunda línea de texto que indica el encabezado.

Un desarrollador de CSS podría pensar: “Está bien, liderar es el line-height, Vamonos.” Si bien los dos están relacionados, también son diferentes en algunos aspectos muy importantes.

Tomemos un documento en blanco y agreguemos un “restablecimiento de CSS” clásico:

* {
  margin: 0;
  padding: 0;
}

Esto elimina el margen y el relleno de cada elemento.

También usaremos Lato de Google Fonts como nuestro font-family.

Necesitaremos algo de contenido, así que creemos un <h1> etiqueta con un poco de texto y establezca el line-height a algo odiosamente enorme, como 300px. El resultado es una sola línea de texto con una sorprendente cantidad de espacio tanto por encima como por debajo de la única línea de texto.

Cuando un navegador encuentra el line-height propiedad, lo que realmente hace es tomar la línea de texto y colocarla en el medio de un “cuadro de línea” que tiene una altura que coincide con la altura de la línea del elemento. En lugar de establecer el interlineado en una fuente, obtenemos algo parecido a rellenar uno a cada lado del cuadro de línea.

Dos líneas de texto con bordes naranjas alrededor de cada línea de texto, que indican el cuadro de línea para cada línea.  El borde inferior de la primera línea y el borde superior de la segunda línea se tocan.

Como se ilustra arriba, el cuadro de línea se envuelve alrededor de una línea de texto donde se crea el interlineado usando el espacio debajo de una línea de texto y arriba de la siguiente. Esto significa que por cada elemento de texto en una página habrá la mitad del encabezado por encima de la primera línea de texto y después de la última línea de texto en un bloque de texto en particular.

Lo que podría ser más sorprendente es que establecer explícitamente el line-height y font-size en un elemento con el mismo valor dejará espacio adicional por encima y por debajo del texto. Podemos ver esto agregando un color de fondo a nuestros elementos.

Esto se debe a que aunque el font-size está configurado en 32px, el tamaño real del texto es algo menor que ese valor debido al espaciado generado.

Conseguir que CSS trate la altura de la línea como interlineado

Si queremos que CSS use un estilo de configuración de tipo más tradicional en lugar del cuadro de línea, queremos que una sola línea de texto no tenga espacio ni arriba ni abajo, pero permitamos que los elementos de varias líneas mantengan su totalidad line-height valor.

Es posible enseñar a CSS sobre liderazgo con un poco de esfuerzo. Michael Taranto lanzó una herramienta llamada Basekick que resuelve este mismo problema. Lo hace aplicando un margen superior negativo a la ::before pseudoelemental y un translateY al elemento en sí. El resultado final es una línea de texto sin ningún espacio adicional a su alrededor.

La versión más actualizada de la fórmula de Basekick se puede encontrar en el código fuente del Braid Design System de SEEK. En el siguiente ejemplo, estamos escribiendo un mixin de Sass para hacer el trabajo pesado por nosotros, pero la misma fórmula se puede usar con JavaScript, Less, mixins de PostCSS o cualquier otra cosa que proporcione este tipo de funciones matemáticas.

@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;
  }
}

A primera vista, este código definitivamente parece una gran cantidad de números mágicos improvisados. Pero se puede descomponer considerablemente si se piensa en el contexto de un sistema en particular. Echemos un vistazo a lo que necesitamos saber:

  • $baseFontSize: Este es el normal font-size para nuestro sistema alrededor del cual se gestionará todo lo demás. Usaremos 16px como valor predeterminado.
  • $typeSizeModifier: Este es un multiplicador que se usa junto con el tamaño de fuente base para determinar el font-size regla. Por ejemplo, un valor de 2 junto con nuestro tamaño de fuente base de 16px nos dará font-size: 32px.
  • $descenderHeightScale: Esta es la altura del descendente de la fuente expresada como una proporción. Para Lato, esto parece estar alrededor de 0,11.
  • $capHeight: Esta es la altura de la tapa específica de la fuente expresada como una proporción. Para Lato, esto es alrededor de 0,75.
  • $gridRowHeight: Los diseños generalmente se basan en un ritmo vertical predeterminado para crear una experiencia de lectura agradable y uniformemente espaciada. Por ejemplo, todos los elementos de una página pueden estar separados en múltiplos de cuatro o cinco píxeles. Usaremos 4 como valor porque se divide fácilmente en nuestro $ baseFontSize de 16px.
  • $typeRowSpan: Me gusta $typeSizeModifier, esta variable sirve como un multiplicador que se utilizará con la altura de la fila de la cuadrícula para determinar la regla line-height valor. Si nuestra altura de fila de cuadrícula predeterminada es 4 y nuestro intervalo de fila de tipo es 8, eso nos dejaría con una altura de línea: 32px.

Ahora podemos insertar esos números en la fórmula de Basekick anterior (con la ayuda de funciones SCSS y mixins) y eso nos dará el resultado a continuación.

Eso es justo lo que estamos buscando. Para cualquier conjunto de elementos de bloque de texto sin márgenes, los dos elementos deben chocar entre sí. De esta manera, los márgenes establecidos entre los dos elementos serán perfectos en píxeles porque no estarán peleando con el espaciado del cuadro de línea.

Refinando nuestro código

En lugar de volcar todo nuestro código en una sola mezcla de SCSS, organicémoslo un poco mejor. Si pensamos en términos de sistemas, notaremos que hay tres tipos de variables con las que estamos trabajando:

Tipo de variable Descripción Variables de mezcla
Nivel del sistema Estos valores son propiedades del sistema de diseño con el que estamos trabajando. $baseFontSize
$gridRowHeight
Nivel de fuente Estos valores son intrínsecos a la fuente que estamos usando. Puede haber algunas conjeturas y ajustes involucrados para obtener los números perfectos. $descenderHeightScale
$capHeight
Nivel de regla Estos valores serán específicos de la regla CSS que estamos creando. $typeSizeMultiplier
$typeRowSpan

Pensar en estos términos nos ayudará a escalar nuestro sistema mucho más fácilmente. Consideremos a cada grupo por turno.

En primer lugar, las variables a nivel del sistema se pueden configurar globalmente, ya que es poco probable que cambien durante el transcurso de nuestro proyecto. Eso reduce la cantidad de variables en nuestro mixin principal a cuatro:

$baseFontSize: 16;
$gridRowHeight: 4;

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

También sabemos que las variables de nivel de fuente son específicas de su familia de fuentes determinada. Eso significa que sería bastante fácil crear un mixin de orden superior que los establezca como constantes:

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

Ahora, sobre una base de reglas, podemos llamar al Lato mezclando con poco alboroto:

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

Esa salida nos da una regla que usa la fuente Lato con un font-size de 32px y un line-height de 40px con todas las traducciones y márgenes relevantes. Esto nos permite escribir reglas de estilo simples y utilizar la consistencia de la cuadrícula a la que los diseñadores están acostumbrados cuando usan herramientas como Sketch y Figma.

Como resultado, podemos crear fácilmente diseños de píxeles perfectos con poco esfuerzo. Vea qué tan bien se alinea el ejemplo con nuestra cuadrícula base de 4px a continuación. (Es probable que tenga que acercar la imagen para ver la cuadrícula).

Hacer esto nos da un superpoder único cuando se trata de crear diseños en nuestros sitios web: podemos, por primera vez en la historia, crear páginas con píxeles perfectos. Combine esta técnica con algunos componentes básicos de diseño y podemos comenzar a crear páginas de la misma manera que lo haríamos en una herramienta de diseño.

Avanzando hacia un estándar

Si bien enseñar CSS a comportarse más como nuestras herramientas de diseño requiere un poco de esfuerzo, hay buenas noticias en el horizonte. Se ha propuesto una adición a la especificación CSS para alternar este comportamiento de forma nativa. La propuesta, tal como está ahora, agregaría una propiedad adicional a los elementos de texto similar a line-height-trim o leading-trim.

Una de las cosas asombrosas de los lenguajes web es que todos tenemos la capacidad de participar. Si le parece una característica que le gustaría ver como parte de CSS, tiene la posibilidad de ingresar y agregar un comentario a ese hilo para que se escuche su voz.