javascriptcssgrid-layout

Count how many elements in dynamic row


When using CSS Grid Layout to dynamically arrange elements in rows the browser calculates how many items go in a row depending on the size of the viewport. I'm looking for a way using JavaScript to determine how many items are in each row at a given viewport size. My purpose is to fill the inevitable part empty row at the end.

For example, given the CSS and HTML listed below, and at a particular viewport size results in this layout:

CSS Grid Layout Narrow, Dynamic Rows, Part Empty Last Row

And at a wider viewport results in this layout:

CSS Grid Layout Wide, Dynamic Rows, Part Empty Last Row

Each have missing elements in the last row.

Having the maximum number of elements a row I can dynamically load more elements to fill in the last row so it's the same number as the rest.


Example CSS and HTML:

<style>

* {
  box-sizing: border-box;
}
body {
  padding: 1rem;
}

main {
  max-width: 500px;
  margin: 0 auto;
}
article {
  margin: 1rem 0;
  overflow: hidden;
}

main {
  max-width: 10000px;
  margin: 0;
}
article {
  margin: 0;
}
.listings {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 1rem;
}

.listings {
  font-family: Avenir, Roboto, Helvetica, san-serif;
  font-size: 80%;
}
.listing {
  display: flex;
  flex-flow: column;
  border: 1px solid silver;
}
.listing > h1 {
  margin: 1rem 1rem 0;
}
.listing > div {
  margin: 0.25em 1rem 1rem;
}
.listing > img {
  width: auto;
  max-height: 200px;
  order: -1;
  align-self: center;
}

</style>
<main class="listings">
    <article class="listing">
        <img src="images/computer.jpg">
        <h1>87,500</h1>
        <div>1008[3/2]0</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>30,000</h1>
        <div>952[2/1]0</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>70,000</h1>
        <div>1090[3/1]0</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>11,000</h1>
        <div>828[2/1]2</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>25,000</h1>
        <div>1484[2/1]0</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>199,000</h1>
        <div>2160[3/2]0</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>42,000</h1>
        <div>1509[3/2]0</div>
    </article>

    <article class="listing">
        <img src="images/computer.jpg">
        <h1>230,000</h1>
        <div>1885[3/2]0</div>
    </article>
</main>


Solution

  • elem.getBoundingClientRect().left

    The elem.getBoundingClientRect().left property can be used to get the number of dynamic rows in a CSS grid layout. Just loop through the article elements NodeList and compare the element's getBoundingClientRect().left value to the previous. The compared value will increase within each row then drop when starting in the next row. Count the increments until the first drop occurs to determine the number of elements in each row.

    var articles = document.querySelectorAll('article')
    console.log(articles[0].getBoundingClientRect().left) // 16
    console.log(articles[1].getBoundingClientRect().left) // 238.156
    console.log(articles[2].getBoundingClientRect().left) // 460.312
    console.log(articles[3].getBoundingClientRect().left) // 682.468
    console.log(articles[4].getBoundingClientRect().left) // 904.625
    console.log(articles[5].getBoundingClientRect().left) // 1126.78
    console.log(articles[6].getBoundingClientRect().left) // 16
    

    Therefore we just need to count the increases in value between the previous left value and the next and once it drops we have our row count.


    Here using call to apply array method reduce on the NodeList. Using reduce to carry forward the each value to compare to the next value:

    var rowlen = Array.prototype.reduce.call(document.querySelectorAll('article'), function (prev, next) {
      if ( !prev[2] ) {
        var ret = next.getBoundingClientRect().left
        // if increasing, increment counter
        if ( !(prev[0] > -1 && ret < prev[1]) ) { prev[0]++ }
        else { prev[2] = 1 } // else stop counting
      }
      return [prev[0],ret, prev[2]] // [counter, elem, stop-counting]
    }, [0,null,0])[0]
    

    Codesandbox.io "complete partial row" example.

    Or old-school JS, sporting a for loop nested in an IIFE:

    var rowlen = (function () {
      for ( var ii = 0, arts = document.querySelectorAll('article'), 
            len = arts.length, el = 0, ell = 0; 
          ii < len; ii++ ) {
        el = arts[ii].getBoundingClientRect().left
        if ( el > ell || 0 === ell ) { ell = el }
        else { return ii; }
      }
    }())
    

    Codesandbox.io "drop partial row" example.