I developed this CSS Masonry.
It works with a CSS grid
with grid-auto-rows: 1px
. There is a JS recalc
script in there, which recalculates the grid-row-end
values (with the span
prefix). Apart from that, the layout is totally up to the Browser's engine.
(The --raster
property is used to set vertical raster on or off. With the raster enanbled the heights of the items are calculated as multiples of the gaps, so the bottoms of the grid items align a little bit nicer).
On slow machines (esp. in Chrome) it might become a little bit laggy (not too dramatically), so I want to work on the performance a little bit.
I know, I could use a debounce on the resize
event handler, but that's not the point here. I want to improve on the performace of the recalc
function alone.
I used getElementsByClassName
instead of querySelectorAll
to improve performance. I needed to filter the resulting Elements by only direct descendants (simulate querySelectorAll(':scope > .item')
). Don't know, if this is slower in the end than using querySelectorAll
in the first place.
I used getBoundingClientRect()
to calculate height of the items, so there doesn't seem to be any improvement there.
Is there anything else I could improve?
"use strict";
console.clear();
{
const gridClass = 'grid';
const itemClass = 'item';
const grid = document.getElementsByClassName(gridClass);
const recalc = () => {
Array.from(grid).forEach(gr => {
// const items = gr.querySelectorAll(`:scope > .${itemClass}`);
const items = gr.getElementsByClassName(itemClass);
Array.from(items)
.filter(el => el.parentElement === gr)
.forEach(it => {
it.style.setProperty('grid-row-end', 'span 1');
const h = parseInt(Math.ceil(it.children[0].getBoundingClientRect().height));
const gap = parseInt(getComputedStyle(it).getPropertyValue('--gap'));
const raster = !!parseInt(getComputedStyle(it).getPropertyValue('--raster'), 10)
let span;
if (raster) {
span = parseInt(Math.ceil((h + gap) / gap) * gap);
} else {
span = parseInt(Math.ceil(h + gap));
}
it.style.setProperty('grid-row-end', `span ${span}`);
})
})
}
window.addEventListener('resize', recalc);
window.addEventListener('load', recalc);
recalc()
}
.grid {
--cols: 12;
--gap: 16;
--raster: 1;
display: grid;
grid-template-columns: repeat(var(--cols, 12), calc((100% - 1px * var(--gap, 20) * (var(--cols, 12) - 1)) / var(--cols, 12)));
grid-auto-rows: 1px;
grid-gap: 0px calc(1px * var(--gap, 20));
margin-top: calc(-1px * var(--gap, 20));
}
@media screen and (max-width: 900px) and (min-width: 600px) {
.grid {
--cols: 8;
}
}
@media screen and (max-width: 599px) {
.grid {
--cols: 4;
}
}
.grid > .item {
grid-column-end: span 4;
grid-row-end: span 400;
margin-top: calc(1px * var(--gap, 20));
display: flex;
flex-direction: column;
justify-items: stretch;
}
.grid > .item > div {
background-color: gold;
flex-basis: 100%;
}
<h1>CSS Grid Masonry with little help from JS</h1>
<div class="grid" style="--raster: 1;">
<div class="item">
<div><div>1<br>1<br></div></div>
</div>
<div class="item">
<div><div>2<br>2<br>2<br>2<br>2<br>2<br>2<br></div></div>
</div>
<div class="item">
<div>3<br>3<br>3<br>3<br></div>
</div>
<div class="item">
<div>4<br>4<br>4<br></div>
</div>
<div class="item">
<div>5<br>5<br>5<br>5<br>5<br></div>
</div>
<div class="item">
<div>6<br>6<br>6<br></div>
</div>
<div class="item">
<div>7<br>7<br>7<br>7<br>7<br></div>
</div>
<div class="item">
<div>8<br>8<br>8<br>8<br>8<br><div class="item">item</div></div>
</div>
<div class="item">
<div>9<br></div>
</div>
<div class="item">
<div>10<br>10<br>10<br></div>
</div>
<div class="item">
<div>11<br>11<br>11<br>11<br>11<br>11<br>11<br>11<br>11<br>11<br>11<br>11<br></div>
</div>
<div class="item">
<div>12<br>12<br>12<br>12<br>12<br></div>
</div>
<div class="item">
<div>13<br>13<br></div>
</div>
<div class="item">
<div>14<br>14<br>14<br></div>
</div>
<div class="item">
<div>15<br>15<br>15<br></div>
</div>
<div class="item">
<div>16<br>16<br>16<br></div>
</div>
<div class="item">
<div>17<br>17<br>17<br>17<br>17<br></div>
</div>
<div class="item">
<div>18<br>18<br></div>
</div>
<div class="item">
<div>19<br>19<br>19<br>19<br></div>
</div>
<div class="item">
<div>20<br>20<br></div>
</div>
<div class="item">
<div>21<br>21<br>21<br>21<br>21<br>21<br>21<br></div>
</div>
<div class="item">
<div>22<br>22<br>22<br></div>
</div>
<div class="item">
<div>23<br>23<br></div>
</div>
<div class="item">
<div>24<br>24<br></div>
</div>
<div class="item">
<div>25<br>25<br>25<br>25<br></div>
</div>
</div>