Tipografía sensible a los fluidos con CSS Poly Fluid Sizing
Publicado: 2022-03-10En este artículo, vamos a llevarlo a otro nivel. Vamos a examinar cómo crear una tipografía fluida y escalable a través de múltiples puntos de interrupción y tamaños de fuente predefinidos utilizando funciones de navegador bien compatibles y algo de álgebra básica. La mejor parte es que puedes automatizarlo todo usando Sass.
Lectura adicional en SmashingMag:
- Tipografía verdaderamente fluida con unidades vh y vw
- Patrones tipográficos en el diseño de boletines de correo electrónico HTML
- Lo bueno, lo malo y los grandes ejemplos de tipografía web
- Herramientas y recursos para una tipografía web más significativa
Cuando se trabaja con diseñadores creativos en diseños de páginas web, es bastante común recibir múltiples diseños/mesas de trabajo de Sketch o Photoshop, una para cada punto de interrupción. En ese diseño, los elementos (como un encabezado h1
) generalmente tendrán diferentes tamaños en cada punto de interrupción. Por ejemplo:
- El
h1
en el diseño pequeño podría ser22px
- El
h1
en el diseño medio podría ser24px
- El
h1
en el diseño grande podría ser34px
El CSS mínimo para esto utiliza consultas de medios:
h1 { font-size: 22px; } @media (min-width:576px) { h1 { font-size: 22px; } } @media (min-width:768px) { h1 { font-size: 24px; } } @media (min-width:992px) { h1 { font-size: 34px; } }

Este es un buen primer paso, pero está limitando el font-size
solo a lo que especificó el diseñador en los puntos de interrupción proporcionados. ¿Qué diría el diseñador si le preguntara: "¿Cuál debería ser el font-size
en una ventana gráfica de 850 px de ancho?" La respuesta en la mayoría de los casos es que estaría entre 24px y 34px. Pero en este momento, son solo 24 px de acuerdo con su CSS, que probablemente no sea lo que el diseñador estaba imaginando.
Su opción en este punto es calcular cuál debería ser ese tamaño y agregar otro punto de interrupción. Eso es bastante fácil. Pero, ¿qué pasa con todas las demás resoluciones? ¿Cuál debería ser el font-size
en 800px de ancho? ¿Qué pasa con 900px? ¿Qué pasa con 935px? Obviamente, el diseñador no le proporcionará un diseño completo para cada resolución posible. Incluso si lo hicieran, ¿debería agregar docenas (o cientos) de puntos de interrupción para todos los diferentes font-sizes
que desea el diseñador? Por supuesto que no.
Su diseño ya se está escalando de manera fluida con el ancho de su ventana gráfica. ¿No sería bueno si su tipografía se escalara de manera predecible con su diseño fluido? ¿Qué más podemos hacer para mejorar esto?
¿Unidades de ventana al rescate?
Las unidades de ventana gráfica son otro paso en la dirección correcta. Permiten que su texto cambie de tamaño de manera fluida con sus diseños. Y el soporte del navegador es excelente en estos días.

Pero la viabilidad de las unidades Viewport depende en gran medida de los diseños creativos originales de una página web. Sería genial configurar el font-size
usando vw
y listo:
h1 { font-size: 2vw; }

Pero esto solo funciona si sus mesas de trabajo creativas lo tienen en cuenta. ¿Eligió el diseñador un tamaño de texto que fuera exactamente el 2 % del ancho de cada una de sus mesas de trabajo? Por supuesto que no. Calculemos cuál debería ser el valor vw
para cada uno de nuestros puntos de interrupción:
Tamaño de 22 px a 576 576px
de ancho = 22 ⁄ 576 * 100 = 3,82 22px
Tamaño de 24 px a 24px
768px
ancho = 24 ⁄ 768 * 100 = 3,13 34px
Tamaño de 34 px a 992 992px
ancho = 34 ⁄ 992 * 100 = 3,43 vw
Están cerca pero no son todos iguales. Por lo tanto, aún necesitaría usar consultas de medios para hacer la transición entre los tamaños de texto y aún habría saltos. Y considere este extraño efecto secundario:
@ 767 px, el 3,82 % del ancho de la ventana gráfica es 29 px. Si la ventana gráfica es 1 píxel más ancha, el font-size
vuelve a caer repentinamente a 24 píxeles . Esta animación de una ventana gráfica que cambia de tamaño demuestra este efecto secundario no deseado:

