Quantcast
Channel: Active questions tagged flexbox - Stack Overflow
Viewing all articles
Browse latest Browse all 1323

2D flex layout: row wrap jointly with column shrink

$
0
0

in the first code snippet below I show a layout using flex-flow: row wrap which has desirable layout properties but can exceed the maximum specified height of the container. The second code snippet shows a layout with flex-flow: column nowrap that accomplishes the desired behaviour to keep the total height of content to fit on one screen but for a single column.

I would like a layout that combines the desirable properties of both, specifically I'd like to accomplish these goals:

  1. all elements within the container will have equal widths which is either 30px or 20vw which ever is smaller. When there are more elements than fit on a single row they wrap to a new row, there are very often more than a single row as I typically have 7 to 16 tubes.
  2. the elements all have (quantized and statically specified) arbitrary heights, rows should be tall enough for their tallest element and won't necessarily be all the same height.
  3. if the final result (whole container, typically multiple rows) has a height that exceeds some maximum like 90vh it should shrink all rows by equal proportion to fit into the specified maximum height. (like they had flex-shrink: 1 property in the vertical direction)

I personally will not accept an answer that uses javascript to set styling but you are welcome to post one to help people with similar problems but less strict requirements. I would prefer a pure CSS solution that gets close enough than a solution that works perfectly so long as you setup all the right listeners.

My current layout

The following is the layout I am using presently, there is a #gameContainer which contains a list of .Tubes which each contain some .Balls. The variable --ball-size is set on the root container to make all the tube widths the same, each tube has a --capacity property to specify it's height as a multiple of --ball-size.

If you change the width of the main container and/or the ball size you will see that if it would exceed the specified max-height: 90vh on the main container it overflows overlapping with the footer content (setting the width to 140 accomplishes this for me but may differ if your viewing window is different size)

/** * sets a style property of the #gameContainer * the property set is the `name` field of the given input * and the value is the value from the input with the specified suffix. */function updateGameContainerStyle(inputForm, suffix=""){document.getElementById("gameContainer").style.setProperty(inputForm.name, inputForm.value + suffix);}
#gameContainer {  /* size of each ball, represents the size for clickable elements */  --ball-size: 30px;  width: 210px;  /* make the boundary of the container easier to identify */  border: 2px solid red;  /* desirable to have the game not take more than majority of height of screen */  max-height: 90vh;  /* plesent spacing between tubes */  display: flex;  flex-flow: row wrap;  gap: 5px;  justify-content: space-evenly;   /* when tubes of different heights are on same row align to the bottom */  align-items: flex-end;}.Tube {  display: flex;  /* order contents from bottom up */  flex-flow: column-reverse nowrap;  /* tube is one ball wide and `capacity` balls high */  width: var(--ball-size);  height: calc(var(--ball-size) * var(--capacity));  /* if the container size is small enough shrink elements to be a fifth or so of the container,  because of gaps and borders this generally means there will never be less than 3-4 tubes per row */  max-width: 20%;  /* to show border of tubes */  border: 3px solid blue;}.Ball {  /* since the hight of of tube is ball-size*capacity this ends up     being just ball-size, but is dynamic so if tube had to      be shrunk it stills is the right proportion */  flex-basis: calc(100%/var(--capacity));  align-content: center;  text-align: center;}.Ball.A {  background-color: rgba(255,0,0,0.5);}.Ball.B {  background-color: rgba(0,0,255,0.5);}.Ball.C {  background-color: rgba(0,255,0,0.5);}
<body><header><label>--ball-size = <input type="number" value="30" name="--ball-size" onchange="updateGameContainerStyle(this, 'px');">px</label> <br><label>width = <input type="number" value="210" name="width" onchange="updateGameContainerStyle(this, 'px');">px</label> <br></header><main id="gameContainer"><div class="Tube" style="--capacity:4"><div class="Ball A"> A </div><div class="Ball B"> B </div><div class="Ball A"> A </div></div><div class="Tube" style="--capacity:4"><div class="Ball A"> A </div><div class="Ball B"> B </div><div class="Ball C"> C </div><div class="Ball C"> C </div></div><div class="Tube" style="--capacity:4"><div class="Ball B"> B </div><div class="Ball B"> B </div></div><div class="Tube" style="--capacity:3"><div class="Ball C"> C </div><div class="Ball A"> A </div></div><div class="Tube" style="--capacity:3"><div class="Ball C"> C </div></div></main><footer>    this is footer content</footer></body>

