Wextensible

Uso de height y line-height en elementos Inline

Creo que conviene refrescar algunas cosas antes de ver cómo alineamos verticalmente los elementos Inline. Como, por ejemplo, que la altura de un elemento Inline no puede declararse explícitamente. Se ignora la propiedad height y pasa a ser calculada por el navegador en base al tamaño de la fuente de texto. A no ser que el elemento sea de contenido reemplazado, como sucede con <img> que es un elemento en línea y su altura se especifica en un atributo o, en su defecto, se extrae de las medidas de la imagen original.

Otra propiedad que afecta a los elementos en línea es la altura de línea line-height. Y es necesario recordar que una caja de línea se compone de elementos Inline y el propio texto que ocupan una línea completa en el contenedor de bloque de contención que la alberga. Sobre esto puede ver la definición de elemento a nivel de línea.

En este ejemplo vamos a poder modificar font-size y line-height de elementos <span> y observar el valor computado que, para ser breves, es el valor CSS que el navegador aplica al elemento y que puede obtenerse con JavaScript usando getComputedStyle() (ver más en valores especificado, computado, usado, real y resuelto). Finalmente también podemos observar las dimensiones finales con JavaScript accediendo a offsetHeight que nos da la altura renderizada del elemento. Este valor es independiente del CSS puesto que podemos obtenerlo de cualquier elemento tenga o no declarado estilo CSS.

Ejemplo: Espacio vertical en elementos Inline

El contenedor de bloque con borde marrón, que hará las veces de caja de línea, contiene tres elementos Inline con estas propiedades que podemos modificar:

PropiedadSpan1Span2Span3
font-sizepxpxpx
line-height

El resultado se muestra a continuación:

Span1Span2Span3

Las propiedades computadas son las siguientes:

PropiedadSpan1Span2Span3Caja de línea
height
line-height
font-size
offsetHeight
offsetHeight/font-sizen.a.

Lo que nunca debemos olvidar es que un elemento Inline no tiene altura CSS. En el ejemplo si incrementamos el font-size vemos que el valor CSS de height siempre se mantiene en auto. Aunque efectivamente el elemento renderizado aumenta en alto, tal como se observa por su offsetHeight. Además la relación entre offsetHeigh y font-size siempre se mantiene en valores cercanos.

La altura de la caja de línea viene determinada por el producto line-height por font-size. El valor normal para line-height generará una altura de caja de línea mínima suficiente para albergar todos los caracteres de la fuente. Cada fuente tiene un valor normal propio, estando generalmente comprendido entre 1 y 2.

Vemos que line-height no modifica la altura del elemento renderizado tal como resulta al observar offsetHeight. Por lo tanto no hay ninguna relación entre line-height y offsetHeight para un elemento Inline. Lo único que modifica line-height es el alto de la caja de línea y, como consecuencia, la propiedad height del contenedor de bloque que alberga esa caja de línea. Además la altura de la caja de línea viene determinada por el mayor line-height de los elementos Inline que componen esa caja.

Alineación vertical de elementos Inline con CSS3

Figura
Figura. Líneas base (baselines) de una fuente de texto para CSS.

Antes de hablar de alineaciones verticales recordaremos el concepto de líneas base (baseline), que son líneas imaginarias que estructuran el espacio vertical de los caracteres. Y esto es importante conocerlo porque algunas alineaciones verticales se basan en estas líneas base. En la Figura puede ver un esquema de las líneas base representativas para CSS. La línea alphabetic es sobre la que se asientan los caracteres en idiomas occidentales y que, por generalización, suele indicarse como baseline. Este valor es el que tiene por defecto la propiedad vertical-align. Vemos que algunos caracteres como "p" minúscula descienden bajo ella, pero la mayoría permanecen por encima. La línea middle es la que cruza la mitad del alto del carácter "x" minúscula. Los límites de la caja están en text-after-edge y text-before-edge. Otras líneas son referencias para otros idiomas, pues usan un baseline distinto del alphabetic.

