33

Although I have not yet been able to find an answer, the question is simple: Is there a way, other than brute force, to count the number of columns in a responsive grid?

#grid-container {
  width: 100%;
  height: 85%;
  position: relative;
  padding: var(--gap); /* adjusted with JS to make var(--gap) responsive */
  display: grid;
  grid-gap: var(--gap); /* adjusted with JS to make var(--gap) responsive */
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  box-sizing: border-box;
  background: skyblue;
}

.grid-item {
  width: 100%;
  min-width: 120px;
  max-width: 450px;
  height: ; /* adjusted with JS on resize events to roughly maintain proportions */
  min-height: 192px;
  border-radius: 10px;
  background: #333;
}

The reason I ask is because I have given the grid items a max-width that causes a massive gap at the very last breakpoint, which I would like to be able to detect instead of setting an explicit media query.

enter image description here

oldboy
  • 5,729
  • 6
  • 38
  • 86
  • you have set `max-width: 450px` but in the last breakpoint you want to allow it to be more than this value? – kukkuz Mar 17 '19 at 06:45
  • if you increase the `grid-gap` you can see that in other breakpoints as well you will start seeing this *gap*... consider removing `max-width`? – kukkuz Mar 17 '19 at 06:51
  • @kukkuz i simply want to be able to count the number of columns that are being displayed at any given time without creating some complex algorithm – oldboy Mar 17 '19 at 18:09
  • I could be totally misunderstanding this but aren't you simply looking for the `:last-child` selector? Or do you explicitly need the amount of columns? If so and you have only columns inside your `#grid-container`, you could use the [`childNodes property`](https://www.w3schools.com/jsref/prop_node_childnodes.asp) – Evochrome Mar 17 '19 at 19:06
  • @Evochrome youre way off the mark lol. in a responsive grid, like the one in my question, the number of columns increases or decreases as you resize the window. all i want to do, without creating some brute force algorithm, is to be able to know how many columns are being displayed at any given time. also, `childNodes` would simply return an array of child nodes?? – oldboy Mar 17 '19 at 19:57
  • Oops... My definition of columns went wrong in my head :p – Evochrome Mar 18 '19 at 00:07
  • @Evochrome hahah – oldboy Mar 18 '19 at 00:23

3 Answers3

51

One way to get the number of rows/columns of a css grid is by using the grid-template-rows or grid-template-columns from the computed style of the grid window.getComputedStyle(grid).

The returned values are always transformed to separated pixel values (e.g. 20px 20px 50px), where each value represents the size of the respective column/row. All that's left to do is splitting up the string into an array and counting the number of values.

const gridComputedStyle = window.getComputedStyle(grid);

// get number of grid rows
const gridRowCount = gridComputedStyle.getPropertyValue("grid-template-rows").split(" ").length;

// get number of grid columns
const gridColumnCount = gridComputedStyle.getPropertyValue("grid-template-columns").split(" ").length;

console.log(gridRowCount, gridColumnCount);

Here is the full snippet (Codepen):

function getGridData () {
    // calc computed style
  const gridComputedStyle = window.getComputedStyle(grid);
  
  return {
    // get number of grid rows
    gridRowCount: gridComputedStyle.getPropertyValue("grid-template-rows").split(" ").length,
    // get number of grid columns
    gridColumnCount: gridComputedStyle.getPropertyValue("grid-template-columns").split(" ").length,
    // get grid row sizes
    gridRowSizes: gridComputedStyle.getPropertyValue("grid-template-rows").split(" ").map(parseFloat),
    // get grid column sizes
    gridColumnSizes: gridComputedStyle.getPropertyValue("grid-template-columns").split(" ").map(parseFloat)
  }
}

window.addEventListener("DOMContentLoaded", outputGridData);
window.addEventListener("resize", outputGridData);

function outputGridData () {
  const gridData = getGridData();
  output.textContent = `
    Rows: ${gridData.gridRowCount}
    Columns: ${gridData.gridColumnCount}
    Rows sizes: ${gridData.gridRowSizes}
    Column sizes: ${gridData.gridColumnSizes}
  `;
}
#grid {
  display: grid;
  grid-gap: 20px;
  grid-template-columns: repeat(auto-fill, 200px);
}


#output {
  white-space: pre-wrap;
}

.A, .B, .C {
  font-family: Arial;
  font-weight: 600;
  font-size: 2rem;
  border-radius: 50% 50%;
  width: 80px;
  height: 80px;
  text-align: center;
  line-height: 80px;
  box-shadow: -3px 5px 20px -3px #AAA;
  border: solid 10px #fff;
  color: #fff;
}

.A {
  background: #ffc300;
}

