Layout de alvenaria somente CSS

134

Preciso implementar um layout de alvenaria bastante comum. No entanto, por várias razões, não quero usar JavaScript para fazer isso.

Uma grade de várias colunas de retângulos de altura variável.

Parâmetros:

  • Todos os elementos têm a mesma largura
  • Os elementos têm uma altura que não pode ser calculada no lado do servidor (uma imagem mais várias quantidades de texto)
  • Eu posso viver com um número fixo de colunas se precisar

existe uma solução trivial para isso que funciona em navegadores modernos, a column-countpropriedade.

O problema com essa solução é que os elementos são ordenados em colunas:

Começando pela caixa superior esquerda, eles são numerados de 1 a 4, a caixa superior na coluna seguinte é 5 e assim por diante.

Embora eu precise que os elementos sejam ordenados em linhas, pelo menos aproximadamente:

Começando na caixa superior à esquerda, eles são numerados de 1 a 6 em linha reta, mas como a caixa 5 é a mais curta, a caixa abaixo dela é 7, pois tem a aparência de estar em uma linha mais alta que a próxima caixa na extrema esquerda.

Abordagens que tentei que não funcionam:

Agora eu poderia alterar a renderização no servidor e reordenar os itens que dividem o número de itens pelo número de colunas, mas isso é complicado, propenso a erros (com base em como os navegadores decidem dividir a lista de itens em colunas), então eu gostaria para evitá-lo, se possível.

Existe alguma mágica flexível do flexbox que torna isso possível?

Pekka
fonte
7
Não consigo pensar em uma maneira que não dependa de alturas predefinidas. Se você reconsiderar JS, dê uma olhada em stackoverflow.com/questions/13518653/… onde eu implemento uma solução que é bastante simples.
Gabriele Petrioli
1
@Gaby implementando sua solução agora, obrigado!
Pekka
4
Sei que você disse apenas CSS. Eu só quero mencionar que a Maçonaria não requer mais o jQuery - a biblioteca minificada tem menos de 8kb - e pode ser inicializada apenas com html. Apenas para referência jsfiddle.net/wp7kuk1t
I haz kode
1
Se você pode determinar a altura dos elementos antes do tempo, conhecendo a altura da linha, o tamanho da fonte (você teria que servir uma fonte específica e fazer alguns cálculos inteligentes), altura da imagem, margem de verticle e preenchimento, poderá faça isso. Caso contrário, você não poderá fazer isso usando apenas CSS. Você também pode usar algo como o PhantomJS para pré-renderizar cada elemento e obter a altura desse elemento, mas haverá sobrecarga / latência significativa.
Timothy Zorn

Respostas:

157

Flexbox

Um layout de alvenaria dinâmico não é possível com o flexbox, pelo menos não de maneira limpa e eficiente.

Flexbox é um sistema de layout unidimensional. Isso significa que ele pode alinhar itens ao longo de linhas horizontais OU verticais. Um item flexível está confinado à sua linha ou coluna.

Um sistema de grade real é bidimensional, o que significa que ele pode alinhar itens ao longo de linhas horizontais e verticais. Os itens de conteúdo podem se estender por linhas e colunas simultaneamente, o que os itens flexíveis não podem.

É por isso que o flexbox tem uma capacidade limitada para construir grades. É também uma razão pela qual o W3C desenvolveu outra tecnologia CSS3, o Grid Layout .


row wrap

Em um contêiner flexível com flex-flow: row wrap, os itens flexíveis devem quebrar em novas linhas .

Isso significa que um item flexível não pode quebrar em outro item na mesma linha .

Observe acima como a div nº 3 se divide abaixo da div nº 1 , criando uma nova linha. Não pode quebrar abaixo da div # 2 .

Como resultado, quando os itens não são os mais altos da linha, o espaço em branco permanece, criando lacunas desagradáveis.


column wrap

Se você alternar para flex-flow: column wrap, um layout semelhante à grade é mais viável. No entanto, um contêiner na direção da coluna tem quatro problemas em potencial imediatamente:

  1. Os itens flexíveis fluem verticalmente, não horizontalmente (como você precisa neste caso).
  2. O contêiner se expande horizontalmente, não verticalmente (como o layout do Pinterest).
  3. Requer que o contêiner tenha uma altura fixa, para que os itens saibam onde embrulhar.
  4. No momento da redação deste documento, ele possui uma deficiência em todos os principais navegadores, onde o contêiner não se expande para acomodar colunas adicionais .

Como resultado, um contêiner na direção da coluna não é uma opção nesse caso e em muitos outros casos.


Grade CSS com dimensões de item indefinidas