Teníamos CSS2.1 vertical-align para alinear verticalmente elementos HTML en línea o Inline. Ahora tenemos en CSS3 el módulo CSS Inline Layout Module Level 3, con alineaciones verticales en su apartado Line Heights and Baseline Alignment (en fase WD de borrador del 17 septiembre 2015). Se agregan nuevas propiedades dominant-baseline, alignment-baseline y baseline-shift. La propiedad vertical-align viene a ser un shorthand o forma corta o abreviada de los valores de alignment-baseline y baseline-shift, advirtiéndose que se debe seguir usando vertical-align en lugar de estas dos nuevas propiedades.

Por lo que hasta ahora he podido explorar es que estas nuevas propiedades sólo aplican a elementos SVG como <text> o <tspan>. Pero intentaremos sacarle el mayo provecho para ver hasta dónde podemos llegar alineando verticalmente elementos de texto. Veámos como son las nuevas propiedades:

dominant-baseline
Especifica la línea base dominante de un contenedor de bloque que será usada para alinear el texto y los elementos Inline de ese contenedor. El valor inicial es auto que equivale a la línea base alphabetic en escritura horizontal. Otros valores son alphabetic, central, mathematical, hanging, text-bottom, text-top. Estos dos últimos equivalen a las líneas base text-after-edge y text-before-edge. La central se ubica al centro vertical entre esos dos extremos.
alignment-baseline
Especifica que punto de un elemento Inline es alineado con un punto del contenedor padre. El valor inicial es baseline. Otros valores son alphabetic, middle, central, mathematical, text-bottom, text-top, bottom, center, top. Las tres últimas alinean respecto a la caja de línea. Para SVG deben usarse text-before-edge y text-after-edge en lugar de text-top y text-bottom respectivamente.
baseline-shift
Especifica un desplazamiento del elemento Inline con respecto a su punto de alineación base. El valor inicial es cero, con valores como sub, super, dimensiones como píxeles y porcentajes referidos a la altura de la caja de línea.
Figura
Figura. El área de contenido de texto está entre text-top y text-bottom.

Puede ser interesante realizar una agrupación de alineaciones verticales que podría ser por líneas base, por área de contenido de texto, por caja de línea y finalmente por desplazamiento de línea base.

La alineación por líneas base (baselines) trata de alinear la línea base del elemento Inline con la línea base del texto del contenedor padre que alberga ese elemento. Incluiría el valor baseline de vertical-align de CSS2.1 más otros que se incorporan en CSS3.

El área de contenido de texto para un elemento es áquella que permite alojar completamente los caracteres de una fuente determinada con un tamaño dado. Sus límites son text-top y text-bottom tal como se expone en la Figura. Su altura viene determinada por el valor normal de la propiedad line-height multiplicado por el valor de font-size. Por eso también podemos denominarla como caja de línea normal. En este caso se trata de alinear el borde superior o inferior del hijo con el respectivo del área de contenido de texto del padre. El nuevo valor central alinearía al centro.

La caja de línea se limita por los valores top y bottom tal como se observa en la Figura. Cuando la altura de línea es normal esos valores coinciden con text-top y text-bottom. En este caso se alineará el borde superior o inferior del hijo Inline con el respectivo de la caja de línea del padre. El nuevo center centraría en CSS3 en esa caja de línea, si funcionara. Podríamos incluir aquí también el valor middle pues su objetivo es alinear el centro de la "x" del padre con el centro de la caja de línea del hijo.

Y por último la alineación por desplazamiento de línea base. Se declara una medida de longitud positiva o negativa que desplaza la línea base del elemento respecto de su ubicación original. Este método es también el usado por el navegador para presentar los valores sub y super.

Las tres especificaciones que usaremos para intentar ver cómo ejecutan la alineación vertical son las dos que indicamos más arriba y una tercera de SVG:

La siguiente tabla hace corresponder los valores con las especificaciones anteriores. Los valores CSS finalizados en top o bottom son equivalentes a los que se usan en SVG y que finalizan en before-edge y after-edge respectivamente.

GrupoValorCSS2.1CSS3(WD)SVG1.1
Baselinesbaseline
alphabetic
mathematical
hanging
ideographic
Área contenidotext-toptext-before-edge
central
text-bottomtext-after-edge
Caja líneatopbefore-edge
center
middle
bottomafter-edge
Baseline-shiftsuper
sub