.B {
  background: #c70039;
}
.C {
  background: #581845;
}
<div id="output"></div>
<div id="grid">
 <div class="A">A</div>
 <div class="B">B</div>
 <div class="C">C</div>
 <div class="A">A</div>
 <div class="B">B</div>
 <div class="C">C</div>
  <div class="A">A</div>
 <div class="B">B</div>
 <div class="C">C</div>
 <div class="A">A</div>
 <div class="B">B</div>
 <div class="C">C</div>
</div>
Robbendebiene
  • 4,215
  • 3
  • 28
  • 35
  • does this work for responsive grids? for instance, as the grid and the number of columns and rows shrinks or grows, does this value update accordingly? – oldboy Oct 15 '19 at 19:18
  • @BugWhisperer Yes, just check out the linked Codepen example and resize the window – Robbendebiene Oct 15 '19 at 22:03
  • This is brilliant! Do you know of any computed styles on individual grid items, that we could use to figure out the coordinates? ie: row 2 column 3? – Chase Jan 29 '20 at 20:33
  • 2
    This is excellent, thanks, although… I have found that sometimes the computed columns ends in ' 0px', leading the column calculation to be out by 1. Simply fixed by adding `.replace(' 0px', '')` before the `.split('')`. – Jonathan Schofield Mar 13 '20 at 12:56
  • Hmm, I spoke too soon. For reasons I'm not clear on, in Chrome 80 and Firefox 74, sometimes the computed style returns lots of `0px` columns even though they are rendered at the same width as non-zero width columns. Sometimes the trailing `0px` is actually extraneous (not visible) and sometimes its not. Weird. And unresolvable? – Jonathan Schofield Mar 16 '20 at 14:40
  • Here's a tweet about the issue, with a reference to a CodePen test case: https://twitter.com/schofeld/status/1239574151079354373?s=20 – Jonathan Schofield Mar 16 '20 at 15:29
  • 1
    @JonathanSchofield I've tested your solution using `replace(/ 0px/g, "")` (regex instead of string) and it works as expected for me. It counts only the visible non zero columns/rows. I never encountered the case that a zero width row had the same width as a non-zero width row. – Robbendebiene Mar 17 '20 at 23:00
  • Hi @Robbendebiene. Did you see and play with my CodePen? The thing is I've found scenarios where the `0px` is not redundant: see [this tweet](https://twitter.com/schofeld/status/1239593481124950018). Haven’t yet managed to replicate that in my Pen. – Jonathan Schofield Mar 18 '20 at 15:39
  • I have tried this in my application and it also works in responsive grid. This is really helpful. Thanks – abhijeet_shela Aug 23 '23 at 15:46
10

I think the easiest way without brute force is to consider a simple division. You can easily find the width of the container and each column is defined by minmax(300px,1fr) and we know the gap. Using all these information the calculation should be done like below:

If we will have N columns then we will have N-1 gaps. We also know that W should at least be 300px and cannot be more than a value (we will call Wmax).

Let's suppose the gap is equal to 10px.

If N=2 and each column is equal to 300px we will have the container width equal to 300px*2 + 10px*1 = 610px.

If N=3 and each column is equal to 300px we will have 300px*3 + 10px*2=920px.

Now it's clear that if the container width is between 610px and 920px we will have 2 columns because there is no space to hold 3 columns but enough space to hold 2 columns that we expand to fill the remaining space (using 1fr) so Wmax in this case is (920px - 10px)/2 = 455px. In other words, the width will vary from 300px to 455px when having 2 columns.

So if we take the formula 300px*N + 10px*(N-1) = Wc with Wc our container width you will see that N is equal to 2 when Wc=610px and 3 when Wc=920px and between we will have a result in [2,3] so we simply round the value to the smallest one (2 in this case) and we will have our column number.

Here is a basic example:

var gap = 10;
var minW = 200;

var Wc = document.querySelector('.grid').offsetWidth;
var N = Math.floor((Wc+gap)/(minW+gap));
console.log(N);


window.addEventListener('resize', function(event){
   Wc = document.querySelector('.grid').offsetWidth;
   N = Math.floor((Wc+gap)/(minW+gap));
   console.log(N);
});
.grid {
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(200px,1fr));
  grid-gap:10px;
}
span {
  min-height:50px;
  background:red;
}
<div class="grid">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

In case you don't know the value of gap and min-width you can consider getComputedStyle() to get the different values. In this case, the N should already be an Integer because grid-template-columns will give us the computed width of each column (in practice, we will still have some fraction due to rounding ).

var grid = document.querySelector('.grid');

var gap = parseFloat(window.getComputedStyle(grid,null).getPropertyValue("column-gap"));
var minW = parseFloat(window.getComputedStyle(grid,null).getPropertyValue("grid-template-columns"));