Este cambio dramático en el tamaño de la fuente definitivamente no es lo que el diseñador estaba imaginando. Entonces, ¿cómo resolvemos este problema?
¿Regresión lineal estadística?
Esperar. ¿Qué? Sí, este es un artículo sobre CSS, pero algunas matemáticas básicas pueden ser de gran ayuda para encontrar una solución elegante a nuestro problema.
Primero, tracemos nuestras resoluciones y los tamaños de texto correspondientes en un gráfico:

font-size
y el ancho correspondiente de la ventana gráfica (Hojas de cálculo de Google) (Ver versión grande) Aquí puede ver un diagrama de dispersión de los tamaños de texto especificados por el diseñador en los anchos de ventana definidos. El eje x es el ancho de la ventana gráfica y el eje y es el font-size
. ¿Ves esa línea? Eso se llama línea de tendencia . Es una forma de encontrar un valor de font-size
interpolado para cualquier ancho de ventana gráfica, según los datos proporcionados.
La línea de tendencia es la clave de todo esto
Si pudiera establecer su font-size
acuerdo con esta línea de tendencia, tendría un h1 que se escala sin problemas en todas las resoluciones que se acercarían a lo que pretendía el diseñador. Primero, veamos las matemáticas. La recta se define por esta ecuación:

- m = pendiente
- b = el intercepto en y
- x = el ancho de la ventana gráfica actual
- y = el
font-size
resultante
Hay varios métodos para determinar la pendiente y el intercepto en y. Cuando hay varios valores involucrados, un método común es el ajuste por mínimos cuadrados:

Una vez que ejecuta esos cálculos, tiene su ecuación de línea de tendencia.
¿Cómo uso esto en CSS?
Bueno, esto se está poniendo bastante pesado en las matemáticas. ¿Cómo usamos realmente estas cosas en el desarrollo web front-end? ¡La respuesta es CSS calc()
! Una vez más, una tecnología CSS bastante nueva que está muy bien soportada.

Puede usar la ecuación de la línea de tendencia de esta manera:
h1 { font-size: calc({slope}*100vw + {y-intercept}px); }
Una vez que encuentre su pendiente y su intersección en Y, ¡simplemente conéctelos!
Nota: debe multiplicar la pendiente por 100
, ya que la está utilizando como una unidad vw
, que es 1/100 del ancho de la ventana gráfica.
¿Se puede automatizar esto?
Porté el método de ajuste de mínimos cuadrados a una función Sass fácil de usar:
/// least-squares-fit /// Calculate the least square fit linear regression of provided values /// @param {map} $map - A Sass map of viewport width and size value combinations /// @return Linear equation as a calc() function /// @example /// font-size: least-squares-fit((576px: 24px, 768px: 24px, 992px: 34px)); /// @author Jake Wilson <[email protected]> @function least-squares-fit($map) { // Get the number of provided breakpoints $length: length(map-keys($map)); // Error if the number of breakpoints is < 2 @if ($length < 2) { @error "leastSquaresFit() $map must be at least 2 values" } // Calculate the Means $resTotal: 0; $valueTotal: 0; @each $res, $value in $map { $resTotal: $resTotal + $res; $valueTotal: $valueTotal + $value; } $resMean: $resTotal/$length; $valueMean: $valueTotal/$length; // Calculate some other stuff $multipliedDiff: 0; $squaredDiff: 0; @each $res, $value in $map { // Differences from means $resDiff: $res - $resMean; $valueDiff: $value - $valueMean; // Sum of multiplied differences $multipliedDiff: $multipliedDiff + ($resDiff * $valueDiff); // Sum of squared resolution differences $squaredDiff: $squaredDiff + ($resDiff * $resDiff); } // Calculate the Slope $m: $multipliedDiff / $squaredDiff; // Calculate the Y-Intercept $b: $valueMean - ($m * $resMean); // Return the CSS calc equation @return calc(#{$m*100}vw + #{$b}); }
¿Esto realmente funciona? Abre este CodePen y cambia el tamaño de la ventana de tu navegador. ¡Funciona! Los tamaños de fuente son bastante parecidos a los que pedía el diseño original y se escalan sin problemas con su diseño.