Y ha llegado el momento de ver el ejemplo. Probaremos los valores de vertical-align de CSS2 y los nuevos valores de CSS3 y SVG. Los aplicaremos a elementos HTML y SVG. De entrada hay que decir que los nuevos valores de CSS3 sólo funcionan en SVG y para Chrome 47, a excepción de center que no está contemplado en SVG. Ninguno funciona en Firefox 43. IE 11 parece admitir estos valores pero no hace nada con ellos.

Los valores resaltados en amarillo son de CSS2. Estos más los resaltados en negrita son los de CSS3. Agregando los que aparecen en cursiva son los de SVG, a excepción de center que no lo contempla. Puede ver la tabla anterior para la correspondencia entre valores y especificaciones.

Ejemplo: Alineación vertical Inline en CSS2, CSS3 y SVG

alignment-baseline
Baselines
Área de contenido
Caja de línea
baseline-shift
Cualquier valor

El valor se aplica al elemento de color azul que es (o debería ser) alineado respecto al padre de color rojo. El contenedor con texto "Html" tiene un <div> con un hijo <span>. El de texto "Svg" tiene un <text> con un hijo <tspan>.

Html......Html...
Svg......Svg...

Sólo los valores sub, super y baseline se ejecutan actualmente igual en HTML y SVG. Con el resto hay diferencias. Los valores que Chrome 47 aplica a SVG difieren en el sentido de la alineación. Por ejemplo, la alineación con text-before-edge (text-top) y text-after-edge (text-bottom) parecen estar al revés en SVG con respecto a HTML.

Tras estudiar todo esto se llega a la conclusión de que, por ahora, tenemos que seguir alineando verticalmente los elementos Inline con la propiedad vertical-align de CSS2.1. Y si usamos SVG olvidarnos de alinear verticalmente, pues sólo Chrome lo soporta.

Finalizaremos exponiendo un poco de código usado en el ejemplo. Los elementos HTML y SVG:

<!-- Elementos Inline HTML -->
<div id="xdiv">
    Html...
    <span id="xspan">...Html...</span>
</div>
<!-- Elementos de texto SVG -->
<svg id="xsvg">
    <text id="xtext" y="75">
        Svg...
        <tspan id="xtspan">...Svg...</tspan>
    </text>
</svg>
    

Y el estilo aplicado:

/* HTML -------------------------------- */
#xdiv {
    color: red;
    width: 150px;
    font-size: 32px;
    /* mayor que line-height normal (32×1.2) 
    para ver top y bottom */
    line-height: 128px;
}
#xspan {
    color: blue;
    /* menor fuente para ver text-top y text-bottom */
    font-size: 14px;
    /* resetear line-height heredado para ver top y bottom */ 
    line-height: normal;
} 
/* SVG --------------------------------  */
#xsvg {
    width: 150px;
    height: 125px;
}
#xtext {
    fill: red;
    font-size: 32px;
}
#xtspan {
    fill: blue;
    font-size: 14px;
}
    

El resto del código lo puede ver usando el botón "Código" de la barra de menú superior que expone todo el código fuente original de esta página.

Buscando el baseline y el alto de la "x"

Figura
Figura. La línea base (baseline) del texto se alinea con el borde inferior de un inline-block vacío.

En el apartado anterior vimos que para HTML tenemos que seguir con vertical-align de CSS2.1 pues los nuevos valores no funcionan. De todas formas aún con los valores de CSS2 me ronda una pregunta: ¿A qué altura respecto a la caja de línea ubica el navegador la línea baseline para cada familia y tamaño de fuentes? Hay una forma de llegar a observarlo basándonos en el comportamiento del navegador siguiendo la especificación de CSS2.1.

En el apartado alineando elementos inline-block del tema anterior expusimos cuatro elementos inline-block, los tres primeros con las letras "A" (rojo), "B" (verde), "C" (azul) y el cuarto sin contenido alguno (negro). Con el valor baseline de alineación por defecto resultaba que los cuatro se alineaban por el borde inferior del elemento sin contenido, tal como aparece en la Figura. He pintado una raya de color magenta para observar mejor ese alineamiento. Este comportamiento se basa en lo que expone la especificación de CSS2.1 vertical-align en un párrafo al final:

La línea base (baseline) de un inline-block es la línea base de su última caja de línea en el flujo normal, a menos que no tenga cajas de línea en flujo o su propiedad overflow tenga un valor computado distinto de "visible", en cuyos casos la línea base es el borde del margen inferior del inline-block.

