#1 - CSS Grid Item Placement: Meu primeiro desafio frontend

Recentemente, terminei meu primeiro desafio do Frontend Mentor. Como estou começando, escolhi aquele que me pareceu o mais fácil. Ainda assim, aprendi bastante com ele e estou aqui para compartilhar um pouco do que aprendi. Se você quiser entender um pouco melhor como funciona o algoritmo de posicionamento em grids, espero que esse conteúdo lhe seja útil.

O desafio

O desafio escolhido foi o Stats preview card component[1]. Utilizei HTML e CSS básicos, mobile-first e o layout foi criado com CSS Grid.

Como ambiente de desenvolvimento, utilizei o CodeSandbox [2]. É uma boa alternativa se você não quer configurar um novo ambiente. Além disso, você pode conectar um repositório GitHub e realizar commits diretamente da sua sandbox.

Você pode acessar meu repositório e o resultado final.

O layout

O grid criado para o layout foi bastante simples: 1x2 (1 coluna x 2 linhas) para o layout mobile e 2x1 (2 colunas x 1 linha) para o layout desktop. No layout mobile, o primeiro grid item (célula) é uma imagem e o segundo é um conteúdo de texto. No layout desktop é o inverso: o primeiro grid item é o conteúdo de texto e o segundo é a imagem. A imagem abaixo ilustra os layouts.

Layouts mobile e desktop

O desafio

O problema a ser resolvido então era simples: trocar as posições dos itens do grid de acordo com layout a ser apresentado. Ou, em outras palavras, especificar, de forma explícita, a posição, de cada célula. Dá pra fazer isso em CSS? Claro que sim!

Buscando uma solução, me deparei com uma que resolveu o meu problema de forma bem direta. Bastou adicionar a seguinte declaração ao item de conteúdo de texto: grid-row: 1.

Aqui é a hora em que podemos ficar tentados a continuar em frente sem entender muito bem o que está acontecendo. Mas acho que o melhor a se fazer nesses casos é focar nos fundamentos e entender, mesmo que superficialmente, o que acontece atrás das cortinas.

Começando a entender

A propriedade grid-row é na verdade um atalho para as propriedades grid-row-start e grid-row-end, que definem, respectivamente, a linha de início e de fim de um item do grid. Também existem as propriedades grid-column-start e grid-column-end que fazem o mesmo para as colunas de início e fim. Essas propriedades são bem explicadas em um artigo do CSS Tricks [3].

Numeração de linhas e colunas em um grid
Source: Mozilla Developer Network [4]

Assim, grid-row: 1 é o mesmo que:

{
    grid-row-start: 1;
    grid-row-end: auto;
} 

Mas, um momento... No layout de desktop, o item de texto já começava na primeira linha, bem como o item de imagem; a única coisa que mudou foi que eu defini a linha de início de forma explícita (mantendo o mesmo valor). Isso me deixou com duas dúvidas:

  1. Porque a ordem dos itens mudou se eu especifiquei a mesma linha que o item já ocupava?
  2. Se eu estou querendo trocar a ordem de duas colunas, porque estou usando uma regra que define a linha e não a coluna do item?

O algoritmo de posicionamento

Quando você cria um grid, pode especificar, usando as regras acima, a posição de cada item do grid. Mas, comumente, não fazemos isso e especificamos a posição de apenas alguns itens (ou de nenhum). Nesse caso, deixamos que um algoritmo [5] determine automaticamente a posição de cada item que não teve uma posição explicitamente especificada (chamados de itens auto placed)[6].

Explicando bem por alto, o algoritmo reserva os espaços especificados dos itens que definiram suas posições e organiza os demais itens nos espaços que sobraram. Para isso, ele mantém um cursor da sua posição atual (linha e coluna) e leva em consideração propriedades como grid-auto-flow [7] que podem alterar a ordem dos items.

Vale notar que o algoritmo pode criar colunas e linhas adicionais se achar necessário. Assim, existe o explicit grid - o grid que você cria explicitamente com, por exemplo, grid-template-columns e grid-template-rows - e o implicit grid - que o algoritmo cria quando houver necessidade.

Ao especificar explicitamente a linha do item de texto com a declaração grid-row: 1, temos a certeza de que ele necessariamente estará na primeira linha. O mesmo também é verdade quando eu especifico uma coluna. Entretanto, nesse caso, eu não especifiquei a coluna e ainda assim a ordem das colunas foi alterada. A primeira dúvida persiste.