Usuario de prueba SCSS de ajuste de mínimos cuadrados = "jakobud"] Vea la prueba SCSS de ajuste de mínimos cuadrados de pluma por Jake Wilson (@jakobud) en CodePen.
Ahora, hay que reconocer que no es perfecto. Los valores están cerca del diseño original pero no coinciden del todo. Esto se debe a que una línea de tendencia lineal es una aproximación de tamaños de fuente específicos en anchos de ventana específicos. Esto es heredado de la regresión lineal. Siempre hay algún error en sus resultados. Es una compensación entre simplicidad y precisión. Además, tenga en cuenta que cuanto más variados sean los tamaños de texto, más error habrá en su línea de tendencia.
¿Podemos hacerlo mejor que esto?
Ajuste de mínimos cuadrados polinómicos
Para obtener una línea de tendencia más precisa, debe buscar temas más avanzados, como una línea de tendencia de regresión polinomial que podría verse así:

Ahora que es más como él! Mucho más precisa que nuestra línea recta. Una ecuación básica de regresión polinomial se ve así:

Cuanto más precisa desee su curva, más complicada se vuelve la ecuación. Desafortunadamente, no puedes hacer esto en CSS . calc()
simplemente no puede hacer este tipo de matemática avanzada. Específicamente, no puedes calcular exponentes:
font-size: calc(3vw * 3vw); /* This doesn't work in CSS */
Por lo tanto, hasta que calc()
admita este tipo de matemáticas no lineales, solo nos quedaremos con ecuaciones lineales . ¿Hay algo más que podamos hacer para mejorar esto?
Puntos de ruptura y ecuaciones lineales múltiples
¿Qué pasaría si solo estuviéramos calculando una línea recta entre cada par de puntos de ruptura? Algo como esto:

Así que en este ejemplo calcularíamos la recta entre 22px
y 24px
y luego otra entre 24px
y 34px
. El Sass se vería así:
// SCSS h1 { @media (min-width:576px) { font-size: calc(???); } @media (min-width:768px) { font-size: calc(???); } }
Podríamos usar el método de ajuste de mínimos cuadrados para esos valores calc()
pero dado que es solo una línea recta entre 2 puntos, las matemáticas podrían simplificarse mucho. ¿Recuerdas la ecuación de una línea recta?

Como ahora estamos hablando de solo 2 puntos, encontrar la pendiente (m) y la intersección con el eje y (b) es trivial:

Aquí hay una función Sass para esto:
/// linear-interpolation /// Calculate the definition of a line between two points /// @param $map - A Sass map of viewport widths and size value pairs /// @returns A linear equation as a calc() function /// @example /// font-size: linear-interpolation((320px: 18px, 768px: 26px)); /// @author Jake Wilson <[email protected]> @function linear-interpolation($map) { $keys: map-keys($map); @if (length($keys) != 2) { @error "linear-interpolation() $map must be exactly 2 values"; } // The slope $m: (map-get($map, nth($keys, 2)) - map-get($map, nth($keys, 1)))/(nth($keys, 2) - nth($keys,1)); // The y-intercept $b: map-get($map, nth($keys, 1)) - $m * nth($keys, 1); // Determine if the sign should be positive or negative $sign: "+"; @if ($b < 0) { $sign: "-"; $b: abs($b); } @return calc(#{$m*100}vw #{$sign} #{$b}); }
Ahora, solo use la función de interpolación lineal en múltiples puntos de interrupción en su Sass. Además, agreguemos algunos font-sizes
mínimos y máximos:
// SCSS h1 { // Minimum font-size font-size: 22px; // Font-size between 576 - 768 @media (min-width:576px) { $map: (576px: 22px, 768px: 24px); font-size: linear-interpolation($map); } // Font-size between 768 - 992 @media (min-width:768px) { $map: (768px: 24px, 992px: 34px); font-size: linear-interpolation($map); } // Maximum font-size @media (min-width:992px) { font-size: 34px; } }
Y genera este CSS:
h1 { font-size: 22px; } @media (min-width: 576px) { h1 { font-size: calc(1.04166667vw + 16px); } } @media (min-width: 768px) { h1 { font-size: calc(4.46428571vw - 10.28571429px); } } @media (min-width: 992px) { h1 { font-size: 34px; } }

