Disposition de maçonnerie en CSS uniquement

134

J'ai besoin de mettre en œuvre une disposition de maçonnerie assez délabrée. Cependant, pour un certain nombre de raisons, je ne souhaite pas utiliser JavaScript pour le faire.

Une grille de plusieurs colonnes de rectangles de hauteur variable.

Paramètres:

  • Tous les éléments ont la même largeur
  • Les éléments ont une hauteur qui ne peut pas être calculée côté serveur (une image plus diverses quantités de texte)
  • Je peux vivre avec un nombre fixe de colonnes si je le dois

il existe une solution triviale à cela qui fonctionne dans les navigateurs modernes, la column-countpropriété.

Le problème avec cette solution est que les éléments sont classés en colonnes:

En partant de la case la plus à gauche, elles sont numérotées de 1 à 4 vers le bas, la case la plus haute de la colonne suivante est 5, et ainsi de suite.

Bien que j'ai besoin que les éléments soient classés en lignes, au moins environ:

En partant de la case la plus à gauche, elles sont numérotées de 1 à 6 tout droit, mais comme la case 5 est la plus courte, la case en dessous est 7 car elle a l'apparence d'être sur une rangée plus haute que la case suivante à l'extrême gauche.

Les approches que j'ai essayées qui ne fonctionnent pas:

Maintenant, je pourrais changer le rendu côté serveur et réorganiser les éléments en divisant le nombre d'éléments par le nombre de colonnes, mais c'est compliqué, sujet aux erreurs (en fonction de la façon dont les navigateurs décident de diviser la liste d'éléments en colonnes), alors j'aimerais pour l'éviter si possible.

Y a-t-il une nouvelle magie Flexbox qui rend cela possible?

Pekka
la source
7
Impossible de penser à un moyen qui ne dépend pas de hauteurs prédéfinies. Si vous reconsidérez JS, jetez un œil à stackoverflow.com/questions/13518653/…j'implémente une telle solution qui est assez simple.
Gabriele Petrioli
1
@Gaby implémente votre solution dès maintenant, merci!
Pekka
4
Je réalise que vous avez dit uniquement CSS. Je veux juste mentionner que Masonry ne nécessite plus jQuery - la bibliothèque minifiée fait moins de 8 ko - et peut être initialisée avec html seul. Juste pour référence jsfiddle.net/wp7kuk1t
I haz kode
1
Si vous pouvez déterminer la hauteur des éléments à l'avance, en connaissant la hauteur de la ligne, la taille de la police (vous devrez servir une police spécifique et faire des calculs intelligents), la hauteur de l'image, la marge du verticle et le remplissage, vous pouvez fais ça. Sinon, vous ne pouvez pas le faire en utilisant uniquement CSS. Vous pouvez également utiliser quelque chose comme PhantomJS pour pré-rendre chaque élément et obtenir la hauteur de cet élément, mais il y aurait une surcharge / latence importante ajoutée.
Timothy Zorn

Réponses:

157

Flexbox

Une disposition de maçonnerie dynamique n'est pas possible avec flexbox, du moins pas de manière propre et efficace.

Flexbox est un système de mise en page unidimensionnel. Cela signifie qu'il peut aligner les éléments le long de lignes horizontales OU verticales. Un élément flexible est confiné à sa ligne ou à sa colonne.

Un vrai système de grille est bidimensionnel, ce qui signifie qu'il peut aligner des éléments le long de lignes horizontales ET verticales. Les éléments de contenu peuvent s'étendre simultanément sur des lignes et des colonnes, ce que les éléments flexibles ne peuvent pas faire.

C'est pourquoi flexbox a une capacité limitée pour la construction de grilles. C'est aussi une raison pour laquelle le W3C a développé une autre technologie CSS3, Grid Layout .


row wrap

Dans un conteneur flexible avec flex-flow: row wrap, les éléments flex doivent être enveloppés dans de nouvelles lignes .

Cela signifie qu'un élément flexible ne peut pas être enveloppé sous un autre élément de la même ligne .

Remarquez ci-dessus comment la div # 3 s'enroule sous la div # 1 , créant une nouvelle ligne. Il ne peut pas être enveloppé sous la div # 2 .

En conséquence, lorsque les éléments ne sont pas les plus hauts de la ligne, un espace blanc reste, créant des espaces disgracieux.


column wrap

Si vous passez à flex-flow: column wrap, une disposition en forme de grille est plus réalisable. Cependant, un conteneur de direction de colonne présente quatre problèmes potentiels dès le départ:

  1. Les éléments flexibles circulent verticalement et non horizontalement (comme vous en avez besoin dans ce cas).
  2. Le conteneur se développe horizontalement, pas verticalement (comme la mise en page Pinterest).
  3. Il faut que le conteneur ait une hauteur fixe, afin que les articles sachent où emballer.
  4. Au moment d'écrire ces lignes, il présente une carence dans tous les principaux navigateurs où le conteneur ne se développe pas pour accueillir des colonnes supplémentaires .

Par conséquent, un conteneur de direction de colonne n'est pas une option dans ce cas, et dans de nombreux autres cas.


Grille CSS avec des dimensions d'élément non définies

Grid Layout serait une solution parfaite à votre problème si les différentes hauteurs des éléments de contenu pouvaient être prédéterminées . Toutes les autres exigences sont bien dans la capacité de Grid.

La largeur et la hauteur des éléments de la grille doivent être connues afin de combler les espaces avec les éléments environnants.

Donc, Grid, qui est le meilleur CSS a à offrir pour créer une mise en page de maçonnerie à écoulement horizontal, ne suffit pas dans ce cas.

En fait, jusqu'à ce qu'une technologie CSS arrive avec la capacité de combler automatiquement les lacunes, CSS en général n'a pas de solution. Quelque chose comme cela nécessiterait probablement de redéfinir le document, donc je ne suis pas sûr de son utilité ou de son efficacité.

Vous aurez besoin d'un script.

Les solutions JavaScript ont tendance à utiliser un positionnement absolu, qui supprime les éléments de contenu du flux de documents afin de les réorganiser sans espaces. Voici deux exemples:


Grille CSS avec des dimensions d'élément définies

Pour les mises en page où la largeur et la hauteur des éléments de contenu sont connues, voici une mise en page de maçonnerie à écoulement horizontal en CSS pur:

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>

Démo jsFiddle


Comment ça fonctionne

  1. Établissez un conteneur de grille au niveau du bloc. ( inline-gridserait l'autre option)
  2. La grid-auto-rowspropriété définit la hauteur des lignes générées automatiquement. Dans cette grille, chaque ligne mesure 50 px de haut.
  3. La grid-gappropriété est un raccourci pour grid-column-gapet grid-row-gap. Cette règle définit un écart de 10 pixels entre les éléments de la grille. (Cela ne s'applique pas à la zone entre les articles et le conteneur.)
  4. La grid-template-columnspropriété définit la largeur des colonnes définies explicitement.

    La repeatnotation définit un modèle de colonnes (ou lignes) répétitives.

    La auto-fillfonction indique à la grille d'aligner autant de colonnes (ou de lignes) que possible sans déborder du conteneur. (Cela peut créer un comportement similaire à celui de la mise en page flexible flex-wrap: wrap.)

    La minmax()fonction définit une plage de tailles minimale et maximale pour chaque colonne (ou ligne). Dans le code ci-dessus, la largeur de chaque colonne sera au minimum de 30% du conteneur et au maximum de tout espace libre disponible.

    L' frunité représente une fraction de l'espace libre dans le conteneur de grille. C'est comparable à la flex-growpropriété de flexbox .

  5. Avec grid-rowet, spannous indiquons aux éléments de la grille le nombre de lignes sur lesquelles ils doivent s'étendre.


Prise en charge du navigateur pour la grille CSS

  • Chrome - prise en charge complète à partir du 8 mars 2017 (version 57)
  • Firefox - support complet à partir du 6 mars 2017 (version 52)
  • Safari - prise en charge complète à compter du 26 mars 2017 (version 10.1)
  • Edge - prise en charge complète à partir du 16 octobre 2017 (version 16)
  • IE11 - pas de support pour les spécifications actuelles; prend en charge la version obsolète

Voici l'image complète: http://caniuse.com/#search=grid


Fonctionnalité de superposition de grille cool dans Firefox

Dans les outils de développement de Firefox, lorsque vous inspectez le conteneur de grille, il y a une petite icône de grille dans la déclaration CSS. Au clic, il affiche un contour de votre grille sur la page.

Plus de détails ici: https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts

Michael Benjamin
la source
5
Réponse fantastique! Avec la solution "Grille CSS avec dimensions des éléments définies", est-il possible d'exprimer la hauteur d'une cellule en pourcentage de la largeur de la cellule? Cela serait utile pour afficher des images dont le rapport hauteur / largeur est connu. Nous voulons conserver le rapport hauteur / largeur à tout moment.
Oliver Joseph Ash
Merci. En ce qui concerne le problème de rapport hauteur / largeur pour les images, la méthode du «pourcentage de largeur de cellule» (l'astuce du pourcentage de remplissage?), Elle n'est pas fiable dans Grid, ou Flexbox, d'ailleurs. Voir ici et ici . @OliverJosephAsh
Michael Benjamin
Oui, je fais référence à l'astuce de remplissage en pourcentage. Est-il possible d'utiliser l'une des solutions de contournement en combinaison avec votre solution de disposition de maçonnerie? Pour le contexte, nous cherchons à implémenter une mise en page en maçonnerie CSS uniquement pour les images sur unsplash.com .
Oliver Joseph Ash
1
Malheureusement, utiliser JS pour cela n'est pas une option. Nous souhaitons activer le rendu côté serveur pour des raisons de performances et de référencement. Cela signifie que la mise en page doit être rendue avant que JavaScript côté client ne soit téléchargé, analysé et exécuté. Étant donné que cela semble impossible, je suppose que nous devrons faire un compromis quelque part le long de la ligne! Merci pour votre aide :-)
Oliver Joseph Ash
1
Mise à jour des spécifications: dans flexbox, les marges en pourcentage et les rembourrages sont désormais définis pour résoudre à nouveau la taille en ligne du conteneur. drafts.csswg.org/css-flexbox/#item-margins @OliverJosephAsh
Michael Benjamin
13

Il s'agit d'une technique récemment découverte impliquant la flexbox: https://tobiasahlin.com/blog/masonry-with-css/ .

L'article a du sens pour moi, mais je n'ai pas essayé de l'utiliser, donc je ne sais pas s'il y a des mises en garde, autres que celles mentionnées dans la réponse de Michael.

Voici un exemple de l'article, utilisant la orderpropriété, combiné avec :nth-child.

Extrait de pile

.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
la source
Tout d'abord, les réponses de lien uniquement sont mauvaises, car lorsque ce lien meurt, la réponse fait de même. Deuxièmement, si vous lisez plus attentivement la réponse donnée, vous verrez que ce qui est mentionné dans votre lien est couvert. En termes simples, il y a trop de limitations en utilisant Flexbox seule, à moins que ce qui est mentionné ci-dessus ne soit pas un problème.
Ason
3
J'ai lu attentivement la réponse acceptée - vous verrez même quelques commentaires que j'ai ajoutés au bas de celle-ci. L'approche flexbox à laquelle j'ai associé est unique et n'est pas couverte par cette réponse. Je ne veux pas répéter l'article, car c'est très compliqué, et l'article fait un excellent travail pour couvrir cela.
Oliver Joseph Ash
La seule chose que le lien fait différemment est d'utiliser la orderpropriété, ce qui donne l'impression que les éléments circulent de gauche à droite. Pourtant, son principal truc est flex-flow: column wrap, qui est mentionné dans la réponse ci-dessus. De plus, il ne peut pas faire circuler les éléments comme le fait une vraie maçonnerie, par exemple si le 3ème et le 4ème élément tiendraient dans la 3ème colonne, ils le feraient, celui lié ne le ferait pas. Mais encore une fois, comme je l'ai dit, tout dépend de ce que sont les exigences, et dans ce cas (cette question), cela ne fonctionnera pas comme ce qui était demandé.
Ason
De plus, il ne s'agit pas de "vous ne voulez pas répéter l'article" , une réponse doit contenir la partie essentielle du code, elle sera donc toujours valide lorsque la ressource liée meurt.
Ason
3
Je l'ai mis à jour, ajouté un échantillon de l'article + un vote positif.
Ason