O layout da grade seria uma solução perfeita para o seu problema se as várias alturas dos itens de conteúdo pudessem ser pré-determinadas . Todos os outros requisitos estão dentro da capacidade da Grid.

A largura e a altura dos itens da grade devem ser conhecidas para fechar as lacunas nos itens ao redor.

Portanto, o Grid, que é o melhor CSS para oferecer para a construção de um layout de alvenaria com fluxo horizontal, fica aquém neste caso.

De fato, até que uma tecnologia CSS chegue com a capacidade de fechar automaticamente as lacunas, o CSS em geral não tem solução. Algo assim provavelmente exigiria refletir o documento, então não tenho certeza de quão útil ou eficiente seria.

Você precisará de um script.

As soluções JavaScript tendem a usar posicionamento absoluto, o que remove os itens de conteúdo do fluxo de documentos para reorganizá-los sem falhas. Aqui estão dois exemplos:


Grade CSS com dimensões de item definidas

Para layouts em que a largura e a altura dos itens de conteúdo são conhecidas, aqui está um layout de alvenaria que flui horizontalmente em CSS puro:

grid-container {
  display: grid;                                                /* 1 */
  grid-auto-rows: 50px;                                         /* 2 */
  grid-gap: 10px;                                               /* 3 */
  grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));   /* 4 */
}

[short] {
  grid-row: span 1;                                             /* 5 */
  background-color: green;
}

[tall] {
  grid-row: span 2;
  background-color: crimson;
}

[taller] {
  grid-row: span 3;
  background-color: blue;
}

[tallest] {
  grid-row: span 4;
  background-color: gray;
}

grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3em;
  font-weight: bold;
  color: white;
}
<grid-container>
  <grid-item short>01</grid-item>
  <grid-item short>02</grid-item>
  <grid-item tall>03</grid-item>
  <grid-item tall>04</grid-item>
  <grid-item short>05</grid-item>
  <grid-item taller>06</grid-item>
  <grid-item short>07</grid-item>
  <grid-item tallest>08</grid-item>
  <grid-item tall>09</grid-item>
  <grid-item short>10</grid-item>
  <grid-item tallest>etc.</grid-item>
  <grid-item tall></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item tallest></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item tallest></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
</grid-container>

Demonstração do jsFiddle


Como funciona

  1. Estabeleça um contêiner de grade em nível de bloco. (inline-grid seria a outra opção)
  2. A grid-auto-rowspropriedade define a altura das linhas geradas automaticamente. Nesta grade, cada linha tem 50 px de altura.
  3. A grid-gappropriedade é uma abreviação para grid-column-gape grid-row-gap. Esta regra define um intervalo de 10 px entre os itens da grade. (Não se aplica à área entre itens e o contêiner.)
  4. A grid-template-columnspropriedade define a largura das colunas definidas explicitamente.

    A repeatnotação define um padrão de colunas (ou linhas) repetidas.

    A auto-fillfunção diz à grade para alinhar o maior número possível de colunas (ou linhas) sem sobrecarregar o contêiner. (Isso pode criar um comportamento semelhante ao flexflex-wrap: wrap .)

    A minmax()função define um intervalo de tamanho mínimo e máximo para cada coluna (ou linha). No código acima, a largura de cada coluna será no mínimo 30% do contêiner e no máximo de qualquer espaço livre disponível.

    A frunidade representa uma fração do espaço livre no contêiner da grade. É comparável à flex-growpropriedade do flexbox .

  5. Com grid-rowe spanestamos dizendo aos itens da grade quantas linhas eles devem se estender.


Suporte do navegador para CSS Grid

  • Chrome - suporte completo a partir de 8 de março de 2017 (versão 57)
  • Firefox - suporte completo a partir de 6 de março de 2017 (versão 52)
  • Safari - suporte completo a partir de 26 de março de 2017 (versão 10.1)
  • Edge - suporte completo a partir de 16 de outubro de 2017 (versão 16)
  • IE11 - sem suporte para especificações atuais; suporta versão obsoleta

Aqui está a imagem completa: http://caniuse.com/#search=grid


Recurso de sobreposição de grade legal no Firefox

Nas ferramentas de desenvolvimento do Firefox, quando você inspeciona o contêiner de grade, há um pequeno ícone de grade na declaração CSS. Ao clicar, exibe um esboço da sua grade na página.

Mais detalhes aqui: https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts

Michael Benjamin
fonte
5
Resposta fantástica! Com a solução "Grade CSS com dimensões de item definidas", é possível expressar a altura de uma célula como uma porcentagem da largura da célula? Isso seria útil para exibir imagens, onde a proporção é conhecida. Queremos manter a proporção sempre.
Oliver Joseph Ash
Obrigado. No que diz respeito ao problema de proporção das imagens, o método "porcentagem da largura da célula" (o truque de preenchimento percentual?), Não é confiável no Grid, ou no Flexbox, nesse caso. Veja aqui e aqui . @OliverJosephAsh
Michael Benjamin
Sim, estou me referindo ao truque de preenchimento percentual. É possível usar alguma das soluções alternativas em combinação com sua solução de layout de alvenaria? Por contexto, procuramos implementar um layout de alvenaria somente em CSS para as imagens em unsplash.com .
Oliver Joseph Ash
1
Infelizmente, usar JS para isso não é uma opção. Queremos ativar a renderização no servidor por motivos de desempenho e SEO. Isso significa que o layout deve ser renderizado antes que o JavaScript do lado do cliente seja baixado, analisado e executado. Dado que isso parece impossível, acho que teremos que negociar em algum lugar ao longo da linha! Obrigado pela sua ajuda :-)
Oliver Joseph Ash
1
Atualização de especificação: No flexbox, as margens e os preenchimentos de porcentagem agora estão definidos para resolver novamente o tamanho embutido do contêiner. drafts.csswg.org/css-flexbox/#item-margins @OliverJosephAsh
Michael Benjamin
13

Essa é uma técnica recentemente descoberta que envolve o flexbox: https://tobiasahlin.com/blog/masonry-with-css/ .

O artigo faz sentido para mim, mas eu não tentei usá-lo, então não sei se há alguma ressalva, além das mencionadas na resposta de Michael.

Aqui está uma amostra do artigo, fazendo uso da orderpropriedade, combinada com:nth-child .

Fragmento de pilha

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Your container needs a fixed height, and it 
   * needs to be taller than your tallest column. */
  height: 960px;
  
  /* Optional */
  background-color: #f7f7f7;
  border-radius: 3px;
  padding: 20px;
  width: 60%;
  margin: 40px auto;
  counter-reset: items;
}

.item {
  width: 24%;
  /* Optional */
  position: relative;
  margin-bottom: 2%;
  border-radius: 3px;
  background-color: #a1cbfa;
  border: 1px solid #4290e2;
  box-shadow: 0 2px 2px rgba(0,90,250,0.05),
    0 4px 4px rgba(0,90,250,0.05),
    0 8px 8px rgba(0,90,250,0.05),
    0 16px 16px rgba(0,90,250,0.05);
  color: #fff;
  padding: 15px;
  box-sizing: border-box;
}

 /* Just to print out numbers */
div.item::before {
  counter-increment: items;
  content: counter(items);
}

/* Re-order items into 3 rows */
.item:nth-of-type(4n+1) { order: 1; }
.item:nth-of-type(4n+2) { order: 2; }
.item:nth-of-type(4n+3) { order: 3; }
.item:nth-of-type(4n)   { order: 4; }

/* Force new columns */
.break {
  flex-basis: 100%;
  width: 0;
  border: 1px solid #ddd;
  margin: 0;
  content: "";
  padding: 0;
}

body { font-family: sans-serif; }
h3 { text-align: center; }
<div class="container">
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  
  <span class="item break"></span>
  <span class="item break"></span>
  <span class="item break"></span>
</div>

Oliver Joseph Ash
fonte
Primeiro, as respostas apenas ao link são ruins, como quando esse link morre, o mesmo acontece com a resposta. Segundo, se você ler a resposta dada com mais cuidado, encontrará o que é mencionado no seu link. Simplificando, há muitas limitações usando o Flexbox sozinho, a menos que o que foi mencionado acima não seja um problema.
Ason
3
Eu li a resposta aceita muito bem - você até verá alguns comentários que eu adicionei ao final dela. A abordagem do flexbox à qual vinculei é única e não é coberta por essa resposta. Não quero repetir o artigo, porque é muito complicado, e o artigo faz um ótimo trabalho ao abordá-lo.
Oliver Joseph Ash
A única coisa que o link faz de diferente é usar a orderpropriedade, fazendo com que pareça que os itens fluem da esquerda para a direita. Ainda assim, seu principal truque flex-flow: column wrapé mencionado na resposta acima. Além disso, ele não pode fluir os itens da maneira que uma alvenaria real faz; por exemplo, se o 3º e o 4º item couberem na 3ª coluna, eles fariam, o vinculado não. Mas, novamente, como eu disse, tudo depende de quais são os requisitos e, neste caso (essa pergunta), não funcionará como o solicitado.
Ason
Além disso, não se trata de "você não deseja repetir o artigo" ; uma resposta deve conter a parte essencial do código; portanto, ainda será válida quando o recurso vinculado morrer.
Ason
3
Eu o atualizei, adicionei uma amostra do artigo + um voto positivo.
Ason