¿El Santo Grial del tamaño CSS?
Envolvamos todo esto en una buena mezcla de Sass (¡para los perezosos y eficientes!). Estoy acuñando este método Poly Fluid Sizing :
/// poly-fluid-sizing /// Generate linear interpolated size values through multiple break points /// @param $property - A string CSS property name /// @param $map - A Sass map of viewport unit and size value pairs /// @requires function linear-interpolation /// @requires function map-sort /// @example /// @include poly-fluid-sizing('font-size', (576px: 22px, 768px: 24px, 992px: 34px)); /// @author Jake Wilson <[email protected]> @mixin poly-fluid-sizing($property, $map) { // Get the number of provided breakpoints $length: length(map-keys($map)); // Error if the number of breakpoints is < 2 @if ($length < 2) { @error "poly-fluid-sizing() $map requires at least values" } // Sort the map by viewport width (key) $map: map-sort($map); $keys: map-keys($map); // Minimum size #{$property}: map-get($map, nth($keys,1)); // Interpolated size through breakpoints @for $i from 1 through ($length - 1) { @media (min-width:nth($keys,$i)) { $value1: map-get($map, nth($keys,$i)); $value2: map-get($map, nth($keys,($i + 1))); // If values are not equal, perform linear interpolation @if ($value1 != $value2) { #{$property}: linear-interpolation((nth($keys,$i): $value1, nth($keys,($i+1)): $value2)); } @else { #{$property}: $value1; } } } // Maxmimum size @media (min-width:nth($keys,$length)) { #{$property}: map-get($map, nth($keys,$length)); } }
Esta combinación de Sass requiere algunas funciones de Sass en las siguientes esencias de Github:
- Interpolación linear
- ordenación de mapa
- ordenar por lista
- lista-eliminar
El mixin poly-fluid-sizing()
realizará una interpolación lineal en cada par de anchos de ventana gráfica y establecerá un tamaño mínimo y máximo. Puede importar esto en cualquier proyecto de Sass y utilizarlo fácilmente sin necesidad de saber nada de las matemáticas detrás de él. Aquí está el CodePen final que usa este método.
Dimensionamiento de fluidos polivinílicos con ecuaciones lineales, unidades de ventana gráfica y calc() user="jakobud"]Consulte Tamaño de fluidos poligonales con ecuaciones lineales, unidades de ventana gráfica y calc()"] Dimensionamiento de fluidos polivinílicos con ecuaciones lineales, unidades de ventana gráfica y calc() por Jake Wilson (@jakobud) en CodePen.
Algunas notas
- Obviamente, este método se aplica no solo al
font-size
sino también a cualquier propiedad de unidad/longitud (margin
,padding
, etc.). Pasas el nombre de la propiedad deseada al mixin como una cadena. - El mapa de Sass de los pares de valores de ancho + tamaño de la ventana gráfica se puede pasar en cualquier orden a la
poly-fluid-sizing()
. Ordenará automáticamente el mapa según el ancho de la ventana gráfica de menor a mayor . Así que podrías pasar un mapa como este y funcionaría bien:
$map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px); @include poly-fluid-sizing('font-size', $map);
- Una limitación de este método es que no puede pasar unidades mixtas al mixin. Por ejemplo,
3em
@576px
ancho. Sass simplemente no sabrá realmente qué hacer matemáticamente allí.
Conclusión
Es esto lo mejor que podemos hacer? ¿Es Poly Fluid Sizing el Santo Grial del dimensionamiento de unidades de fluidos en CSS? Quizás. CSS actualmente es compatible con la animación no lineal y las funciones de tiempo de transición, por lo que tal vez exista la posibilidad de que calc()
también lo admita algún día. Si eso sucede, la regresión polinomial no lineal podría valer la pena volver a mirarla. Pero tal vez no... La escala lineal podría ser superior de todos modos.
Empecé a explorar esta idea a principios de 2017 y finalmente desarrollé la solución anterior. Desde entonces, he visto algunos desarrolladores con ideas similares y diferentes piezas de este rompecabezas. Pensé que era hora de compartir mi método y cómo llegué allí. Unidades de ventana gráfica. Calc(). Hablar con descaro a. Puntos de ruptura. Ninguna de estas cosas son nuevas. Todas son características del navegador que existen desde hace años (con diversos grados de compatibilidad). Solo los he usado juntos de una manera que aún no había sido explorada por completo. Nunca tenga miedo de mirar las herramientas que usa todos los días y pensar de manera innovadora sobre cómo puede utilizarlas mejor y aumentar su conjunto de habilidades.