Dado que el inline-block de color negro no tiene nada en su interior entonces su línea base es su borde inferior. Para los otros tres elementos sus líneas bases son las que asientan el texto. El navegador alinea las cuatro líneas base para lo que debe desplazar verticalmente los tres primeros elementos. Este efecto nos podria servir para detectar donde se ubica la línea base de una determinada fuente, lo que haremos en el siguiente apartado en un ejemplo interactivo, pero que explicaremos previamente ahora.

Figura
Figura. Buscando ascendente, descendente y altura de la "x" para la fuente Courier New de 48 píxeles.

En la Figura puede ver una captura del ejemplo calculando las posiciones para una fuente Courier New de 48px. Tenemos un contenedor de bloque DIV con fondo naranja con un SPAN con fondo verde y texto rojo. Le sigue otro SPAN pero inline-block sin contenido, al que le damos un ancho de 10px sólo para visualizarlo. El DIV tiene una font-family Courier New y un font-size de 48px. Estos valores y su line-height, que por defecto es normal, se heredarán a sus dos hijos.

El DIV está posicionado relativamente con dos objetivos. Por un lado para obtener las posiciones y medidas de sus hijos y, por otro, para posicionar un tercer hijo que dibujará la línea base en color azul. A la derecha hay otro contenedor DIV independiente del anterior con fondo rosado que contiene un hijo con fondo marrón. Los usaremos para calcular la altura de la letra "x" que explicaremos más abajo.

El elemento inline-block no tiene alto, sólo ancho. No es visible pero agranda su padre DIV hacia la derecha y por eso vemos el fondo de color naranja. Ese es su posicionamiento horizontal. Su posicionamiento vertical, es decir, su valor offsetTop nos dará la ubicación de la línea base (baseline) para la fuente y tamaño dados. Ahí posicionaremos el tercer hijo, una raya horizontal azul sobre la que se asientan los caracteres.

El ascendente (ascent) es el área superior a partir de la línea base sobre la que se asientan los caracteres. Su valor 40px es el del offsetTop del elemento inline-block. Por otro lado el área inferior es el descendente (descent) donde se ubican los trazos que descienden como el de la letra "p". Su valor 14px lo calculamos deduciéndo el ascendente de la altura total de la caja que obtenemos con el offsetHeight (54px) del elemento DIV.

Otro dato que vamos a necesitar es el valor normal del line-height. Dividiendo el alto del DIV por el tamaño de la fuente obtenemos 54 / 48 = 1.125. Para una fuente de 48px Courier New el navegador destinará 48 × 1.125 = 54 píxeles de altura para ubicar con suficiente holgura el alto de todos los caracteres de la fuente. Y precisamente la suma del ascendente y descendente nos da también esa altura de línea.

Por último calcularemos también la altura de la letra "x". La necesitaremos para la línea base middle. Para ello disponemos de otro DIV con fondo rosado y la misma fuente y tamaño. En su interior hay un inline-block sin contenido con fondo marrón al que le ponemos height: 1ex, con lo que tomará la altura de la letra "x". Sólo resta extraer su offsetHeight (20px) para poder usar ese valor para ubicar la línea base middle.

Alineación vertical con vertical-align de CSS2

En el siguiente ejemplo interactivo podemos probar con diversas fuentes y tamaños. Tras esto calcularemos los valores ascendente, descendente y altura de la "x". Luego podemos ver donde se ubican las alineaciones verticales para los principales valores de vertical-align de CSS2.1, pudiéndose modificar también la altura de línea line-height.

Ejemplo: Alineación vertical Inline en CSS2.1

font-family
font-size
Paso 1) Calcular ascendente, descendente y alto de la "x" para esa fuente y tamaño:
AxfpÑ.
Paso 2) Alinear elemento Inline de texto azul con el padre de texto rojo:
line-height
vertical-align de CSS2.1
.AxfpÑ..AxfpÑ
Valores representativos:
  • El lineHeight normal es , que corresponde al valor normal de la propiedad line-height.
  • El lineHeight actual es .
  • El alto de caja de línea normal es píxeles calculados con lineHeight × fontSize para valor normal de lineHeight.
  • El alto de caja de línea actual es píxeles, calculados con lineHeight × fontSize (Con decimales, ver notas).
  • El offsetHeight de la caja actual es píxeles para la altura renderizada de la caja y que debería ser igual al valor anterior (ver notas).

