Tipografia responsiva a fluidos com dimensionamento de fluido CSS Poly
Publicados: 2022-03-10Neste artigo, vamos levá-lo para outro nível. Vamos examinar como criar tipografia fluida e escalável em vários pontos de interrupção e tamanhos de fonte predefinidos usando recursos de navegador bem suportados e algumas álgebra básica. A melhor parte é que você pode automatizar tudo usando Sass.
Leitura adicional no SmashingMag:
- Tipografia verdadeiramente fluida com unidades vh e vw
- Padrões tipográficos no design de newsletter de e-mail HTML
- Os bons, os maus e os grandes exemplos de tipografia na web
- Ferramentas e recursos para uma tipografia web mais significativa
Ao trabalhar com designers criativos em designs de páginas da Web, é bastante comum receber várias pranchetas/layouts do Sketch ou do Photoshop, uma para cada ponto de interrupção. Nesse design, os elementos (como um título h1
) geralmente terão tamanhos diferentes em cada ponto de interrupção. Por exemplo:
- O
h1
no layout pequeno pode ser22px
- O
h1
no layout médio pode ser de24px
- O
h1
no layout grande pode ser34px
O CSS mínimo para isso usa consultas de mídia:
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 é um bom primeiro passo, mas você está limitando o font-size
apenas ao que foi especificado pelo designer nos pontos de interrupção fornecidos. O que o designer diria se você perguntasse: “Qual deve ser o font-size
em uma janela de visualização de 850px de largura?” A resposta na maioria dos casos é que seria algo entre 24px e 34px. Mas agora, são apenas 24px de acordo com seu CSS, o que provavelmente não é o que o designer estava imaginando.
Sua opção neste momento é calcular qual deve ser esse tamanho e adicionar outro ponto de interrupção. Isso é bastante fácil. Mas e todas as outras resoluções? Qual deve ser o font-size
em 800px de largura? E quanto a 900px? E quanto a 935px? Obviamente, o designer não fornecerá um layout completo para todas as resoluções possíveis. Mesmo se o fizessem, você deveria adicionar dezenas (ou centenas) de pontos de interrupção para todos os diferentes font-sizes
desejados pelo designer? Claro que não.
Seu layout já está dimensionando de forma fluida com a largura da sua janela de visualização. Não seria bom se sua tipografia fosse dimensionada de forma previsível com seu layout fluido? O que mais podemos fazer para melhorar isso?
Unidades de viewport para o resgate?
As unidades de viewport são outro passo na direção certa. Eles permitem que seu texto seja redimensionado com fluidez com seus layouts. E o suporte do navegador é ótimo hoje em dia.
Mas a viabilidade das unidades Viewport depende muito dos designs criativos originais de uma página da web. Seria ótimo apenas definir o font-size
usando vw
e pronto:
h1 { font-size: 2vw; }
Mas isso só funciona se suas pranchetas criativas levarem isso em consideração. O designer escolheu um tamanho de texto que fosse exatamente 2% da largura de cada uma de suas pranchetas? Claro que não. Vamos calcular qual seria o valor de vw
para cada um dos nossos pontos de interrupção:
Tamanho de 22px
@ 576px
de largura = 22 ⁄ 576 *100 = 24px
Tamanho de 24px @ 768px
de largura = 24 ⁄ 768 *100 = 34px
Tamanho de 34px @ 992px
de largura = 34 ⁄ 992 *100 = 3,43vw
Eles são próximos, mas não são todos iguais. Portanto, você ainda precisaria usar consultas de mídia para fazer a transição entre tamanhos de texto e ainda haveria saltos. E considere este estranho efeito colateral:
@ 767px, 3,82% da largura da janela de visualização é 29px. Se a janela de visualização for 1 pixel mais larga, o font-size
cairá repentinamente para 24px . Esta animação de uma viewport sendo redimensionada demonstra esse efeito colateral indesejável:
Essa mudança dramática no tamanho da fonte quase definitivamente não é o que o designer estava imaginando. Então, como resolvemos esse problema?
Regressão linear estatística?
Esperar. Que? Sim, este é um artigo sobre CSS, mas um pouco de matemática básica pode ajudar bastante em uma solução elegante para o nosso problema.
Primeiro, vamos plotar nossas resoluções e tamanhos de texto correspondentes em um gráfico:
Aqui você pode ver um gráfico de dispersão dos tamanhos de texto especificados pelo designer nas larguras da janela de visualização definidas. O eixo x é a largura da janela de visualização e o eixo y é o font-size
. Vê aquela linha? Isso é chamado de linha de tendência . É uma maneira de encontrar um valor font-size
interpolado para qualquer largura de janela de visualização, com base nos dados fornecidos.
A linha de tendência é a chave para tudo isso
Se você pudesse definir o font-size
acordo com essa linha de tendência, você teria um h1 que escala suavemente em todas as resoluções que se aproximassem de corresponder ao que o designer pretendia. Primeiro, vamos olhar para a matemática. A linha reta é definida por esta equação:
- m = inclinação
- b = a interceptação em y
- x = a largura atual da janela de visualização
- y = o
font-size
resultante
Existem vários métodos para determinar a inclinação e a interceptação em y. Quando vários valores estão envolvidos, um método comum é o ajuste dos mínimos quadrados:
Depois de executar esses cálculos, você tem sua equação de linha de tendência.
Como faço para usar isso em CSS?
Ok, isso está ficando muito pesado na matemática. Como realmente usamos essas coisas no desenvolvimento web front-end? A resposta é CSS calc()
! Mais uma vez, uma tecnologia CSS relativamente nova que é muito bem suportada.
Você pode usar a equação da linha de tendência assim:
h1 { font-size: calc({slope}*100vw + {y-intercept}px); }
Depois de encontrar sua inclinação e interceptação em y, basta conectá-los!
Nota: Você tem que multiplicar a inclinação por 100
, já que a está usando como uma unidade vw
que é 1/100 da largura da Viewport.
Isso pode ser automatizado?
Eu portei o método de ajuste de mínimos quadrados para uma função 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}); }
Isso realmente funciona? Abra este CodePen e redimensione a janela do seu navegador. Funciona! Os tamanhos de fonte são bastante próximos do que o design original estava pedindo e se ajustam suavemente ao seu layout.
Agora, reconhecidamente, não é perfeito. Os valores estão próximos do projeto original, mas não coincidem. Isso ocorre porque uma linha de tendência linear é uma aproximação de tamanhos de fonte específicos em larguras de janela de visualização específicas. Isso é herdado da regressão linear. Sempre há algum erro em seus resultados. É uma troca de simplicidade versus precisão. Além disso, lembre-se de que quanto mais variados forem os tamanhos de texto, mais erros haverá em sua linha de tendência.
Podemos fazer melhor do que isso?
Ajuste de Mínimos Quadrados Polinomial
Para obter uma linha de tendência mais precisa, você precisa examinar tópicos mais avançados, como uma linha de tendência de regressão polinomial que pode ser algo assim:
Agora é mais assim ! Muito mais preciso que nossa linha reta. Uma equação básica de regressão polinomial se parece com isso:
Quanto mais precisa você quiser sua curva, mais complicada será a equação. Infelizmente, você não pode fazer isso em CSS . calc()
simplesmente não pode fazer esse tipo de matemática avançada. Especificamente, você não pode calcular expoentes:
font-size: calc(3vw * 3vw); /* This doesn't work in CSS */
Portanto, até que calc()
suporte esse tipo de matemática não linear, estamos presos apenas a equações lineares . Há mais alguma coisa que possamos fazer para melhorar isso?
Pontos de interrupção e várias equações lineares
E se estivéssemos calculando apenas uma linha reta entre cada par de pontos de interrupção? Algo assim:
Então, neste exemplo, calcularíamos a linha reta entre 22px
e 24px
e depois outra entre 24px
e 34px
. O Sas ficaria assim:
// SCSS h1 { @media (min-width:576px) { font-size: calc(???); } @media (min-width:768px) { font-size: calc(???); } }
Poderíamos usar o método de ajuste de mínimos quadrados para esses valores calc()
, mas como é apenas uma linha reta entre 2 pontos, a matemática pode ser bastante simplificada. Lembre-se da equação para uma linha reta?
Como estamos falando de apenas 2 pontos agora, encontrar a inclinação (m) e a interceptação em y (b) é trivial:
Aqui está uma função Sass para isso:
/// 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}); }
Agora, basta usar a função de interpolação linear em vários pontos de interrupção em seu Sass. Além disso, vamos lançar alguns font-sizes
mínimo e máximo:
// 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; } }
E gera 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; } }
O Santo Graal do dimensionamento CSS?
Vamos encerrar tudo isso em um bom mixin Sass (para os preguiçosos e eficientes!). Estou criando 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)); } }
Este mixin Sass requer algumas funções Sass nas seguintes gists do Github:
- interpolação linear
- classificação de mapa
- lista de classificação
- lista-remover
O poly-fluid-sizing()
executará a interpolação linear em cada par de larguras de viewport e definirá um tamanho mínimo e máximo. Você pode importar isso para qualquer projeto Sass e utilizá-lo facilmente sem precisar saber nada da matemática por trás dele. Aqui está o CodePen final que usa esse método.
Algumas notas
- Obviamente, esse método se aplica não apenas ao
font-size
mas a qualquer propriedade de unidade/comprimento (margin
,padding
, etc). Você passa o nome da propriedade desejada no mixin como uma string. - O mapa Sass dos pares de valores de largura + tamanho da janela de visualização pode ser passado em qualquer ordem para o
poly-fluid-sizing()
. Ele classificará automaticamente o mapa de acordo com a largura da janela de visualização do menor para o maior . Então você poderia passar um mapa como este e funcionaria bem:
$map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px); @include poly-fluid-sizing('font-size', $map);
- Uma limitação para este método é que você não pode passar unidades mistas para o mixin. Por exemplo,
3em
@576px
largura. Sass simplesmente não saberá o que fazer matematicamente lá.
Conclusão
Isto é o melhor que podemos fazer? O dimensionamento de fluidos poli é o Santo Graal do dimensionamento de unidades de fluidos em CSS? Pode ser. CSS atualmente suporta animações não lineares e funções de tempo de transição, então talvez haja uma chance de que calc()
também o suporte algum dia. Se isso acontecer, a regressão polinomial não linear pode valer a pena dar uma olhada novamente. Mas talvez não… A escala linear pode ser superior de qualquer maneira.
Comecei a explorar essa ideia no início de 2017 e acabei desenvolvendo a solução acima. Desde então, tenho visto alguns desenvolvedores terem ideias semelhantes e peças diferentes desse quebra-cabeça. Achei que era hora de compartilhar meu método e como cheguei lá. Unidades de viewport. Calc(). Sass. Pontos de interrupção. Nenhuma dessas coisas é nova. Eles são todos os recursos do navegador que existem há anos (com vários graus de suporte). Eu só os usei juntos de uma maneira que ainda não havia sido totalmente explorada. Nunca tenha medo de olhar para as ferramentas que você usa todos os dias e pensar fora da caixa sobre como você pode utilizá-las melhor e aumentar seu conjunto de habilidades.