CSS to completely fill container with many identical children

Viewed 124

Is there a way to fill a given container with an arbitrary number of square, identically-sized children (as few as five and as many as 50) so that the children:

  1. fill the container leaving as little vertical and horizontal space as possible; and
  2. do not overflow the container?

For example, imagine a box of 17 circular avatars that have each been scaled up until any further size increase will cause them to wrap and flow beyond the bottom of the container.

I know that I can use JS to calculate the optimal scale of the children to fill the container, but I feel like either flex or grid has this functionality built in.

This is about as close as I have gotten:

.container {
    border: 1px solid tomato;
    display: flex;
    flex-wrap: wrap;
    gap: 1vw;
    height: 100vh;
    justify-content: center;
    width: 100vw;
}

.container > div {
    background-color: coral;
    flex: 0 0 12%;
    margin: .5em;
}

.container .div::before {
    content: '';
    display: block;
    padding-top: 100%;
}
    <div class="container">
        <!-- 32 children -->
        <div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
        <div></div><div></div>
    </div>

Apologies if this is a duplicate. I've done my honest best to seek the answer before asking.


[EDIT: adding detail]

Here's the desired result showing a box with a FIXED size and items sized until the container is completely filled. If the box were a different size than this or there were a different number of items inside, the items would scale until they each fit inside without overflowing. The alignment of the two orphans at the end is unimportant, but they must be the same size as the rest of the items.

Desired result

3 Answers

This is (sort of) possible to achieve with flex. You can make sure the gaps between each column are even with the space-evenly property on justify-content. However, to get the rows to have the same gap between them as the columns is not possible without javascript. Instead, you'll have to manually define a row gap via row-gap.

.flex-container {
  width: 300px;
  
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;
  row-gap: 8px; /* Issue on this line */
  
  background-color: blue;
}

.flex-item {
  padding: 30px;
  background-color: red;
}
<div class="flex-container wrap">
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
  <div class="flex-item">1</div>
</div>


As a side note, this is definitely not possible to do in grid due to the fact that you cannot have implicit size definitions in template-columns. I.e. the following will not work:

grid-template-columns: repeat(auto-fit, minmax(max-content, 1fr))

It seems the only way to do this is with Javascript.

I at first thought of using CSS tables to do this, but you need to determine how much lines exist manually, so that wasn't an option. But if you do want to do it that way, this JSFiddle will be of help.

Also, defining the values manually (eg. 4em) like in @ThomasRyan's answer will not be of help because you need to mind different container sizes (If you want to make the container width 10px instead of 100vw).

Here we simply generate the child count, then get the width and height by dividing the container width/height by the child count, adding 10px to mind margin.

var container = document.querySelector('.container');
var childCount = random(5, 50);

var childHeight = container.clientHeight / childCount + 10;
var childWidth = container.clientWidth / childCount + 10;

for (var i = 0; i < childCount; i++) {
  container.innerHTML += '<div style="height:' + childHeight + 'px;width:' + childWidth + 'px"></div>';
}

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}
body {
  margin: 0;
}

.container {
  display: flex;
  flex-wrap: wrap;
  height: 100vh;
  width: 100vw;
  justify-content: center;
  align-items: center;
}

.container > div {
  background-color: coral;
  margin: 10px;
}
<div class="container"></div>

Is this what you want

.container {
    border: 1px solid tomato;
    display: flex;
    flex-wrap: wrap;
    gap: 1vw;
    height: 100vh;
    justify-content: center;
    width: 100vw;
}

.container > div {
    background-color: coral;
    flex: 0 0 4em;
    
    margin: .5em;
}

.container .div::before {
    content: '';
    display: block;
    padding-top: 100%;
}
    <div class="container">
        <!-- 32 children -->
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
    </div>

Related