15

I'm trying to create masonry layout using css grid layout. All items in grid have variable heights. And I don't know what items will be. So I can't define grid-row for each item. Is it possible to start each new item right after end of previous in column?

Code I'm trying:

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, 330px);
  align-items: flex-start;
  grid-column-gap: 10px;
  grid-row-gap: 50px;
}

.item {
  background: black;
  border-radius: 5px;
}
<div class="wrapper">
  <div class="item" style="height:50px"></div>
  <div class="item" style="height:100px"></div>
  <div class="item" style="height:30px"></div>
  <div class="item" style="height:90px"></div>
  <div class="item" style="height:80px"></div>
  <div class="item" style="height:50px"></div>
  <div class="item" style="height:70px"></div>
  <div class="item" style="height:40px"></div>

</div>

full codepen here

user2950602
  • 395
  • 1
  • 7
  • 21
  • 1
    AFAIK, CSS Grid doesn't do Masonry style any more than other layout methods do. – Paulie_D May 11 '17 at 14:14
  • @Paulie_D Ok, can you say what methods? – user2950602 May 11 '17 at 14:34
  • 3
    CSS Grid can do Masonry just fine, if you can define the heights in the grid. It may not be fully automated but it may be useful in many cases. http://stackoverflow.com/a/43903119/3597276 – Michael Benjamin May 11 '17 at 14:48
  • @user2950602, if you 're looking for other options, see my answer here: http://stackoverflow.com/q/34480760/3597276 – Michael Benjamin May 11 '17 at 14:51
  • @Michael_B Problem is that I can't define height. Thanks for link to other options – user2950602 May 11 '17 at 14:51
  • It would be cool if you can set implicit rows to lets say 5px. The grid items could just span across as many as they need. In the current layout, that means the vertical white space is gone, but it also makes the grid items overlap. But if grid had a property that instructed each item to *find unoccupied cells*, then the overlap would be solved, the vertical gaps would be gone, and `grid-gap` or `margin` could be used to control separation. Alas, I don't know if the spec offers this, but would be awesome if it did. – Michael Benjamin May 11 '17 at 15:00
  • A good tutorial can be found [here](https://www.youtube.com/watch?v=KrPz_wmBsAE) – Flimtix Mar 31 '22 at 10:54

4 Answers4

2

In your question you are setting the height of each item individually. If you are happy to do this then a Masonry layout can easily be achieved with grid.

Instead of setting a height for each item set grid-row-end so that each item spans a certain number of rows.

 <div class="item" style="grid-row-end: span 5"></div>

The height of the item will then depend on the values of grid-auto-rows and grid-row-gap you have set for the grid.

I have made a Codepen here: https://codepen.io/andybarefoot/pen/NaprOB

If you don't want to individually set the grid-row-end value for each item you can use a bit of JavaScript to do it dynamically. I put another "container" div inside each item and measure the height of this container to calculate how many rows the item needs to span. I do this on page load, and again for each item when any images are loaded (as the height of the content will have changed). If you combine this approach with a responsive layout then you should also recalculate on page resize as the width of the columns may have changed and this will affect the height of the content.

Here's my full example with responsive column resizing: https://codepen.io/andybarefoot/pen/QMeZda

If you have items with variable widths you can still achieve a similar effect but the packing of the grid won't be perfect and the item order may be changed to optimise the packing.

I wrote a blog on Medium about this approach in case it is of interest: A Masonry style layout using CSS Grid

1

You can set span values for grid-row-end dynamically (with a bit of JS, like the one based on my Codepen experiment in the example below) and use the dense keyword for grid-auto-placement:

const gridStyles = getComputedStyle(document.querySelector('.wrapper',null));
const rowHeight = parseInt(gridStyles.getPropertyValue('--grid-row-height'));
const gap = parseInt(gridStyles.getPropertyValue('--grid-gutter'));;

let makeGrid = function() {
  let items = document.querySelectorAll('.item');
  for (let i=0, item; item = items[i]; i++) {
    // take an item away from grid to measure it
    item.classList.add('is-being-measured');
    let height = item.offsetHeight;
    // calcylate the row span
    let rowSpan = Math.ceil((height + gap)/(rowHeight + gap));
    // set the span value for grid-row-end
    item.style.gridRowEnd = 'span '+rowSpan;
    // return the item into the grid
    item.classList.remove('is-being-measured');
  }
}

window.addEventListener('load', makeGrid);
window.addEventListener('resize', () => {
  clearTimeout(makeGrid.resizeTimer);
  makeGrid.resizeTimer = setTimeout(makeGrid, 50);
});
.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, 330px);
  --grid-gutter: 10px;
  grid-gap: var(--grid-gutter);
  --grid-row-height: 10px;
  grid-auto-rows: var(--grid-row-height);
  grid-auto-flow: row dense;
  position: relative;
}