var Wc = document.querySelector('.grid').offsetWidth;
var N = (Wc+gap)/(minW+gap);
console.log(N);


window.addEventListener('resize', function(event){
   Wc = document.querySelector('.grid').offsetWidth;minW = parseFloat(window.getComputedStyle(grid,null).getPropertyValue("grid-template-columns"))
   N = (Wc+gap)/(minW+gap);
   console.log(N);
});
.grid {
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(200px,1fr));
  grid-gap:10px;
}
span {
  min-height:50px;
  background:red;
}
<div class="grid">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • i would almost if not consider this brute force, and id also have to take into account padding, etc. but despite that, the issue with this approach is that columns are ***at least*** `300px` and sometimes not `300px`?? ideally, id like to be able to do this with CSS alone, but we can both agree that its definitively impossible since there is no way of knowing `innerWidth`, correct? ill have to come back to this when i get some more free time – oldboy Mar 19 '19 at 21:35
  • @Anthony the *at least 300px* is simply to explain a particular case so you can understand the logic, as you can see in the last example, it's dynamic and I used 200px not 300px ... concerning padding, it's also easy because with JS you can get the width minus padding and border, etc. And of course with only CSS it's impossible. – Temani Afif Mar 19 '19 at 21:54
  • oh so what youre saying is that `300px` or that value of `min` in `minmax` can be or is used to in the formula/algorithm without there being problems. – oldboy Mar 20 '19 at 18:12
  • in order to calculate `Wmax`, why do you minus `10px` and then divide the total by `2`? why are you calculating `Wmax` this way?? is it that subtracting the `10px` represents a grid with one fewer columns? – oldboy Mar 24 '19 at 23:03
  • if i wanted to expand the padding on each side of the grid to prevent rows with different numbers of children, this gets so tricky considering `var(--grid-gap)` in `#grid { grid-gap: var(--grid-gap); padding: 0 var(--grid-gap) }` is responsive. – oldboy Mar 24 '19 at 23:12
  • @Anthony I remove 1 gap (10px) then I divide by 2 (numbers of columns) it was the example for N=2 ... with padding you simply do the same but you consider width minus padding (here is a related question : https://stackoverflow.com/a/38929456/8620333) – Temani Afif Mar 24 '19 at 23:25
  • ahhhh ic. hm im not sure that example u linked is suitable since the the padding and gap in my case is responsive. and the padding and gap effect the width of each column, so if i responsively increase the width of the padding, then itll change the width of the columns – oldboy Mar 24 '19 at 23:49
  • @Anthony it will change nothing, responsive or not it's the same because you will read the computed value. Did you check the last snippet? .. I start my answer with a static example simply to explain, that's why I used fixed value and some examples so you get how the calculation is done. Then the last snippet is reading the value from the CSS so whataver the gap and padding will be it's the same, the logica won't change – Temani Afif Mar 25 '19 at 00:00
  • @Anthony here is an example where everything is dynamic and the calculation I made will give you the result you want : https://jsfiddle.net/xem03z94/ – Temani Afif Mar 25 '19 at 00:06
  • perfect!! ill implement that when as soon as i get a moment. thanks so much. u didnt have to do that – oldboy Mar 25 '19 at 00:55
2

I liked the answer from @Robbendebiene and turned it into a simple react/ts hook that gets nCols. I needed such a hook for a project. Posting here, in case it saves anyone some time!

/**
 * @param containerRef Ref to HTMLElement with grid styles applied
 * @param defaultCount Count to return during SSR or if the containerRef is not yet set
 * @returns number of columns visible in the grid (updates on resize or containerRef change)
 */
const useVisibleGridCols = <T extends HTMLElement>(
  containerRef:RefObject<T>, defaultCount:number = 1):number => {
  const [visibleCols, setVisibleCols] = useState(defaultCount);

  useEffect(() => {
    const calcColumns = () => {
      // Abord if no ref
      if (!containerRef.current) return;

      // infer # of cols by parsing the computed grid-template-columns
      // see https://stackoverflow.com/a/58393617/5389588
      const containerComputerStyle = window
        .getComputedStyle(containerRef.current);
      const nCols = containerComputerStyle
        .getPropertyValue('grid-template-columns')
        .replace(' 0px', '')
        .split(' ')
        .length;

      // Update
      setVisibleCols(nCols);
    };

    // Do it now
    calcColumns();

    // And on resize
    window.addEventListener('resize', calcColumns);
    return () => {
      window.removeEventListener('resize', calcColumns);
    };
  }, [containerRef, setVisibleCols]);

  return visibleCols;
};