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:
And at a wider viewport results in this layout:
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/puter.jpg">
<h1>87,500</h1>
<div>1008[3/2]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>30,000</h1>
<div>952[2/1]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>70,000</h1>
<div>1090[3/1]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>11,000</h1>
<div>828[2/1]2</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>25,000</h1>
<div>1484[2/1]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>199,000</h1>
<div>2160[3/2]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>42,000</h1>
<div>1509[3/2]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>230,000</h1>
<div>1885[3/2]0</div>
</article>
</main>
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:
And at a wider viewport results in this layout:
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/puter.jpg">
<h1>87,500</h1>
<div>1008[3/2]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>30,000</h1>
<div>952[2/1]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>70,000</h1>
<div>1090[3/1]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>11,000</h1>
<div>828[2/1]2</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>25,000</h1>
<div>1484[2/1]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>199,000</h1>
<div>2160[3/2]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>42,000</h1>
<div>1509[3/2]0</div>
</article>
<article class="listing">
<img src="images/puter.jpg">
<h1>230,000</h1>
<div>1885[3/2]0</div>
</article>
</main>
Share
Improve this question
edited Nov 16, 2023 at 17:59
bloodyKnuckles
asked Apr 17, 2018 at 21:51
bloodyKnucklesbloodyKnuckles
12.1k4 gold badges31 silver badges38 bronze badges
4
- possible duplicate of : stackoverflow./questions/49043684/… – Temani Afif Commented Apr 17, 2018 at 21:52
- and what about when browser resize ? – Temani Afif Commented Apr 17, 2018 at 21:53
- @TemaniAfif There is a lot of plexity to the question you linked to, and therefore the answers. The use case I have here is an evenly aligned, minimally interactive grid of elements, somewhat closely spaced, and not expecting the user to resize the browser—which I think covers many use cases. And in the case the user does resize the browser it's no worse than it was without this example. – bloodyKnuckles Commented Apr 17, 2018 at 22:57
- 1 i know that's why it's a possible duplicate ;) i didn't close as duplicate ... but it's almost the same thing, so both question should be linked – Temani Afif Commented Apr 17, 2018 at 23:05
2 Answers
Reset to default 7elem.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 pare the element's getBoundingClientRect().left
value to the previous. The pared 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 pare 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 "plete 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.
elem.clientWidth
This example uses the element property clientWidth
to calculate the number of elements in each row.
var rowlength = Math.floor( // round down to account for some spacing
document.body.clientWidth / // width of viewport divided by...
document.querySelectorAll('article')[0].clientWidth // width of an element
)
This gives me the number of items in each row, except possibly the last row.
Complete Last Row
I know I've loaded 8 elements initially, so I can now calculate how many items I need to plete the last row:
var numelementsloaded = 8
Alternately I can count the number of loaded elements:
var numelementsloaded = document.querySelectorAll('article').length
The modulus (division remainder) operator will give me the number of elements in the last row:
var numelementslastrow = elementsloaded % rowlength
Now calculate the number of elements needed to plete last row:
var numelementsneeded = rowlength - numelementslastrow
All together and pressed, with a bonus:
var rowlength = Math.floor(document.body.clientWidth/document.querySelectorAll('article')[0].clientWidth)
var numelementsneeded = rowlength - (document.querySelectorAll('article').length % rowlength)
// and duplicating the first few elements to illustrate pleting the last row
for ( var ii = 0; ii < numelementsneeded; ii++ ) {
document.querySelector('main').appendChild(
// here is where I'd pull in new items from the server
document.querySelectorAll('article')[ii].cloneNode(true)
)
}
And that pletes the last row:
Codesandbox.io "plete last row" example.
Drop Last Row
Another, technically simpler, option is to—rather than add new items—drop all the items in the last, partial row:
var rowlength = Math.floor(document.body.clientWidth/document.querySelectorAll('article')[0].clientWidth)
var numelementslastrow = document.querySelectorAll('article').length % rowlength
// and dropping the elements in the last row
for ( var ii = 0; ii < numelementslastrow; ii++ ) {
document.querySelector('main').removeChild(
document.querySelector('article:last-child')
)
}
If the last row is full, it stays. For example, if there are 8 items and each row has 4 items, 8 mod 4 = 0, therefore the for
loop does not execute.
Codesandbox.io "drop partial row" example.