.item {
  background: black;
  color: white;
  border-radius: 5px;
}
.item.is-being-measured {
  /* temporary styles for measuring grid items */
  position: absolute;
  width: 330px;
  top: 0;
  left: 0;
}

.item > * { margin-left: 20px; }
<div class="wrapper">
  <div class="item"><h3>1.1</h3><p>1.2</p></div>
  <div class="item"><p>2.1</p><p>2.2</p><p>2.3</p><p>2.4</p><p>2.5</p></div>
  <div class="item"><h2>3.1</h2></div>
  <div class="item"><h2>4.1</h2><p>4.2</p><p>4.3</p><p>4.4</p></div>
  <div class="item"><p>5.1</p><p>5.2</p><p>5.3</p><p>5.4</p></div>
  <div class="item"><h2>6.1</h2><p>6.2</p></div>
  <div class="item"><h2>7.1</h2><p>7.2</p><p>7.3</p></div>
  <div class="item"><p>8.1</p><p>8.2</p></div>

</div>
Ilya Streltsyn
  • 13,076
  • 2
  • 37
  • 57
0

This is one way to create the Masonry layout using only CSS.

*,
*:before,
*:after {
  box-sizing: border-box !important;
}

article {
  -moz-column-width: 13em;
  -webkit-column-width: 13em;
  -moz-column-gap: 1em;
  -webkit-column-gap: 1em;
}

section {
  display: inline-block;
  margin: 0.25rem;
  padding: 1rem;
  width: 100%;
  background: #efefef;
}

p {
  margin: 1rem 0;
}

body {
  line-height: 1.25;
}
<article>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error aliquid reprehenderit expedita odio beatae est.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nobis quaerat suscipit ad.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Rem nihil alias amet dolores fuga totam sequi a cupiditate ipsa voluptas id facilis nobis.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Rem ut debitis dolorum earum expedita eveniet voluptatem quibusdam facere eos numquam commodi ad iusto laboriosam rerum aliquam.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quaerat architecto quis tenetur fugiat veniam iste molestiae fuga labore!</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit accusamus tempore at porro officia rerum est impedit ea ipsa tenetur. Labore libero hic error sunt laborum expedita.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima asperiores eveniet vero velit eligendi aliquid in.</p>
  </section>
  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus dolorem maxime minima animi cum.</p>
  </section>
</article>

Note: I didn't made the code, I found it an made some small adaptation, the original code can be found here.


Please note that, as pointed out by Zen:

[...] the items are laid out top-to-bottom, left-to-right, whereas what one usually expects (cultural assumptions excused) is left-to-right, top-to-bottom layout. This is the showstopper for the usual CSS3-columns-based recommendations.

Paolo Forgia
  • 6,572
  • 8
  • 46
  • 58
  • 8
    "The problem with this solution is that the items are laid out top-to-bottom, left-to-right, whereas what one usually expects (cultural assumptions excused) is left-to-right, top-to-bottom layout. This is the showstopper for the usual CSS3-columns-based recommendations." – Zen May 18 '17 at 09:33
-2

You can accomplish this with column.

.wrapper {
    column-gap: 10px;
    column-count: 4;
}

.item {
    display: inline-block;
    background: #000;
    width: 100%;
    border-radius: 3px;
}

It looks like you were trying to use a combination of flex and grid, which may have been confusing things. As far as I know, flex is relative to the rest of the items on the page, where setting a column affects items falling into those columns.

updated codepen

Will Thresher
  • 109
  • 1
  • 9
  • Whilst this may theoretically answer the question, [**it would be preferable**](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. Link-only answers can become invalid if the linked page changes – Paulie_D May 11 '17 at 14:14
  • @WillThresher, what made you think that "flex is relative to the rest of the items on the page"? Special rules of Flexbox apply only to direct descendants of the element with `display:flex`. – Ilya Streltsyn Aug 07 '17 at 06:56