Tutorial Flexbox e Sass Grid: como otimizar o design responsivo
Publicados: 2022-03-11Recentemente, fui desafiado a criar meu próprio sistema de grade e, como reinventar a roda é sempre útil como aprendizado, fui em frente. Eu sabia que seria um desafio interessante, mas fiquei surpreso com o quão fácil acabou sendo!
Neste experimento, veremos os layouts do Flexbox e como eles permitem implementações graciosas de layouts sem fazer nenhum truque maluco. Além disso, se você não estiver familiarizado com o Sass, veremos como ele funciona e usaremos alguns utilitários úteis do Sass. Você pode até aprender algo novo sobre grades CSS como a que faz parte do Bootstrap.
Uma introdução muito curta de Sass e Flexbox
Sass é basicamente uma ferramenta que permite evitar algumas falhas do CSS, é uma linguagem de script que é interpretada para CSS. A sintaxe parece muito familiar se você já estiver escrevendo estilos CSS, mas sua caixa de ferramentas inclui variáveis, mixins para reutilização e diretivas if, for, each e while entre outras. Uma das coisas mais úteis sobre Sass é que qualquer código CSS válido é Sass válido, então você pode transformar progressivamente sua base de código.
Um exemplo simples de um loop for:
@for $i from 1 through 3 { .a-numbered-class-#{$i} { width: (20 * $i) * 1px; } }
Esse loop simples itera de 1 a 3 e cria classes. O índice da iteração será facilmente armazenado em $i
. Também podemos fazer contas e imprimir o .a-numbered-class-X
três vezes com uma largura diferente a cada vez. Este código gera:
.a-numbered-class-1 { width: 20px; } .a-numbered-class-2 { width: 40px; } .a-numbered-class-3 { width: 60px; }
Como podemos ver, podemos abstrair muito do trabalho que você teria que fazer em CSS. Em CSS, você teria que copiar e colar e modificar manualmente, o que obviamente é mais propenso a erros e menos elegante. Se você ainda não experimentou, não perca mais tempo!
Flexbox significa Flexible Box, um sistema de layout CSS3 que posiciona e distribui elementos dinamicamente. É uma ferramenta muito poderosa que permite layouts flexíveis com o mínimo de esforço. Para obter mais detalhes sobre como aprender Flexbox, confira o Guia Completo de Chris Coyier para Flexbox.
A Grade
Passando para a grade propriamente dita, vamos começar com seus elementos básicos. Eles serão inspirados pelos elementos de grade do Bootstrap: Containers, Rows e Columns, cada um contido no primeiro.
Usaremos as convenções de nomenclatura BEM para os nomes das classes. As convenções BEM são bastante simples de usar e adicionam muitas informações sobre o elemento e seu contexto. Resumidamente, você tem:
- Blocos , que “encapsulam uma entidade autônoma que é significativa por si só”:
.block
. - Elementos , que são “partes de um bloco e não têm significado autônomo” que são indicados pelo nome do bloco, dois sublinhados e o elemento:
.block__elem
- Modificadores , como “Sinalizadores em blocos ou elementos”, que são representados com dois traços:
.block .block--mod
.
Containers
Este é o elemento mais externo da grade, ele conterá nossos elementos de linha. Existem dois tipos de contêineres: .container
e .container--fluid
.
O comportamento do .container
é definido por ter 100% da largura abaixo de um determinado ponto, ter uma largura máxima fixa acima dele e ter margens iguais à esquerda e à direita:
$grid__bp-md: 768; .container { max-width: $grid__bp-md * 1px; margin: 0 auto; }
Brinque com isso aqui expandindo e contraindo a janela de “saída”
Para o container fluido, que sempre tem 100% de largura, apenas sobrescrevemos essas propriedades com um modificador:
&--fluid { margin: 0; max-width: 100%; }
Brinque com isso aqui.
Essa foi fácil! Agora temos os dois contêineres implementados. Vamos para o próximo elemento.
Linhas
As linhas serão os organizadores horizontais do nosso conteúdo.
Usaremos o Flexbox para posicionar os elementos filhos de uma linha, fazendo com que eles se envolvam para que não transbordem e dando-lhes 100% de largura dentro da linha (para que possamos aninha-los mais tarde).
&__row { display: flex; flex-wrap: wrap; width: 100%; }
Isso posicionará os elementos filho lado a lado e os envolverá em novas linhas se a soma de sua largura for maior que ela mesma. Agora só precisamos adicionar alguns divs e ficará assim:
Brinque com isso aqui expandindo e contraindo a janela de “saída”.
As coisas estão começando a tomar forma, mas isso ainda não é uma grade CSS. Está faltando…
Colunas
As colunas são onde o conteúdo do site vive. Eles definem em quantas partes a linha é dividida e quanto elas ocupam. Vamos fazer um layout de doze colunas. Isso significa que podemos dividir a linha em uma ou até doze partes.
Para começar, um pouco de matemática básica. Quando queremos ter uma coluna, sua largura deve ser de 100%. Se queremos doze colunas. Então cada um deve ocupar 8,333…% ou 100/12 da largura.
Com o Flexbox, para distribuir conteúdo dessa maneira, podemos usar flex-basis
.
Para dividir em quatro colunas, agora adicionaríamos algo como:
flex-basis: (100 / 4 ) * 1%;
Dessa forma, podemos fazer com que cada elemento ocupe 25% da largura - ou qualquer porcentagem que desejarmos.
Brinque com isso aqui.
Vamos tornar isso mais dinâmico. Como queremos que isso reflita nossas possíveis classes, vamos chamar .col-1
, uma classe para uma coluna div que terá 8,333% da largura, já que doze delas devem caber antes de terem que quebrar para uma nova linha. A porcentagem será incrementada até .col-12
, que ocupará 100%.
$grid__cols: 12; @for $i from 1 through $grid__cols { .col-#{$i} { flex-basis: (100 / ($grid__cols / $i) ) * 1%; } }
Apenas para esclarecer o que está acontecendo, digamos que queremos dividir a largura em quatro partes iguais. Precisaríamos .col-3
, pois cabe 4 vezes em 12, o que significa que .col-3
deve ter 25% de base flexível:
100 / ($grid__cols / $i) 100 / (12 / 3) = 25
Isso já está começando a parecer uma grade!
Brinque com isso aqui.
Colunas dependentes da largura da tela
Agora queremos poder ter um elemento que tenha uma certa largura no celular, mas diferente nos tablets e assim por diante. Usaremos certos pontos de interrupção dependentes da largura da janela. Nossa interface do usuário reagirá a esses pontos de interrupção e se adaptará a um layout ideal para os tamanhos de tela de diferentes dispositivos. Vamos nomear os breakpoints por tamanho: small (sm), medium (md) e assim por diante, .col-sm-12
será um elemento que ocupa pelo menos 12 colunas até o breakpoint sm
.

Vamos renomear a classe .col- .col-*
.col-sm-*
. Como nossa grade será móvel primeiro, aplicaremos suas propriedades a todos os tamanhos de tela. Para aqueles que precisamos nos comportar de forma diferente com telas maiores, adicionaremos a classe: .col-md-*
.
Imagine um elemento com .col-sm-12
e .col-md-4
. O comportamento esperado será que abaixo do breakpoint “md” (médio) ele terá 100% de largura e acima dele terá 33,333% - uma ocorrência muito comum, já que em dispositivos móveis você pode precisar empilhar elementos no topo e não ao lado uns aos outros quando sua largura é muito mais limitada.
Para isso, precisaremos adicionar uma media query (uma expressão que contém código que só será executado acima ou abaixo de uma certa largura ou em um dispositivo específico) no ponto de interrupção e criar nossas colunas md
como fizemos antes para sm
:
@media screen and (min-width: $grid__bp-md * 1px) { @for $i from 1 through $grid__cols { &__col-md-#{$i} { flex-basis: (100 / ($grid__cols / $i) ) * 1%; } } }
Brinque com isso aqui.
Isso já está chegando perto de algo útil. Isso é um pouco MOLHADO (entendeu? Não está SECO…), então vamos torná-lo mais abstrato.
Como vimos, vamos precisar de uma media query para cada breakpoint, então vamos criar um mixin que receba um breakpoint que crie media queries dinamicamente. Pode ser algo assim:
@mixin create-mq($breakpoint) { @if($breakpoint == 0) { @content; } @else { @media screen and (min-width: $breakpoint *1px) { @content; } } }
Agora, vamos apenas agrupar o que tínhamos para criar as classes __col
em um mixin chamado create-col-classes
e usar o mixin create-mq
.
@mixin create-col-classes($modifier, $grid__cols, $breakpoint) { @include create-mq($breakpoint) { @for $i from 1 through $grid__cols { &__col#{$modifier}-#{$i} { flex-basis: (100 / ($grid__cols / $i) ) * 1%; } } } }
E é isso. Para usá-lo, agora definimos nossos pontos de interrupção em um mapa Sass e os iteramos.
$map-grid-props: ('-sm': 0, '-md': $grid__bp-md, '-lg': $grid__bp-lg); @each $modifier , $breakpoint in $map-grid-props { @include create-col-classes($modifier, $grid__cols, $breakpoint); }
Nosso sistema de grade está basicamente pronto! Definimos uma .container__col-sm-*
que será o padrão e podemos modificar seu comportamento em telas maiores com container__col-md-*
e container__col-lg-*
.
Podemos até aninhar linhas! Brinque com isso aqui.
O bom disso é que, se agora quiséssemos ter todos os mesmos pontos de interrupção do Bootstrap v4, precisaríamos apenas fazer:
$grid__bp-sm: 576; $grid__bp-md: 768; $grid__bp-lg: 992; $grid__bp-xl: 1200; $map-grid-props: ( '': 0, '-sm': $grid__bp-sm, '-md': $grid__bp-md, '-lg': $grid__bp-lg, '-xl': $grid__bp-xl );
E é isso! Brinque com isso aqui.
Observe como o Bootstrap adota uma abordagem mobile-first mais completa do que discutimos inicialmente. Os menores tamanhos de janela não possuem sufixo como sm
ou md
, o raciocínio é que a classe equivalente a .container__col-X
não será aplicada apenas de uma largura de janela de 0 a 576px; se não sobrescrevermos explicitamente, será esse número de colunas em cada tamanho de janela. Caso contrário, poderíamos adicionar a classe .container__col-sm-Y
para torná-la uma largura de colunas Y entre os pontos de interrupção sm
.
Compensações
Os deslocamentos adicionarão uma margem à esquerda em relação à coluna anterior. Um .container__col-offset-4
adicionará uma margin-left: 33.333%
em todos os tamanhos de tela. .container__col-md-offset-4
fará o mesmo, mas acima do ponto de interrupção md
.
A implementação agora é trivial; adicionamos uma propriedade -offset
no mesmo loop em que criamos as classes, mas em vez de flex-bases
, escrevemos a propriedade margin-left
. Temos que fazer um extra para -offset-0
também, pois podemos querer limpar a margem em telas maiores:
@mixin create-col-classes($modifier, $grid-cols, $breakpoint) { @include create-mq($breakpoint) { &__col#{$modifier}-offset-0 { margin-left: 0; } @for $i from 1 through $grid-cols { &__col#{$modifier}-#{$i} { flex-basis: (100 / ($grid-cols / $i) ) * 1%; } &__col#{$modifier}-offset-#{$i} { margin-left: (100 / ($grid-cols / $i) ) * 1%; } } } }
Agora temos offsets totalmente funcionais! Brinque com isso aqui.
Exibição
Às vezes, queremos exibir/ocultar um elemento abaixo ou acima de um determinado ponto. Para isso, podemos disponibilizar classes como as do Bootstrap v4.
Por exemplo, a classe .hidden-md-up
ocultará qualquer elemento com esta classe do ponto de interrupção md
para cima; inversamente, .hidden-md-down
irá escondê-lo do ponto de interrupção para baixo.
O código para isso é simples novamente: apenas iteramos nossos pontos de interrupção e criamos uma classe .hidden-*
com um for each
ponto de interrupção. No entanto, modificamos a classe create-mq
para ser um pouco mais abstrata:
@each $modifier , $breakpoint in $map-grid-props { @if($modifier == '') { $modifier: '-xs'; } @include create-mq($breakpoint - 1, 'max') { .hidden#{$modifier}-down { display: none !important; } } @include create-mq($breakpoint, 'min') { .hidden#{$modifier}-up { display: none !important; } } }
Como uma nota lateral, este não é um dos poucos bons usos para !important
? Observe que o elemento pode ter uma especificidade arbitrariamente maior com uma regra de display: block
mas ainda queremos escondê-lo abaixo ou acima do ponto de interrupção. Se você não concorda com essa abordagem, deixe-me saber nos comentários!
Então é isso: agora temos um sistema de exibição.
Brinque com isso aqui.
Conclusão
Embora esse “framework” não esteja pronto para produção, ele mostra o quão poderosos os layouts do Flexbox podem ser e o quão prático é o Sass. Com apenas algumas linhas de código, implementamos a funcionalidade principal de uma estrutura/grade CSS.
Que sirva também de lição que uma versão básica de praticamente qualquer software pode ser implementada com muita facilidade. São os problemas concretos do mundo real que começam a se somar e dificultam.
Eu criei um repositório do GitHub onde você pode enviar problemas ou pull requests.
Quais recursos você gostaria de ver implementados? A implementação poderia ser simplificada ou mais elegante?
Sinta-se à vontade para me dar sua opinião nos comentários abaixo.