Lorsque les éléments flexbox s'enroulent en mode colonne, le conteneur n'augmente pas sa largeur

168

Je travaille sur une mise en page flexbox imbriquée qui devrait fonctionner comme suit:

Le niveau le plus à l'extérieur ( ul#main) est une liste horizontale qui doit s'étendre vers la droite lorsque d'autres éléments y sont ajoutés. S'il devient trop grand, il devrait y avoir une barre de défilement horizontale.

#main {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    overflow-x: auto;
    /* ...and more... */
}

Chaque élément de cette liste ( ul#main > li) a un en-tête ( ul#main > li > h2) et une liste interne ( ul#main > li > ul.tasks). Cette liste interne est verticale et doit être intégrée dans des colonnes si nécessaire. Lors de l'encapsulation dans plus de colonnes, sa largeur doit augmenter pour faire de la place pour plus d'éléments. Cette augmentation de largeur doit également s'appliquer à l'élément contenant de la liste externe.

.tasks {
    flex-direction: column;
    flex-wrap: wrap;
    /* ...and more... */
}

Mon problème est que les listes internes ne s'enroulent pas lorsque la hauteur de la fenêtre devient trop petite. J'ai essayé beaucoup de falsification de toutes les propriétés flex, en essayant de suivre méticuleusement les directives de CSS-Tricks , mais pas de chance.

Ce JSFiddle montre ce que j'ai jusqu'à présent.

Résultat attendu (ce que je veux) :

Ma sortie souhaitée

Résultat réel (ce que j'obtiens) :

Ma sortie actuelle

Résultat plus ancien (ce que j'ai obtenu en 2015) :

Mon ancienne sortie

METTRE À JOUR

Après quelques recherches, cela commence à ressembler à un problème plus important. Tous les principaux navigateurs se comportent de la même manière , et cela n'a rien à voir avec l'imbrication de ma conception flexbox. Les dispositions de colonne Flexbox encore plus simples refusent d'augmenter la largeur de la liste lorsque les éléments sont enveloppés.

Cet autre JSFiddle démontre clairement le problème. Dans les versions actuelles de Chrome, Firefox et IE11, tous les éléments sont correctement enveloppés; la hauteur de la liste augmente en rowmode, mais sa largeur n'augmente pas en columnmode. De plus, il n'y a pas du tout de redistribution immédiate des éléments lors du changement de hauteur d'un columnmode, mais il y en a en rowmode.

Cependant, les spécifications officielles (regardez spécifiquement l'exemple 5) semblent indiquer que ce que je veux faire devrait être possible.

Quelqu'un peut-il trouver une solution de contournement à ce problème?

MISE À JOUR 2

Après de nombreuses expériences en utilisant JavaScript pour mettre à jour la hauteur et la largeur de divers éléments lors des événements de redimensionnement, j'en suis venu à la conclusion qu'il est trop complexe et trop difficile d'essayer de le résoudre de cette façon. De plus, l'ajout de JavaScript rompt définitivement le modèle de flexbox, qui doit être maintenu aussi propre que possible.

Pour l'instant, je reviens à la overflow-y: autoplace de flex-wrap: wrappour que le conteneur interne défile verticalement en cas de besoin. Ce n'est pas joli, mais c'est une voie à suivre qui au moins ne rompt pas trop la convivialité.

Anders Tornblad
la source
2
Juste une remarque, mais il y a un bogue dans Chrome qui fait qu'un conteneur dont le flux est défini sur column wrapne pas étendre sa largeur lors de l'emballage. Voir code.google.com/p/chromium/issues/detail?id=247963 pour plus d'informations.
Gerrit Bertier
Je ne peux pas non plus le faire fonctionner dans IE11. Là, les éléments les plus internes s'enroulent, mais la liste interne et son conteneur (l'élément externe) n'augmentent pas leur largeur. Actuellement, je retombe au défilement des listes internes, mais ce n'est pas une bonne solution à long terme, et j'aimerais vraiment éviter d'ajouter du JavaScript pour cela.
Anders Tornblad

Réponses:

171

Le problème

Cela ressemble à une lacune fondamentale dans la disposition flexible.

Un conteneur flexible dans le sens des colonnes ne se développera pas pour accueillir des colonnes supplémentaires. (Ce n'est pas un problème dans flex-direction: row.)

Cette question a été posée plusieurs fois (voir la liste ci-dessous), sans réponse claire en CSS.

Il est difficile d'épingler cela comme un bogue car le problème se produit dans tous les principaux navigateurs. Mais cela soulève la question:

Comment est-il possible que tous les principaux navigateurs aient le conteneur flex pour se développer lors de l'enroulement dans le sens des lignes mais pas dans le sens des colonnes?

On pourrait penser qu'au moins l'un d'entre eux ferait les choses correctement. Je ne peux que spéculer sur la raison. C'était peut-être une implémentation techniquement difficile et a été mise de côté pour cette itération.

MISE À JOUR: le problème semble être résolu dans Edge v16.


Illustration du problème

L'OP a créé une démonstration utile illustrant le problème. Je le copie ici: http://jsfiddle.net/nwccdwLw/1/


Options de contournement

Solutions Hacky de la communauté Stack Overflow:


Plus d'analyse


Autres articles décrivant le même problème

Michael Benjamin
la source
2
Selon les commentaires du rapport de bogue, ce problème ne sera pas résolu avant la sortie de leur nouveau moteur de mise en page LayoutNG
Ben.12
5
Le nombre de liens que vous avez fournis et classés par catégorie est vraiment excellent. Je souhaite que la plupart des gens écrivent leurs réponses de cette façon. Très bonne réponse.
Vignesh
4
Voici un repo utile répertoriant plusieurs bogues Flexbox et des solutions de contournement pour eux: Ce bogue est répertorié au n ° 14
Ben.12
pouvons-nous résoudre ce problème en utilisant une grille CSS?
Ismail Farooq
une autre option de contournement: codepen.io/_mc2/pen/PoPmJRP Pour certains aspects, je l'ai trouvé meilleure que l'approche en mode écriture
michalczerwinski
38

En retard à la fête, mais rencontrait toujours ce problème des années plus tard. J'ai fini par trouver une solution en utilisant la grille. Sur le conteneur, vous pouvez utiliser

display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(6, auto);

J'ai un exemple sur CodePen qui bascule entre le problème de la flexbox et le correctif de la grille: https://codepen.io/MandeeD/pen/JVLdLd

MandeeD
la source
1
C'est une solution de contournement élégante si j'en ai déjà vu une! Je viens juste d'entrer dans la grille et j'adore votre solution, merci pour cela.
Albert
Sur place! J'ai résolu mon problème.
Célestin Bochis
Vous rencontrez toujours ce problème dans Chrome. J'espérais éviter d'utiliser une solution de contournement de grille. Bon à savoir que d'autres utilisent cette approche!
trukvl du
sauveur de vie. Vous pouvez également utiliser grid-row-end: span X;si vous avez besoin que certains des éléments occupent plus de lignes.
Meirion Hughes le
0

Il est malheureux que tant de grands navigateurs souffrent de ce bug après de nombreuses années. Envisagez une solution de contournement Javascript. Chaque fois que la fenêtre du navigateur est redimensionnée ou que du contenu est ajouté à l'élément, exécutez ce code pour le redimensionner à la largeur appropriée. Vous pouvez définir une directive dans votre cadre pour le faire à votre place.

    element.style.flexBasis = "auto";
    element.style.flexBasis = `${element.scrollWidth}px`;
Steve Hanov
la source
0

Je viens de trouver une solution de contournement PURE CSS vraiment géniale ici.

https://jsfiddle.net/gcob492x/3/

Le plus délicat: définir writing-mode: vertical-lrdans la liste div puis writing-mode: horizontal-tbdans l'élément de liste. J'ai dû peaufiner les styles dans le JSFiddle (supprimer une grande partie des styles d'alignement, qui ne sont pas nécessaires pour la solution).

Remarque: le commentaire indique que cela ne fonctionne que dans les navigateurs basés sur Chromium, et non dans Firefox. Je n'ai personnellement testé que dans Chrome. Il est possible qu'il y ait un moyen de modifier cela pour le faire fonctionner dans d'autres navigateurs ou qu'il y ait eu des mises à jour de ces navigateurs qui font que cela fonctionne.

Grand bravo à ce commentaire: lorsque les éléments flexbox s'enroulent en mode colonne, le conteneur n'augmente pas sa largeur . Fouiller ce fil de discussion m'a conduit à https://bugs.chromium.org/p/chromium/issues/detail?id=507397#c39 qui m'a conduit à ce JSFiddle.

Dustin Kane
la source
-1

Puisqu'aucune solution ou solution de contournement appropriée n'a encore été suggérée, j'ai réussi à obtenir le comportement demandé avec une approche légèrement différente. Au lieu de séparer la mise en page en 3 divs différents, j'ajoute tous les éléments en 1 div et crée la séparation avec quelques divs supplémentaires entre les deux.

La solution proposée est codée en dur, en supposant que nous avons 3 sections, mais peut être étendue à une section générique. L'idée principale est d'expliquer comment nous pouvons réaliser cette mise en page.

  1. Ajout de tous les éléments dans 1 conteneur div qui utilise flex pour envelopper les éléments
  2. Le premier élément de chaque "conteneur interne" (je l'appellerai une section) aura une classe, ce qui nous aide à faire quelques manipulations qui créent la séparation et le style de chaque section.
  3. En utilisant :beforesur chaque premier élément, nous pouvons localiser le titre de chaque section.
  4. L'utilisation spacecrée un espace entre les sections
  5. Étant donné que le spacene couvre pas toute la hauteur de la section, j'ajoute également :afteraux sections afin de le positionner avec une position absolue et un fond blanc.
  6. Pour styliser la couleur d'arrière-plan de chaque section, j'ajoute un autre div à l'intérieur du premier élément de chaque section. Je serai aussi en position absolue et l'aurai z-index: -1.
  7. Pour obtenir la largeur correcte de chaque arrière-plan, j'utilise JS, en définissant la largeur correcte et en ajoutant également un écouteur à redimensionner.

function calcWidth() {
  var size = $(document).width();
  var end = $(".end").offset().left;

  var todoWidth = $(".doing-first").offset().left;
  $(".bg-todo").css("width", todoWidth);

  var doingWidth = $(".done-first").offset().left - todoWidth;
  $(".bg-doing").css("width", doingWidth);

  var doneWidth = $(".end").offset().left - $(".done-first").offset().left;
  $(".bg-done").css("width", doneWidth + 20);

}

calcWidth();

$(window).resize(function() {
  calcWidth();
});
.container {
  display: flex;
  flex-wrap: wrap;
  flex-direction: column;
  height: 120px;
  align-content: flex-start;
  padding-top: 30px;
  overflow-x: auto;
  overflow-y: hidden;
}

.item {
  width: 200px;
  background-color: #e5e5e5;
  border-radius: 5px;
  height: 20px;
  margin: 5px;
  position: relative;
  box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.75);
  padding: 5px;
}

.space {
  height: 150px;
  width: 10px;
  background-color: #fff;
  margin: 10px;
}

.todo-first:before {
  position: absolute;
  top: -30px;
  height: 30px;
  content: "To Do (2)";
  font-weight: bold;
}

.doing-first:before {
  position: absolute;
  top: -30px;
  height: 30px;
  content: "Doing (5)";
  font-weight: bold;
}

.doing-first:after,
.done-first:after {
  position: absolute;
  top: -35px;
  left: -25px;
  width: 10px;
  height: 180px;
  z-index: 10;
  background-color: #fff;
  content: "";
}

.done-first:before {
  position: absolute;
  top: -30px;
  height: 30px;
  content: "Done (3)";
  font-weight: bold;
}

.bg-todo {
  position: absolute;
  background-color: #FFEFD3;
  width: 100vw;
  height: 150px;
  top: -30px;
  left: -10px;
  z-index: -1;
}

.bg-doing {
  position: absolute;
  background-color: #EFDCFF;
  width: 100vw;
  height: 150px;
  top: -30px;
  left: -15px;
  z-index: -1;
}

.bg-done {
  position: absolute;
  background-color: #DCFFEE;
  width: 10vw;
  height: 150px;
  top: -30px;
  left: -15px;
  z-index: -1;
}

.end {
  height: 150px;
  width: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">

  <div class="item todo-first">
    <div class="bg-todo"></div>
    Drink coffee
  </div>
  <div class="item">Go to work</div>

  <div class="space"></div>

  <div class="item doing-first">
    <div class="bg-doing"></div>
    1
  </div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>

  <div class="space"></div>

  <div class="item done-first">
    <div class="bg-done"></div>
    1
  </div>
  <div class="item">2</div>
  <div class="item">3</div>

  <div class="end"></div>

</div>

Itay Gal
la source
-2

Solution JS possible.

var ul = $("ul.ul-to-fix");

if(ul.find("li").length>{max_possible_rows)){
    if(!ul.hasClass("width-calculated")){
        ul.width(ul.find("li").eq(0).width()*ul.css("columns"));
        ul.addClass("width-calculated");
     }
}
user2323336
la source
Cela n'aiderait malheureusement pas, car la hauteur des éléments individuels peut varier. Le nombre d'éléments n'est donc pas intéressant ... Mais l'utilisation de la columnspropriété style pourrait être un point de départ pour une solution de travail. Peut-être ajuster le nombre de colonnes et la largeur de l'ul.
Anders Tornblad
1
J'ai également opté pour une solution JS à ce problème pour une application React sur laquelle j'ai travaillé. Il est conçu pour fonctionner avec des éléments de toutes tailles et de toutes tailles. Exemple ici: djskinner.github.io/react-column-wrap . Code source ici: github.com/djskinner/react-column-wrap
djskinner