La tipografía receptiva se ha probado en el pasado con una gran cantidad de métodos, como consultas de medios y CSS calc()
.
Aquí, vamos a explorar una forma diferente de escalar linealmente el texto entre un conjunto de tamaños mínimo y máximo a medida que aumenta el ancho de la ventana gráfica, con la intención de hacer que su comportamiento en diferentes tamaños de pantalla sea más predecible: todo en una sola línea de CSS , gracias a clamp()
.
La función CSS clamp()
es un bateador pesado. Es útil para una variedad de cosas, pero es especialmente bueno para la tipografía. Así es como funciona. Toma tres valores:
clamp(minimum, preferred, maximum);
El valor que devuelve será el valor preferido, hasta que ese valor preferido sea menor que el valor mínimo (momento en el cual se devolverá el valor mínimo) o mayor que el valor máximo (momento en el cual se devolverá el máximo).
En este ejemplo, el valor preferido es 50%. A la izquierda, el 50% de la ventana gráfica de 400 px es de 200 px, que es menor que el valor mínimo de 300 px que se usa en su lugar. A la derecha, el 50% de la ventana gráfica de 1400 px es igual a 700 px, que es mayor que el valor mínimo e inferior al valor máximo de 800 px, por lo que equivale a 700 px.
Entonces, ¿no sería siempre el valor preferido, asumiendo que no está siendo raro y lo establece entre el mínimo y el máximo? Bueno, se espera que use una fórmula para el valor preferido, como:
.banner {
width: clamp(200px, 50% + 20px, 800px); /* Yes, you can do math inside clamp()! */
}
Digamos que desea establecer el mínimo de un elemento font-size
a 1rem cuando el ancho de la ventana gráfica es de 360 px o menos, y establece el máximo en 3.5rem cuando el ancho de la ventana es de 840 px o más.
En otras palabras:
1rem = 360px and below
Scaled = 361px - 839px
3.5rem = 840px and above
Cualquier ancho de ventana gráfica entre 361 y 839 píxeles necesita un tamaño de fuente escalado linealmente entre 1 y 3.5rem. Eso es realmente muy fácil con clamp()
! Por ejemplo, con un ancho de ventana de 600 píxeles, a medio camino entre 360 y 840 píxeles, obtendríamos exactamente el valor medio entre 1 y 3.5rem, que es 2.25rem.
Qué estamos tratando de lograr con clamp()
Se llama interpolación lineal: obtener información intermedia entre dos puntos de datos.
Estos son los cuatro pasos para hacer esto:
Paso 1
Elija sus tamaños de fuente mínimo y máximo, y sus anchos de ventana gráfica mínimos y máximos. En nuestro ejemplo, eso es 1rem y 3.5rem para los tamaños de fuente, y 360px y 840px para los anchos.
Paso 2
Convierta los anchos a rem
. Dado que 1rem en la mayoría de los navegadores es 16px por defecto (más sobre eso más adelante), eso es lo que vamos a usar. Entonces, ahora los anchos mínimo y máximo de la ventana gráfica serán 22.5rem y 52.5rem, respectivamente.
Paso 3
Aquí, vamos a inclinarnos un poco hacia el lado de las matemáticas. Cuando se combinan, los anchos de la ventana gráfica y los tamaños de fuente forman dos puntos en un sistema de coordenadas X e Y, y esos puntos forman una línea.
(22.5, 1)
y (52.5, 3.5)
Necesitamos un poco esa línea, o más bien su pendiente y su intersección con el eje Y para ser más específicos. He aquí cómo calcular eso:
slope = (maxFontSize - minFontSize) / (maxWidth - minWidth)
yAxisIntersection = -minWidth * slope + minFontSize
Eso nos da un valor de 0.0833 para la pendiente y -0.875 para la intersección en el eje Y.
Etapa 4
Ahora construimos el clamp()
función. La fórmula para el valor preferido es:
preferredValue = yAxisIntersection[rem] + (slope * 100)[vw]
Entonces la función termina así:
.header {
font-size: clamp(1rem, -0.875rem + 8.333vw, 3.5rem);
}
Puedes visualizar el resultado en la siguiente demostración:
Adelante, juega con él. Como puede ver, el tamaño de fuente deja de crecer cuando el ancho de la ventana gráfica es de 840 px y deja de encogerse a 360 px. Todo lo que hay en el medio cambia de forma lineal.
¿Qué pasa si el usuario cambia el tamaño de fuente de la raíz?
Es posible que haya notado un pequeño defecto con todo este enfoque: solo funciona siempre que el tamaño de fuente de la raíz sea el que cree que es, que es de 16 px en el ejemplo anterior, y nunca cambia.
Estamos convirtiendo los anchos, 360px y 840px, a rem
unidades dividiéndolas por 16 porque eso es lo que asumimos es el tamaño de fuente de la raíz. Si el usuario tiene sus preferencias configuradas en otro tamaño de fuente raíz, digamos 18px en lugar de los 16px predeterminados, entonces ese cálculo será incorrecto y el texto no cambiará el tamaño de la forma que esperaríamos.
Solo hay un enfoque que podemos usar aquí, y es (1) realizar los cálculos necesarios en el código al cargar la página, (2) escuchar los cambios en el tamaño de fuente de la raíz y (3) volver a calcular todo si se produce algún cambio .
Aquí hay una función de JavaScript útil para hacer los cálculos:
// Takes the viewport widths in pixels and the font sizes in rem
function clampBuilder( minWidthPx, maxWidthPx, minFontSize, maxFontSize ) {
const root = document.querySelector( "html" );
const pixelsPerRem = Number( getComputedStyle( root ).fontSize.slice( 0,-2 ) );
const minWidth = minWidthPx / pixelsPerRem;
const maxWidth = maxWidthPx / pixelsPerRem;
const slope = ( maxFontSize - minFontSize ) / ( maxWidth - minWidth );
const yAxisIntersection = -minWidth * slope + minFontSize
return `clamp( ${ minFontSize }rem, ${ yAxisIntersection }rem + ${ slope * 100 }vw, ${ maxFontSize }rem )`;
}
// clampBuilder( 360, 840, 1, 3.5 ) -> "clamp( 1rem, -0.875rem + 8.333vw, 3.5rem )"
Estoy dejando de lado deliberadamente cómo inyectar la cadena devuelta en el CSS porque hay un montón de formas de hacerlo según sus necesidades y si está utilizando CSS vanilla, una biblioteca CSS-in-JS u otra cosa. Además, no hay un evento nativo para cambios de tamaño de fuente, por lo que tendríamos que verificarlo manualmente. Podríamos usar setInterval
verificar cada segundo, pero eso podría tener un costo de rendimiento.
Este es más un caso de borde. Muy pocas personas cambian el tamaño de fuente de su navegador e incluso menos lo cambiarán precisamente mientras visitan su sitio. Pero si desea que su sitio sea lo más receptivo posible, este es el camino a seguir.
Para aquellos a quienes no les importa ese caso extremo
¿Crees que puedes vivir sin que sea perfecto? Entonces tengo algo para ti. Hice una pequeña herramienta para realizar los cálculos de forma rápida y sencilla.
Todo lo que tiene que hacer es conectar los anchos y tamaños de fuente en la herramienta, y la función se calcula para usted. Copia y pega el resultado en tu CSS. No es lujoso y estoy seguro de que se puede mejorar mucho, pero, para el propósito de este artículo, es más que suficiente. Siéntase libre de bifurcar y modificar al contenido de su corazón.
Cómo evitar que el texto vuelva a fluir
Tener un control tan preciso sobre las dimensiones de la tipografía nos permite hacer otras cosas interesantes, como evitar que el texto se vuelva a fluir en diferentes anchos de ventana.
Así es como se comporta normalmente el texto.
Tiene varias líneas en un determinado ancho de ventana …
… y envuelve sus líneas para adaptarse a otro ancho
Pero ahora, con el control que tenemos, podemos hacer que el texto mantenga el mismo número de líneas, dividiéndose siempre en la misma palabra, en cualquier ancho de ventana que le demos.
Ancho de la ventana gráfica = 400 px
Ancho de la ventana gráfica = 740 px
¿Entonces como hacemos esto? Para empezar, la relación entre los tamaños de fuente y el ancho de la ventana gráfica debe permanecer igual. En este ejemplo, pasamos de 1rem a 320px a 3rem a 960px.
320 / 1 = 320
960 / 3 = 320
Si estamos usando el clampBuilder()
función que hicimos anteriormente, que se convierte en:
const text = document.querySelector( "p" );
text.style.fontSize = clampBuilder( 320, 960, 1, 3 );
Mantiene la misma proporción de ancho a fuente. La razón por la que hacemos esto es porque necesitamos asegurarnos de que el texto tenga el tamaño correcto en cada ancho para que pueda mantener el mismo número de líneas. Seguirá refluyendo en diferentes anchos, pero hacer esto es necesario para lo que vamos a hacer a continuación.
Ahora tenemos que obtener ayuda del carácter CSS (ch
) porque tener el tamaño de fuente correcto no es suficiente. Una ch
unidad es el equivalente al ancho del glifo “0” en la fuente de un elemento. Queremos que el cuerpo del texto sea tan ancho como la ventana gráfica, no estableciendo width: 100%
pero con width: Xch
, donde X
es la cantidad de ch
unidades (o ceros) necesarias para llenar la ventana de visualización horizontalmente.
Encontrar X
, debemos dividir el ancho mínimo de la ventana gráfica, 320 px, por el ch
tamaño en cualquier tamaño de fuente que sea cuando la ventana gráfica tiene 320px de ancho. Eso es 1rem en este caso.
No se preocupe, aquí hay un fragmento para calcular la ch
Talla:
// Returns the width, in pixels, of the "0" glyph of an element at a desired font size
function calculateCh( element, fontSize ) {
const zero = document.createElement( "span" );
zero.innerText = "0";
zero.style.position = "absolute";
zero.style.fontSize = fontSize;
element.appendChild( zero );
const chPixels = zero.getBoundingClientRect().width;
element.removeChild( zero );
return chPixels;
}
Ahora podemos proceder a establecer el ancho del texto:
function calculateCh( element, fontSize ) { ... }
const text = document.querySelector( "p" );
text.style.fontSize = clampBuilder( 320, 960, 1, 3 );
text.style.width = `${ 320 / calculateCh(text, "1rem" ) }ch`;
Umm, ¿quién te invitó a la fiesta, barra de desplazamiento?
Espera. Algo malo sucedio. ¡Hay una barra de desplazamiento horizontal arruinando las cosas!
Cuando hablamos de 320px, estamos hablando del ancho de la ventana gráfica, incluida la barra de desplazamiento vertical. Por lo tanto, el ancho del texto se establece en el ancho del área visible, más el ancho de la barra de desplazamiento, lo que hace que se desborde horizontalmente.
Entonces, ¿por qué no utilizar una métrica que no incluya el ancho de la barra de desplazamiento vertical? No podemos y es por CSS vw
unidad. Recuerde, estamos usando vw
en clamp()
para controlar los tamaños de fuente. Verás, vw
incluye el ancho de la barra de desplazamiento vertical que hace que la fuente se escale a lo largo del ancho de la ventana, incluida la barra de desplazamiento. Si queremos evitar cualquier reflujo, entonces el ancho debe ser proporcional al ancho de la ventana gráfica, incluida la barra de desplazamiento.
¿Asi que que hacemos? Cuando hacemos esto:
text.style.width = `${ 320 / calculateCh(text, "1rem") }ch`;
… Podemos reducir el resultado multiplicándolo por un número menor que 1. 0.9 es suficiente. Eso significa que el ancho del texto será el 90% del ancho de la ventana gráfica, lo que explicará con creces la pequeña cantidad de espacio que ocupa la barra de desplazamiento. Podemos reducirlo usando un número aún menor, como 0.6.
function calculateCh( element, fontSize ) { ... }
const text = document.querySelector( "p" );
text.style.fontSize = clampBuilder( 20, 960, 1, 3 );
text.style.width = `${ 320 / calculateCh(text, "1rem" ) * 0.9 }ch`;
¡Hasta luego, barra de desplazamiento!
Es posible que tenga la tentación de simplemente restar algunos píxeles de 320 para ignorar la barra de desplazamiento, así:
text.style.width = `${ ( 320 - 30 ) / calculateCh( text, "1rem" ) }ch`;
¡El problema con esto es que trae de vuelta el problema del reflujo! Eso es porque restar de 320 rompe la relación de la ventana gráfica a la fuente.
Ancho de la ventana gráfica = 650 px
Ancho de la ventana gráfica = 670 px
El ancho del texto siempre debe ser un porcentaje del ancho de la ventana gráfica. Otra cosa a tener en cuenta es que debemos asegurarnos de que estamos cargando la misma fuente en todos los dispositivos que usan el sitio. Esto suena obvio, ¿no? Bueno, aquí hay un pequeño detalle que podría confundir tu mensaje de texto. Haciendo algo como font-family: sans-serif
no garantizará que se utilice la misma fuente en todos los navegadores. sans-serif
configurará Arial en Chrome para Windows, pero Roboto en Chrome para Android. Además, la geometría de algunas fuentes puede provocar un reflujo incluso si hace todo bien. Las fuentes monoespaciadas tienden a producir los mejores resultados. Así que siempre asegúrese de que sus fuentes estén en el punto.
Vea este ejemplo sin reflujo en la siguiente demostración:
Texto sin reflujo dentro de un contenedor
Todo lo que tenemos que hacer ahora es aplicar el tamaño y el ancho de la fuente al contenedor en lugar de los elementos de texto directamente. El texto que contiene solo deberá configurarse en width: 100%
. Esto no es necesario en los casos de párrafos y encabezados, ya que de todos modos son elementos a nivel de bloque y llenarán el ancho del contenedor automáticamente.
Una ventaja de aplicar esto en un contenedor principal es que sus hijos reaccionarán y cambiarán de tamaño automáticamente sin tener que establecer los tamaños y anchos de fuente uno por uno. Además, si necesitamos cambiar el tamaño de fuente de un solo elemento sin afectar a los demás, todo lo que tendríamos que hacer es cambiar su tamaño de fuente a cualquier em
cantidad y será naturalmente relativo al tamaño de fuente del contenedor.
El texto que no se repite es delicado, pero es un efecto sutil que puede aportar un toque agradable a un diseño.
Terminando
Para rematar, hice una pequeña demostración de cómo podría verse todo esto en un escenario de la vida real.
En este ejemplo final, también puede cambiar el tamaño de la fuente raíz y la clamp()
La función se volverá a calcular automáticamente para que el texto pueda tener el tamaño correcto en cualquier situación.
Aunque el objetivo de este artículo es utilizar clamp()
con tamaños de fuente, esta misma técnica podría usarse en cualquier propiedad CSS que reciba una unidad de longitud. Ahora, no estoy diciendo que debas usar esto en todas partes. Muchas veces, un buen viejo font-size: 1rem
es todo lo que necesitas. Solo intento mostrarte cuánto control puedes tener cuando lo necesitas.
Personalmente yo creo clamp()
es una de las mejores cosas que llegan a CSS y no puedo esperar a ver qué otros usos se le ocurren a la gente a medida que se generaliza cada vez más.