El valor baseline se ubica donde se asientan los caracteres. Alineará la línea base del elemento Inline con texto azul con la línea base del padre con texto rojo. Con valor normal para line-height vemos que esa posición es la del ascent calculado en el paso previo.

La renderización de las fuentes no es necesariamente la misma en todos los navegadores. Digo esto porque en el siguiente párrafo se exponen algunos valores númericos que he tomado en el navegador Chrome 47 y que en otros navegadores podrían ser diferentes.

Si la fuente es Courier New de 48px la caja de línea normal será de 54px, resultado del producto de su valor del lineHeight normal de 1.125 por el fontSize de 48. Si usamos una altura de línea mayor con valor númerico 2, entonces se genera una caja de línea de 2 × 48 = 96px. La diferencia o exceso sobre la caja normal es de 96-54 = 42px. Ese exceso se distribuirá en partes iguales por encima y por debajo de la caja de línea normal. Por lo tanto se sumará la mitad 21px a los 40px del ascendente resultando en una ubicación de 61px para la línea base "baseline".

La alineación middle alineará el centro vertical de la caja de línea del elemento Inline con texto azul con la mitad del caracter "x" del padre con texto rojo. La mitad de la "x" no tiene porque coincidir con el centro vertical del padre por lo que no podemos usar middle para alinear al centro vertical. De hecho CSS2.1 no tiene alineación al centro vertical, pero si está previsto en CSS3 con los valores central y center tal como vimos en un apartado anterior. Aunque por ahora no funciona en HTML.

Con valor normal para line-height las alineaciones top y bottom son iguales a text-top y text-bottom respectivamente. Pues en ese caso la caja de línea tiene el mismo alto que la que se calcula con el producto de lineHeight × fontSize. Podemos decir que text-top y text-bottom nos dan los límites de la caja de línea normal mientras que top y bottom nos dan los límites de la caja de línea distinta de normal. Si incrementamos la altura de línea por encima del valor normal veremos la diferencia entre estos valores.

Notas sobre el ejemplo de vertical-align de CSS2

En el cálculo de la caja de línea actual intervienen decimales al multiplicar la altura de línea actual (número real) por el tamaño de la fuente (número entero). Dado que el navegador sólo puede pintar píxeles enteros cabría esperar que lo redondee al entero más cercano. Pero con ciertos valores vemos que la altura de línea calculada no coincide con el efectivo offsetHeight que finalmente renderiza el navegador. Para observarlo presentamos el cálculo con decimales. Así para un ejemplo con Courier New de 48px con un line-height de 1.6 tenemos una caja calculada de 1.6 × 48 = 76.8 píxeles. Chrome la renderiza a 76 píxeles, mientras que Firefox e IE lo hacen a 77 píxeles, lo que parece más adecuado. Esto puede dar lugar a desviaciones de ±1 píxel en la presentación de la línea base del ejemplo. Para intentar anular este efecto usamos en los cálculos el valor renderizado de la caja de línea en lugar del calculado.

Otro aspecto que he observado está relacionado con las fuentes Arial y Times New Roman. Vea la siguiente captura de pantalla para la fuente Arial de 48px con valor normal para la altura de línea en los navegadores Chrome 47, Firefox 43 e IE11. Se presentan los valores ascendente y descendente para esa fuente y tamaño.

Figura
Figura. Distinto comportamiento para fuente Arial en Chrome, Firefox e IE ("a" es el ascendente y "d" el descendente).

En IE 11 la presentación es, supuestamente, la esperada. Porque la caja de línea del elemento con fondo verde debería ocupar todo el alto del contenedor padre con fondo naranja. Sin embargo en Chrome y Firefox no es así, apareciendo dos líneas naranja en los bordes superior e inferior. Esto es una evidencia de que la caja verde no ocupa todo el alto del contenedor. En esos casos la línea baseline será el ascendente menos el alto de esa separación superior, al menos para estas fuentes Arial y Times New Roman en los navegadores Chrome y Firefox. No sé si esto se manifestará en otras fuentes que no estén en la lista del ejemplo y/o otros dispositivos.