I would like to effectively specify the .Tube's height property as a flex-basis in the column direction and also flex-shrink: 1, this means it will shrink the contents to fit within my maximum allowable height. However this is obviously not possible at the same time as using flex-flow: row wrap.

single column example

To demonstrate my desirable behaviour I've included a near identical copy of the above snippet with only the .Tube {height} and #gameContainer {flex-flow} properties altered and fewer tubes to make it more reasonable to see. If you change the ball-size you will see for lower values the size of the whole board is variable but at a certain size it stops getting taller so the whole container still fits within 90vh and the footer is never overlapped.

(also note the max-width: 20% is still in effect here which is why the tubes won't grow to be wider than a fifth of the container)

function updateGameContainerStyle2(inputForm, suffix=""){document.getElementById("gameContainer2").style.setProperty(inputForm.name, inputForm.value + suffix);}
#gameContainer2 {  /* size of each ball, represents the size for clickable elements */  --ball-size: 30px;  width: 210px;  /* make the boundary of the container easier to identify */  border: 2px solid red;  /* desirable to have the game not take more than majority of height of screen */  max-height: 90vh;  /* plesent spacing between tubes */  display: flex;  flex-flow: column nowrap;  gap: 5px;  justify-content: space-evenly;   /* when tubes of different heights are on same row align to the bottom */  align-items: flex-end;}.Tube {  display: flex;  /* order contents from bottom up */  flex-flow: column-reverse nowrap;  /* tube is one ball wide and `capacity` balls high */  width: var(--ball-size);  flex-basis: calc(var(--ball-size) * var(--capacity));  flex-shrink: 1;  /* if the container size is small enough don't shrink elements to be thinner than a fifth or so of the container  because of gaps and borders this generally means there will never be less than 3-4 tubes per row */  max-width: 20%;  /* to show border of tubes */  border: 3px solid blue;}.Ball {  /* since the hight of of tube is ball-size*capacity this ends up being just ball-size, but is dynamic so if tube had to be shrunk it stills is the right proportion */  flex-basis: calc(100%/var(--capacity));  align-content: center;  text-align: center;}.Ball.A {  background-color: rgba(255,0,0,0.5);}.Ball.B {  background-color: rgba(0,0,255,0.5);}.Ball.C {  background-color: rgba(0,255,0,0.5);}
<body><header><label>--ball-size = <input type="number" value="30" name="--ball-size" onchange="updateGameContainerStyle2(this, 'px');">px</label> <br><label>width = <input type="number" value="210" name="width" onchange="updateGameContainerStyle2(this, 'px');">px</label> <br></header><main id="gameContainer2"><div class="Tube" style="--capacity:4"><div class="Ball A"> A </div><div class="Ball B"> B </div><div class="Ball C"> C </div></div><div class="Tube" style="--capacity:2"><div class="Ball A"> A </div><div class="Ball B"> B </div></div><div class="Tube" style="--capacity:3"><div class="Ball B"> B </div><div class="Ball C"> C </div></div></main><footer>    this is footer content</footer></body>

If I changed the structure of the HTML to have intermediate <div class="row"> I could get the gameboard to be column nowrap and the rows to be flex-shrink:1; flex-flow: row nowrap which would meet the sizing criteria but would not redistribute elements amongst rows when the container resizes.

Is there a way to both have a row wrap like behaviour and shrink behaviour in the column direction? I suspect there may be a solution using grid layout but the closest I could get was using:

#gameContainer {   display: grid;   grid-template-columns: repeat(auto-fill, clamp(1ch, var(--ball-size), 20vw));}

which does give basically the same behaviour as the first version, it creates an automatic number of columns that use the ball-size variable as the width but clamped at being no wider than 20vw (and at least 1 character wide). However I am unaware of any equivalent feature to flex-shrink using grid display.


Viewing all articles
Browse latest Browse all 1323

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>