wandres.dev
ESCALAS FLUIDAS · clamp() y la tipografía que respira

La recta entre dos puntos

Derivar la pendiente y el corte de un clamp con viewport para que la interpolación pase exactamente por los dos tamaños que has decidido, y los tres errores que desalinean la fórmula.

⏱ 17 min

Una escala fluida es una función lineal del ancho del viewport recortada por dos topes. Eso es todo lo que hay. La parte que se hace mal no es la sintaxis de clamp() sino la aritmética: la mayoría de las escalas fluidas que circulan tienen coeficientes copiados de un generador, no coinciden con los tamaños que el diseño pedía en ningún ancho concreto, y los topes recortan antes de tiempo sin que nadie lo note.

🎯 Al terminar esta lección sabrás
  • Enunciar la equivalencia de clamp() con max() y min().
  • Derivar la pendiente y el término constante a partir de dos pares de ancho y tamaño.
  • Comprobar que los topes coinciden con los extremos de la recta.
  • Reconocer los tres errores que desplazan la fórmula del diseño.

Qué hace clamp exactamente

clamp(MIN, PREF, MAX) es idéntico a max(MIN, min(PREF, MAX)). Evalúa los tres, devuelve el preferido si cae dentro del intervalo, y si no devuelve el tope correspondiente. Un detalle de la definición que importa: si MIN es mayor que MAX, gana MIN. No hay error, hay una regla de desempate.

Los tres argumentos son valores CSS completos, así que pueden mezclar unidades y llevar calc() implícito:

.titulo { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); }

El valor preferido, 1rem + 2vw, es una función lineal del ancho del viewport. Escribiéndola con nombres: S = A·R + B·V/100, donde R es el tamaño de fuente de la raíz, V el ancho del viewport en píxeles, A el coeficiente en rem y B el coeficiente en vw. Toda la lección es esa ecuación.

Derivar la recta

Partes de dos decisiones de diseño: cuánto debe medir el texto en el ancho mínimo que te importa y cuánto en el máximo. Llamemos V1 y V2 a esos anchos y S1 y S2 a los tamaños, todos en píxeles.

Una recta que pasa por los dos puntos tiene pendiente (S2 − S1)/(V2 − V1). Como 1vw es una centésima del viewport, el coeficiente en vw es esa pendiente multiplicada por 100:

B = 100 x (S2 - S1) / (V2 - V1)

El término constante sale de sustituir uno de los puntos:

A_px = S1 - B x V1 / 100
A    = A_px / 16

La división final por 16 convierte el término constante de píxeles a rem asumiendo el tamaño de fuente por defecto del navegador, que es exactamente la asunción correcta: los tamaños de diseño se eligieron mirando una pantalla con la configuración por defecto.

Un ejemplo completo. Quieres 18px a 360px de viewport y 28px a 1240px:

B     = 100 x (28 - 18) / (1240 - 360) = 1000/880 = 1.1364 vw
A_px  = 18 - 1.1364 x 360/100 = 18 - 4.0909 = 13.909 px
A     = 13.909 / 16 = 0.8693 rem

Y la comprobación, que hay que hacer siempre:

en 360 px:  13.909 + 1.1364 x 3.60  = 13.909 + 4.091  = 18.00  ✔
en 1240 px: 13.909 + 1.1364 x 12.40 = 13.909 + 14.091 = 28.00  ✔

Los topes son los mismos dos tamaños convertidos a rem: 18/16 = 1.125rem y 28/16 = 1.75rem.

.titulo { font-size: clamp(1.125rem, 0.869rem + 1.136vw, 1.75rem); }

Los topes tienen que coincidir con los extremos

Este es el error número uno de las escalas fluidas ajenas: topes elegidos a ojo que no corresponden al valor de la recta en V1 y V2.

Si el mínimo es mayor que el valor de la recta en V1, el clamp recorta antes de llegar al ancho mínimo y el texto se queda plano en un tramo que creías fluido. Si es menor, la recta sigue bajando por debajo del ancho de diseño y en un móvil estrecho el texto acaba más pequeño de lo que decidiste. Lo mismo por arriba.

El síntoma clásico es un clamp que “no hace nada” en la mayoría de los anchos: la recta está tan inclinada que sale del intervalo enseguida y solo interpola en una franja estrecha en el medio. La comprobación es la aritmética de arriba, y con la fórmula bien derivada sale gratis: los topes son, por construcción, los dos tamaños de diseño.

Los tres errores que desplazan la fórmula

El primero: 100vw no es el ancho del contenido. El viewport incluye la barra de scroll clásica, que en escritorio se lleva unos 15 píxeles. Si diseñaste para una ventana de contenido de 1240 píxeles, el viewport real era 1255 y tu tamaño en ese ancho sale un 1.5% mayor de lo previsto. No es dramático para tipografía, pero explica por qué la comprobación en el navegador nunca da el número redondo. Si necesitas exactitud, mide document.documentElement.clientWidth en lugar de fiarte del ancho de la ventana.

El segundo: redondear los coeficientes. Cortar 1.1364 a 1.14 parece inofensivo y desplaza el valor en el extremo superior: 1.14 x 12.4 = 14.14 frente a 14.09, medio píxel de error a 1240. Con dos puntos separados, el error de redondeo se amplifica en el extremo lejano al que usaste para despejar A. Tres o cuatro decimales cuestan seis caracteres y eliminan el problema.

El tercero: pendientes negativas mal comprobadas. clamp() acepta perfectamente una pendiente negativa, y es lo que quieres para line-height o para letter-spacing, que deben bajar al crecer el tamaño. Pero entonces el valor de la recta en V1 es el mayor de los dos, y por tanto el que corresponde al argumento MAX, no al MIN. Intercambiarlos produce un clamp con el mínimo por encima del máximo, que como vimos devuelve siempre el mínimo: un valor constante que parece que funciona porque es plausible.

💡
Comprueba en tres anchos, no en dos

La aritmética garantiza los extremos. Lo que no garantiza es que el punto medio te guste, y el punto medio es el ancho en el que se ve la mayoría del tráfico. Con la fórmula anterior, a 800px el tamaño es 13.909 + 1.1364 x 8 = 23.0px. Mira ese número antes de dar la escala por buena: si 23px no es el tamaño que habrías elegido para una pantalla de 800, tus dos puntos de anclaje están mal elegidos aunque la recta sea impecable.

Un font-size fluido convierte la tipografía en una dependencia del layout

Hay una consecuencia de meter vw en un font-size que casi nadie traza hasta el final, y es la que produce los bugs raros. En cuanto el tamaño de fuente depende del viewport, todo lo que dependa del tamaño de fuente hereda esa dependencia: los padding en em de los botones, las medidas de línea en ch, las separaciones en lh, las alturas mínimas en cap, los umbrales de las consultas de contenedor que hayas escrito en em. Una cadena que era estática pasa a recalcularse en cada píxel de resize, y aparecen efectos de segundo orden: un contenedor cuyo ancho depende de una medida en ch que depende de un font-size que depende del ancho del viewport, es decir, una realimentación que el motor resuelve pero que tú ya no puedes predecir mentalmente. No es un argumento para no usar tipografía fluida; es un argumento para acotar dónde entra el vw. La disciplina que funciona es que el vw aparezca únicamente en un puñado de custom properties de la raíz que definen la escala, y que absolutamente todo lo demás se exprese en rem, em o ch respecto a esa escala. Así hay un solo punto en toda la hoja donde el viewport entra en el sistema tipográfico, y cuando algo se comporte de forma extraña sabes exactamente dónde mirar.