Curso de CSS / Layout fluido com min() e clamp()

Um layout fluido permite que as dimensões se adaptem ao espaço disponível enquanto impõe limites úteis. Larguras relativas, max-width e funções como clamp() podem manter o conteúdo legível sem forçar que as mesmas dimensões fixas sejam aplicadas a todas as telas.

Separe largura da viewport e largura disponível

A área onde uma página da web é exibida no navegador é chamada de viewport. No entanto, a largura disponível para cartões dentro do corpo pode ser menor que a do viewport. Isso ocorre porque o cartão deve caber no espaço restante, excluindo a margem do elemento pai, o espaçamento de outras colunas e o limite de largura máxima.

A porcentagem em width: 100% usa a largura da área base ao redor do elemento em um layout de bloco típico. 100vw é baseado na largura do viewport, então mesmo que você o coloque dentro de um pequeno cartão, ele pode ser tão grande quanto a tela inteira. Quando barras de rolagem verticais e margens externas são adicionadas, pode ocorrer transbordamento horizontal. Para a largura dos elementos dentro do corpo, primeiro considere porcentagem ou dimensionamento automático.

Declaração Pai estreito Pai largo Padrão de uso
width: 48rem Pode ser maior que os pais Manter 48rem Não adequado como valor padrão para texto que pode ser reduzido
width: 100% Reduzido para corresponder aos pais Continua a aumentar até a largura dos pais Elementos que precisam preencher o pai
width: 100%; max-width: 48rem Reduzido para menos de 48rem Até 48rem Texto que requer largura de leitura limitada
min-width: 48rem Não reduz abaixo de 48rem Decidido de acordo com diferentes regras de tamanho Use somente quando houver um tamanho mínimo necessário

Limite e centralize a área de conteúdo

*, *::before, *::after { box-sizing: border-box; }
.content {
  width: 100%;
  max-width: 48rem;
  margin-inline: auto;
  padding-inline: 1rem;
}

Com border-box, padding e bordas se encaixam na largura declarada. Se a fonte raiz for 16px e o elemento pai tiver 390px de largura, a largura externa desta caixa de conteúdo é 390px e seu padding de 16px em cada lado deixa 358px para o conteúdo. Com uma largura do elemento pai de 1000px, a largura externa para em 768px e as margens automáticas o centralizam. Para mais detalhes, veja Largura, altura e limites min/max com box-sizing.

Aplicar content-box ao mesmo elemento adicionará padding ao width: 100%, tornando-o mais largo do que seu elemento pai. Por outro lado, margens também não estão incluídas no border-box. Ao especificar width: 100% e margens fixas em ambos os lados simultaneamente, você deve verificar a soma total. Se o min-width e o max-width entrarem em conflito, verifique também min-width, pois o min-width pode fazer com que ele fique maior.

Use min() e clamp()

min() seleciona o menor valor entre os candidatos. width: min(100%, 48rem) usa o menor entre a largura do elemento pai e 48rem, então os limites de 100% e largura máxima especificados anteriormente podem ser expressos em uma única declaração.

.content { width: min(100%, 48rem); }
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); }

clamp(minimum, preferred, maximum) avalia o valor preferido e o limita ao intervalo definido pelo mínimo e pelo máximo; não calcula uma média aritmética. No título acima, rem é baseado no tamanho da fonte raiz e vw é baseado em 1% da largura da viewport. Se o tamanho da raiz é 16px, o mínimo é 24px, o máximo é 40px e o valor preferido é 16px mais 2% da largura da tela.

largura da viewport Cálculo do valor preferido tamanho real do título
320px 16 + 6,4 = 22,4px Mínimo 24px
600px 16 + 12 = 28px 28px
1200px 16 + 24 = 40px 40px
1600px 16 + 32 = 48px Máximo 40px

Se você fizer o texto do corpo depender apenas da largura da tela, ele pode ficar muito pequeno em telas estreitas ou em situações de zoom. Ao usar tamanhos fluidos em títulos, um bom ponto de partida é ter um limite inferior legível e manter o corpo em um tamanho baseado em rem. Incluir rem na expressão não garante automaticamente que a ampliação seja acessível, então você também precisará verificar a ampliação real do navegador e as configurações de fonte padrão. Você pode verificar o papel de cada argumento na definição de clamp() do MDN e na explicação de acessibilidade.

Execute o exemplo de cartão fluido e imagem

