Create a CSS Grid that flows by column, with dynamic columns and rows based on content
Asked Answered
A

3

16

I have a dynamic number of elements (all of equal dimensions). I want to position them in a grid, such that they are ordered in columns, with a dynamic number of columns based on the available container width, and with a dynamic number of rows based on the number of elements.

For example, assuming there are 9 elements:

1 4 7
2 5 8
3 6 9

But if the container width expands:

1 3 5 7 9
2 4 6 8

Or shrinks:

1 6
2 7
3 8
4 9
5

This works fine when the items are positioned by row, but not by column. Also, if I explicitly set the number of rows, it works fine, but if I use auto-fill for both rows and columns, it just displays everything in a single row.

Here's a simple example I would expect to be rendered as a 3x3 grid: https://codepen.io/anon/pen/ZxPQNd

#grid {
  width: 320px;
  border: 1px solid red;
  display: grid;
  grid-gap: 10px;
  grid-auto-flow: column;
  grid-template-rows: repeat(auto-fill, 100px);
  grid-template-columns: repeat(auto-fill, 100px);
  grid-auto-columns: 100px;
  grid-auto-rows: 100px;
}

#grid>div {
  background: lime;
}
<div id="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>
Anthroposophy answered 10/4, 2018 at 23:9 Comment(4)
Have you tried flex and media queries?Cottar
Why would that be rendered as a 3x3 grid? It takes up as much space as possible for the columns, which in this case is sufficient.Mcintire
I think that this problem is best suited to column-count than to display: gridEnclasp
Trying to avoid media queries, since they require discrete sizes, and column-count still requires an explicit number of columns, whereas I want it to automatically figure out the number of columns that will fit into the available space.Anthroposophy
A
8

Your desired layout is achievable using CSS multi-column layout using column-width property. Demo:

#grid {
  border: 1px solid red;
  column-width: 100px;
  column-gap: 10px;
}

#grid > div {
  break-inside: avoid-column; /* Prevent element from breaking */
  page-break-inside: avoid; /* Prevent element from breaking in Firefox */
  background: lime;
  width: 100px;
  height: 100px;
  margin-bottom: 10px;
}
<div id="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>
Abel answered 24/4, 2018 at 5:55 Comment(0)
G
3

This works fine when the items are positioned by row, but not by column.

This is because block elements consume the full width of their parent, by default.

But this behavior does not extend to height. Most elements are, by default, the height of their content (i.e., no extra space).

Essentially, your container is set to width: 100% and height: auto. For the row behavior in the vertical axis add height: 100vh.

More info: How to make a div 100% height of the browser window?

#grid {
  width: 320px;
  border: 1px solid red;
  display: grid;
  grid-gap: 10px;
  grid-auto-flow: column;
  grid-template-rows: repeat(auto-fill, 100px);
  grid-template-columns: repeat(auto-fill, 100px);
  grid-auto-columns: 100px;
  grid-auto-rows:  100px;
  height: 100vh; /* NEW */
}

#grid > div {
  background: lime;
}
<div id="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>

In terms of the re-ordering of grid items based on screen size, with grid-auto-flow: row the items will respond to container width. With grid-auto-flow: column, the items will respond to container height. To make the column-flowing items respond to width re-sizing will require some trickery.

More info: Make grid items fill columns not rows

Gregson answered 10/4, 2018 at 23:44 Comment(3)
But I don't want the grid to take up the full height of the container, let alone the viewport. I only want it to be as high as the content requires.Anthroposophy
How is the grid container supposed to know what the content requires?Gregson
By calculating it? This may not be possible currently, but it's a relatively simple calculation. If the container is 400px wide, and there are 9 child elements, each 100x100px, with a 10px gap, then it is easy to calculate there must be 3 columns (320px), and 3 rows. Obviously this can only work when the child dimensions are known (grid-auto-rows/columns?). In my head what I'm describing is pretty simple, but it seems like this simply isn't possible currently with CSS grids.Anthroposophy
S
2

The OP has the question exactly right. You've got an indeterminate number of elements to display and an indeterminately wide viewport in which to display them. And you want to display the elements in a grid.

Depending on the width of the viewport, you're going to want more or fewer columns. So the question is how to set that up.

One solution that comes to mind is to use CSS media queries to test the viewport width and then alter your layout accordingly. But that's tedious and prone to error. What you really want is a solution that works independently of viewport width.

The simplest solution, if all your elements are of uniformly fixed width, is to use flexbox. You might try something like this:

#grid {
  display: flex;
  flex-wrap: wrap;
}

#grid div {
  flex: 1 0 110px;
  background-color: yellow;
  width: 100px;
  height: 100px;
  margin: 10px;
  max-width: 100px;
  text-align: center;
}
<div id="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>

But you might also want the elements to expand to fill the horizontal space while still maintaining a uniform grid. For this you can use a CSS grid. The trick is to use a repeat and a minmax on the grid-template-columns property:

#grid {
  display: grid;
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    grid-template-rows: repeat(auto-fill, 1fr);
    column-gap: 10px;
    row-gap: 10px;
}

#grid div {
  background-color: yellow;
  height: 100px;
  text-align: center;
}
<div id="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>
Swat answered 30/4, 2021 at 1:57 Comment(1)
The solutions presented here don't account for the 'top to bottom, then left to right' sort requirement (the tough part), and instead sort by 'left to right, then top to bottom' (which is trivial).Durant

© 2022 - 2024 — McMap. All rights reserved.