CSS Flexbox module allows you to layout things more easily. In this post, I’m going to share an easy way to do a masonry layout with Flexbox.

I have already posted about CSS-only masonry earlier before; but this one is little bit different from that covering the modern approach. So without much ado, lets start.

Update: Check out the latest Horizontal Masonry using CSS Grid.

Flexbox module provides a set of different properties that you may set to lay and align the boxes. If you don’t know much about Flexbox, this guide is a recommended read.

Pure CSS Flexbox Masonries Flexbox Masonry with JS

Horizontal Flexbox Masonry

The first concept is to give all the masonry bricks or cells or flex container’s child items flex: auto. The children then will be sized according to their width and height properties, but will also grow to absorb any extra free space in the masonry, and shrink to their minimum sizes to fit the masonry.

Since this is going to be a fancy masonry layout, we have to give each masonry brick an equal height to arrange all of them in a horizontal order.

If you want a vertical masonry with variable heights of the bricks, you should skip to the next section

.masonry { 
  display: flex;
  flex-flow: row wrap;
  margin-left: -8px; /* Adjustment for the gutter */
  width: 100%;
}

.masonry-brick {
  flex: auto;
  height: 250px;
  min-width: 150px;
  margin: 0 8px 8px 0; /* Some gutter */
}

We will also be giving different widths to the .masonry-brick items that will make the items arranged in a masonry fashion.

.masonry-brick:nth-child(4n+1) {
  width: 250px;
}
.masonry-brick:nth-child(4n+1):nth-child(4n+2) {
  width: 325px;
}
.masonry-brick:nth-child(4n+1):nth-child(4n+3) {
  width: 180px;
}
.masonry-brick:nth-child(4n+1):nth-child(4n+4) {
  width: 380px;
}

Demo the Simple Flexbox Masonry

Vertical Flexbox Masonry

With different heights of the masonry cells, you don’t really have any option other than ordering the cells vertically. With Flexbox as well, you’ll have to content with the vertical order only.

So, this concept is to give our masonry container (.masonry) a fixed height and then set the flex-direction to column. This technique is not so handy, as you will have to tweak up the height of the container everytime you add new items to the masonry.

.masonry { 
  display: flex;
  flex-flow: column wrap;
  max-height: 800px;
  margin-left: -8px; /* Adjustment for the gutter */
  width: 100%;
}

.masonry-brick {
  margin: 0 8px 8px 0; /* Some gutter */
}

Demo the Vertical Masonry

Calculating and then setting height in CSS every time a new cell is added would be quite bothering, and won’t be feasible if your need involves an indefinite number of cells. How about a little piece of JavaScript do that task for us?

Vertical Flexbox Masonry with JavaScript

Below JavaScript function calculates the collective height of all the bricks, and then to provide the masonry container an average of that height based on the masonry gutter, and the number of columns provided for different screen breakpoints.

<script>
/**
 * Calculate the masonry
 *
 * Calculate the average of heights of masonry-bricks and then
 * set it as the height of the masonry element.
 *
 * @since 12212018
 * @author Rahul Arora
 * @param grid       Object  The Masonry Element 
 * @param gridCell   Object  The Masonry bricks
 * @param gridGutter Integer The Vertical Space between bricks 
 * @param dGridCol   Integer Number of columns on big screens
 * @param tGridCol   Integer Number of columns on medium-sized screens
 * @param mGridCol   Integer Number of columns on small screens
 */
function masonry(grid, gridCell, gridGutter, dGridCol, tGridCol, mGridCol) {
  var g = document.querySelector(grid),
      gc = document.querySelectorAll(gridCell),
      gcLength = gc.length, // Total number of cells in the masonry
      gHeight = 0, // Initial height of our masonry
      i; // Loop counter
  
  // Calculate the net height of all the cells in the masonry
  for(i=0; i<gcLength; ++i) {
    gHeight+=gc[i].offsetHeight+parseInt(gridGutter);
  }
  
  /*
   * Calculate and set the masonry height based on the columns
   * provided for big, medium, and small screen devices.
   */ 
  if(window.screen.width >= 1024) {
    g.style.height = gHeight/dGridCol + gHeight/(gcLength+1) + "px";
  } else if(window.screen.width < 1024 && window.screen.width >= 768) {
    g.style.height = gHeight/tGridCol + gHeight/(gcLength+1) + "px";
  } else {
    g.style.height = gHeight/mGridCol + gHeight/(gcLength+1) + "px";
  }
}
</script>

Now, we need to bind the above function with “load” and “resize” events for the browser window, so that it could be executed everytime the document is loaded or resized. For the proper functionality with images, I’m using ImagesLoaded to detect if all images are loaded, and then I’m firing the function inside it for the perfect calculation of the height of each brick.

<script>
/**
 * Reform the masonry
 *
 * Rebuild the masonry grid on every resize and load event after making sure 
 * all the images in the grid are completely loaded.
 */
["resize", "load"].forEach(function(event) {
  // Follow below steps every time the window is loaded or resized
  window.addEventListener(event, function() {
    // Check if all the images finished loading
    imagesLoaded( document.querySelector('.masonry'), function() {
      /*
       * A maonsry grid with 8px gutter, with 3 columns on desktop,
       * 2 on tablet, and 1 column on mobile devices.
       */
      masonry(".masonry", ".masonry-brick", 8, 3, 2, 1);
    });
});
</script>

Fancy Preloading

In order to add fancy preloading text to the masonry, you may consider using something like below. It’s about nothing but adding an adjacent HTML element to our masonry container, which we have to hide as soon as the all the images finish loading. A toggle in display on certain events is what we want here.

<script>
/**
 * Preload and reform the masonry
 *
 * Add some preloader text to the masonry grid and rebuild it on every resize
 * and load event after making sure all the images in the grid are loaded
 * completely.
 */

// Grab the pointer to the masonry grid
var masonryElem = document.querySelector('.masonry');

// Insert preloader text dynamically
masonryElem.insertAdjacentHTML('afterend', '<div class="masonry-preloader">Loading...</div>');

// Grab the pointer to the masonry preloader
var masonryPreloader = document.querySelector('.masonry-preloader');

// Fire the magic on every load and resize event
['resize', 'load'].forEach(function(event) {
  // Hide the masonry until it loads
  masonryElem.style.display='none';

  // Follow the below steps on every load and resize event
  window.addEventListener(event, function() {

    // Check if all the images finished loading
    imagesLoaded( document.querySelector('.masonry'), function() {

      // Show the masonry, as it is loaded now
      masonryElem.style.display='flex';

      // Hide the preloader, as it is not needed anymore
      masonryPreloader.style.display='none';

      /*
       * A maonsry grid with 8px gutter, with 3 columns on desktop,
       * 2 on tablet, and 1 column on mobile devices.
       */
      masonry('.masonry', '.masonry-brick', 8, 3, 2, 1);

      // Done!
      console.log('Flexbox Masonry Loaded');
    });
  }, false);
});
</script>

Demo the Flexbox Masonry with JS

Pretty lightweight, I guess! Please share your thoughts in the comments section.

And this is it. Currently, I’m working on the JS part to make it ordered from left-to-right Check out left-to-right CSS masonry. It is kinda tricky though, not sure where I’m gonna end up with it. Stay tuned, and don’t forget to share the post if you found it cool. Thanks for your time :)