E quanto à segunda dúvida? Por que não usar uma propridade que atue sobre a coluna do item se quero trocar a ordem das colunas? Faria mais sentido, não? Resolvi então substituir grid-row: 1 por grid-column-start: 1, mas o resultado não foi o que eu esperava.

Itens do grid na ordem errada

Eu esperava que o resultado fosse o mesmo ao usar grid-row-start: 1 já que eu estou, de igual forma, definindo explicitamente a posição do item de texto. Ele até foi para a primeira coluna, como deveria, mas agora ocupando a segunda linha; o algoritmo criou um implicit grid.

Continuando a experimentar, resolvi definir explicitamente também a coluna que o item de imagem deveria ocupar. Agora, um item está na coluna 1 e outro na coluna 2.

#text {
    grid-column-start: 1;
}

#image {
    grid-column-start: 2;
} 

E o resultado, novamente, não foi o que eu esperava. O item de texto continuou ocupando a segunda linha.

Itens do grid na ordem errada

E se eu definir explicitamente que o item de texto deve estar na primeira linha? Posso fazer isso usando a declaração grid-row-start:

#text {
    grid-column-start: 1;
    grid-row-start: 1;
}

#image {
    grid-column-start: 2;
} 
Itens do grid na ordem correta

Agora sim, funcionou! Mas tem um detalhe que talvez você tenha notado: para funcionar, eu tive que voltar a usar uma regra para definir a posição da linha.

Na verdade, se eu remover, do CSS acima, as declarações grid-column-start, eu obtenho o mesmo resultado e, de fato, voltei ao início dessa aventura quando comecei com o grid-row: 1 apenas.

O detalhe

Ainda sem entender o que se passava, cheguei a postar minha dúvida no StackOverflow [8]. E foi aí que me mostraram o detalhe que havia passado despercebido em minha primeira leitura do algoritmo de posicionamento.

O segundo e quarto passo do algoritmo são, conforme documentação [5]:

    1. Process the items locked to a given row.
    1. Position the remaining grid items.

Agora, o mistério foi solucionado. ✔ Quando eu usei grid-row: 1 para o item de texto, ele foi processado primeiro e foi alocado para a primeira área disponível do grid (linha 1, coluna 1); ou seja, o item de texto foi processado no passo 2 e o item de imagem foi processado no passo 4.

Já quando eu usei grid-column-start: 1 para o item de texto, ambos os itens foram processados no passo 4 e, pela ordem DOM, o item de imagem foi processado primeiro e foi alocado para a primeira posição (linha 1, coluna 1). Como agora o item de texto tinha necessariamente que iniciar na coluna 1, o algoritmo criou uma linha (implicit grid) abaixo para que ele pudesse estar na coluna correta.

Quando eu adicionei grid-column-start: 2 para o item de imagem, o item de texto continuou na linha 2 por conta do cursor que mencionei mais acima; ele continua a posicionar após o último item posicionado. Esse comportamento pode ser alterado se o grid for definido como dense (ele é sparse por padrão) [7].

Conclusão

Mesmo um exercício simples como o que escolhi pode nos ensinar detalhes fundamentais se cavarmos fundo o bastante. E, depois de experimentar tanto e quebrar a cabeça tentando entender a razão das coisas, dificilmente vou esquecer essa lição. Na hora é um pouco cansativo, mas vale a pena.

Por fim, um outro método para se alterar a ordem dos itens é com a propriedade order[9]. Mas perceba essa mudança é apenas visual e pode não ser suficiente para sua necessidade:

Since order is only meant to affect the visual order of elements and not their logical or tab order. order must not be used on non-visual media such as speech.

Referências

[1] - [https://www.frontendmentor.io/challenges/stats-preview-card-component-8JqbgoU62]
[2] - [https://codesandbox.io]
[3] - [https://css-tricks.com/snippets/css/complete-guide-grid/]
[4] - [https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout]
[5] - [https://drafts.csswg.org/css-grid/#auto-placement-algo]
[6] - [https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Auto-placement_in_CSS_Grid_Layout]
[7] - [https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow]
[8] - [https://stackoverflow.com/questions/68595914/css-grid-item-placement]
[9] - [https://developer.mozilla.org/en-US/docs/Web/CSS/order]