Salve o código a seguir como fluid-size.html em UTF-8 e abra-o em seu navegador. A imagem é um dado SVG incluído no arquivo, então não há necessidade de baixar uma imagem separada ou biblioteca externa. Compare a largura do corpo, o número de colunas do cartão e o tamanho do título em uma tela ampla e em uma tela estreita de cerca de 390px. Se você quiser colocar vários cartões de forma flexível, confira Grid: repeat(), auto-fit, auto-fill e áreas para ver como definir a largura mínima das colunas.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>fluid-size.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
.content { width: min(100%, 48rem); margin-inline: auto; padding: 1rem; }
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); line-height: 1.3; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); gap: 1rem; }
.card { min-width: 0; padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.card img { display: block; max-width: 100%; height: auto; margin-bottom: 1rem; }
.token { font-family: monospace; }
</style>
</head>
<body>
<main class="content">
<h1>Size elements to fit the screen and content</h1>
<p>The content is at most 48rem wide and shrinks to fit its parent on smaller screens.</p>
<div class="cards">
  <article class="card"><img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22400%22%20height%3D%22300%22%20viewBox%3D%220%200%20400%20300%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%23287699%22%2F%3E%3Crect%20width%3D%22400%22%20height%3D%2235%22%20fill%3D%22%23205c32%22%2F%3E%3Crect%20y%3D%22265%22%20width%3D%22400%22%20height%3D%2235%22%20fill%3D%22%239e3131%22%2F%3E%3Ccircle%20cx%3D%22200%22%20cy%3D%22150%22%20r%3D%2270%22%20fill%3D%22%23f6ae4c%22%2F%3E%3Crect%20x%3D%2212%22%20y%3D%22120%22%20width%3D%2230%22%20height%3D%2260%22%20fill%3D%22white%22%2F%3E%3Crect%20x%3D%22358%22%20y%3D%22120%22%20width%3D%2230%22%20height%3D%2260%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E" width="400" height="300" alt="Practice drawing with upper and lower bands and center circle"><h2>card with picture</h2><p>The image stays within the card width and preserves its original aspect ratio.</p></article>
  <article class="card"><h2>A heading and description that wrap onto several lines</h2><p>Without a fixed height, the card grows as its text gets longer.</p></article>
  <article class="card"><h2>Long identifier</h2><p class="token">ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</p></article>
</div></main>
</body>
</html>

O minmax(min(100%, 14rem), 1fr) do cartão usa uma largura mínima padrão de 14rem, mas se o elemento pai for mais estreito, o mínimo passa a ser 100% da largura disponível do pai. auto-fit cria tantas colunas quanto couberem, recolhe as faixas vazias e distribui o espaço restante entre as colunas de 1fr. Por causa do padding dentro do cartão, a largura real do texto e das imagens é menor do que a largura externa do cartão. Não julgue o número de colunas pelo nome do dispositivo, mas pela largura disponível para o elemento pai.

min-width: 0 remove uma restrição de tamanho mínimo que poderia impedir que o cartão encolhesse. overflow-wrap: anywhere fornece quebras dentro de uma URL longa ou identificador quando necessário. Eles têm funções diferentes: permitir que a caixa encolha não quebra o texto por si só. A quebra do texto altera a apresentação, não o valor da string copiada.

Teste uma mudança de tamanho de cada vez

  1. Reduza a tela para 390px. O texto deve caber dentro da tela, o cartão deve ficar em posição vertical e as faixas acima e abaixo da imagem e o círculo no centro devem estar todos visíveis.
  2. Altere apenas a largura de .content para 48rem. Após verificar por que o texto está ultrapassando na tela estreita, retorne para min(100%, 48rem).
  3. Altere apenas o valor máximo do título para 3rem e veja a diferença em uma tela mais ampla. Calcule o valor preferido: o novo máximo só muda o resultado quando esse valor supera o limite anterior. Em telas estreitas, o mínimo pode continuar prevalecendo. Depois restaure 2.5rem.
  4. Remova apenas overflow-wrap: anywhere de .card. Verifique se o identificador longo ultrapassa o cartão e restaure a declaração. Experimentos subsequentes para alterar o tamanho da raiz e o zoom do navegador devem ser feitos separadamente, registrando as condições.

Antes de inserir overflow-x: hidden, que oculta o overflow, em toda a página, verifique a largura, margem, largura mínima e string. Se você escondê-lo da vista, o conteúdo ou elementos operacionais podem ser cortados sem resolver a causa. Para os princípios básicos de configuração responsiva, você também pode consultar o guia de design responsivo de MDN.