Masonry Image Gallery with Bootstrap 4 Modals

Reading Time: 13 minutes

Today, you will learn about how to create an image with Masonry grid system and Bootstrap 4 modals. In this tutorial you will be working with HTML, CSS and jQuery. That being said, this tutorial is suitable for people with wide range of knowledge, from complete beginner to professional. I will also include links to demo and repository on GitHub where you can download the source code and use it on your website.

You can see the gallery in action on Codepen and download all files from Github repository.

Gathering Dependencies

The first thing you need to do is to get all the assets we are going to work with in this tutorial. Necessary assets are Bootstrap 4 stylesheet and Javascript, source code for jQuery and Masonry plugin. I also customized the style with Fontawesome and font “Poiret One” from Google fonts. You have two options. First, you can download the assets described to your computer. Second, you can use CDN and just include the links in your project.

Note: Images used in demo on Codepen and GitHub are provided by unsplash.com which is great source of high-quality photos. These photos are licensed under Creative Commons Zero. In other words, you can copy, modify, distribute and use all of them as you wish.

Creating the Gallery Structure

The first step, after gathering the dependencies you will need, is to create the HTML structure. The whole structure is composed of four main elements. These elements, or building blocks, are header with short title for the project, .grid containing the gallery with cards, .grid__item for every image card and finally the modal dialog for larger version of image and some icons. These building blocks will be inside section element with class of “container-fluid”, “gallery” and “bg-faded”.

First class is from Bootstrap 4 (and 3) framework. I used it to add small padding on the sides of the gallery. Second class is just for marking the gallery, but it will not be used. Third class is also from Bootstrap 4 and it changes the background color of section to “#f7f7f9(very light grey). First child of section element will be header containing h1 tag with text “Masonry Gallery”.

Second child of the section will be div element with class “grid”. It will be this element where you will place the individual items that will populate the gallery, in this case cards with image thumbnails.

<section class="container-fluid gallery bg-faded">
 <header>
  <h1 class="text-center">Masonry Gallery</h1>
 </header>
 <div class="grid"></div>
</section>

The whole grid will compose of twelve cards each featuring some image of your choice with class “grid__item”. Their structure will be almost the same. The only exception will be different src attributes for img elements. Wait, there will be one more difference. The first card will have class “grid__sizer”. This class will be later used by Masonry plugin to count the width of grid columns. Also, there will be two types of cards. One type for square cards and one for rectangle. Don’t worry about any issues with layout. Remember, you’ve got Masonry to handle the hard work for you.

First child of every card will be div with class “item__overlay” containing button element to open modal dialog. The most important attributes for this button will be data-toggle with value “modal” and data-target with value “#modalPitcure” (id of modal dialog).

Data-toggle attribute tells bootstrap what type of element is the button triggering while data-target says exactly what element it is. I also used “js-button” class for later event handling and, styling classes from Bootstrap–“btn”, btn-secondary-outline” and “center-block”. There are also value and role attributes.

Next element, right after overlay div, second child of grid__item, will be img tag with src and alt attributes. As I mentioned, I used twelve cards (grid items). However, feel free to use as many cards as you want, Masonry plugin will automatically take care about its distribution.

Structure of the first gallery card:

<a href="#" class="grid__item grid__sizer">
 <div class="item__overlay">
  <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
 </div>
 <img src="http://ultraimg.com/images/photo-1.jpg" alt="Credits to Unsplash.com" />
</a>

Structure of the square gallery cards:

<a href="#" class="grid__item">
 <div class="item__overlay">
  <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
 </div>
 <img src="http://ultraimg.com/images/photo-3.jpg" alt="Credits to Unsplash.com" />
</a>

Structure of the rectangle gallery cards:

<a href="#" class="grid__item grid__item--high">
 <div class="item__overlay">
  <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
 </div>
 <img src="http://ultraimg.com/images/photo-43eec3.jpg" alt="Credits to Unsplash.com" />
</a>

The last piece of the HTML puzzle is modal dialog. The structure of modal starts with the main wrapper div with classes “modal” and “fade”, id “modalPicture” and few more attributes for accessibility. Inside this wrapper will be nested div with class “modal-dialog” and role attribute with value “document”. Dialog will contain another div with class “modal-content”. The content div compose of three main parts, or sections, of the modal dialog.

These parts are “modal-header” with button for closing the dialog containing two spans–first one for icon and second for screen readers, “modal-body” containing img tag with blank src attribute and “modal-footer” with some buttons for download, like, comment and tweet. All of these buttons will, again, contain two span elements. Changing the src attribute of img tag will be handled with JavaScript.

Note: All of the buttons are created with Fontawesome mentioned in “Gathering Dependencies” part and are fully functional. “Download” button will download the image to your computer, “comment” will let you tweet me a message on twitter and “share” will open a window to share the link on twitter. “Like” button is just a decoration that will only change color.

Structure of modal dialog:

<div id="modalPicture" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modalPictureLabel" aria-hidden="true">
 <div class="modal-dialog" role="document">
  <div class="modal-content">
   <!-- Modal - header -->
   <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
     <span class="fa fa-times" aria-hidden="true"></span>
     <span class="sr-only">Close</span>
    </button>
   </div><!-- .end Modal - header -->
   <!-- Modal - body -->
   <div class="modal-body">
    <img class="js-modal-image" src="" alt="Credits to Unsplash.com" />
   </div><!-- .end Modal - body -->
   <!-- Modal - footer -->
   <div class="modal-footer">
    <a class="js-download m-r" href="#" aria-label="Download" download>
     <span class="fa fa-download" aria-hidden="true"></span>
     <span class="sr-only">Download</span>
    </a>
    <a class="js-heart m-r" href="#" aria-label="Like">
     <span class="fa fa-heart" aria-hidden="true"></span>
     <span class="sr-only">Like</span>
    </a>
    <a class="m-r" href="https://twitter.com/home?status=%40alexdevero%20" target="_blank" aria-label="Comment">
     <span class="fa fa-comment" aria-hidden="true"></span>
     <span class="sr-only">Comment</span>
    </a>
    <a href="https://twitter.com/home?status=Check%20out%20this%20Mansory%20Gallery%20tutorial%20on%20http%3A//blog.alexdevero.com/%20%23design%20%23webdesign" target="_blank" aria-label="Share">
     <span class="fa fa-twitter" aria-hidden="true"></span>
     <span class="sr-only">Tweet it</span>
    </a>
   </div><!-- .end Modal - footer -->
  </div>
 </div>
</div>

The whole HTML structure is folowing:

<section class="container-fluid gallery bg-faded">
 <header>
  <h1 class="text-center">Masonry Gallery</h1>
 </header>
 <div class="grid">
  <a href="#" class="grid__item grid__sizer">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-1.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item grid__item--high">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-2.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-3.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item grid__item--high">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-43eec3.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-5.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-6.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-7.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-8.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item grid__item--high">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-9.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-10.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-11.jpg" alt="Credits to Unsplash.com" />
  </a>
  <a href="#" class="grid__item">
   <div class="item__overlay">
    <button class="js-button btn btn-secondary-outline center-block" data-toggle="modal" data-target="#modalPicture" type="button" value="Expand photo" role="button">Expand photo</button>
   </div>
   <img src="http://ultraimg.com/images/photo-12.jpg" alt="Credits to Unsplash.com" />
  </a>
 </div>
</section>
<!-- Modal -->
<div id="modalPicture" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modalPictureLabel" aria-hidden="true">
 <div class="modal-dialog" role="document">
  <div class="modal-content">
   <!-- Modal - header -->
   <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
     <span class="fa fa-times" aria-hidden="true"></span>
     <span class="sr-only">Close</span>
    </button>
   </div><!-- .end Modal - header -->
   <!-- Modal - body -->
   <div class="modal-body">
    <img class="js-modal-image" src="" alt="Credits to Unsplash.com" />
   </div><!-- .end Modal - body -->
   <!-- Modal - footer -->
   <div class="modal-footer">
    <a class="js-download m-r" href="#" aria-label="Download" download>
     <span class="fa fa-download" aria-hidden="true"></span>
     <span class="sr-only">Download</span>
    </a>
    <a class="js-heart m-r" href="#" aria-label="Like">
     <span class="fa fa-heart" aria-hidden="true"></span>
     <span class="sr-only">Like</span>
    </a>
    <a class="m-r" href="https://twitter.com/home?status=%40alexdevero%20" target="_blank" aria-label="Comment">
     <span class="fa fa-comment" aria-hidden="true"></span>
     <span class="sr-only">Comment</span>
    </a>
    <a href="https://twitter.com/home?status=Check%20out%20this%20Mansory%20Gallery%20tutorial%20on%20http%3A//blog.alexdevero.com/%20%23design%20%23webdesign" target="_blank" aria-label="Share">
     <span class="fa fa-twitter" aria-hidden="true"></span>
     <span class="sr-only">Tweet it</span>
    </a>
   </div><!-- .end Modal - footer -->
  </div>
 </div>
</div>
<!-- .end modal -->

Adding Style

After the stage is set up, you can add some additional styling to the gallery. Since we are using Bootstrap framework, at least half of the styling is taken care of. The styles important to mention are related to grid__item and grid__sizer. Here, I set the width to “20%” to keep the layout responsive and min-width to “260px”. These values will be also later used by Masonry plugin to calculate the width of columns and change the distribution of cards in layout accordingly.

The default shape of the cards will be square, but this is too boring. To spice the layout up a bit, we will set the height of grid__item–high cards to 480px to show you the magic of Masonry.

Note: Remy function is a sass function used to convert pixel value to rems.

Sass version of the structure:

// Remy function
@function remy($value) {
 @return ($value / 16px) *1rem;
}
/**
 * Base styles
 */
body {
 font: 100% / 1.414 "Poiret One", "Roboto", sans-serif;
}
/**
 * Layout styles
 */
header {
 padding-top: 2rem;
 padding-bottom: 2rem;
}
/**
 * Button component
 */
.btn {
 position: relative;
 top: 0;
 margin-top: 50%;
 opacity: 0;
 font-size: 1rem;
 color: #fff;
 border-radius: remy(35px);
 transform: translateY(-50%);
 transition: all .25s ease-in-out;
 &:focus,
 &:hover {
  color: #111;
  background-color: #fff;
  border-color: #fff;
 }
}
/**
 * Grid styles
 */
.item__overlay {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background-color: rgba(#111, .45);
 transition: background-color .55s ease-in-out;
 &:focus,
 &:hover {background-color: rgba(#111, .05);}
 &:focus > button,
 &:hover > button {opacity: 1;}
}
.grid__sizer,
.grid__item {
 position: relative;
 width: 20%;
 min-width: remy(260px);
}
.grid__item {
 margin-bottom: .9375rem;
 border-radius: remy(4px);
 overflow: hidden;
}
.grid__item--high,
.grid__item--high img {height: remy(480px);}
img {
 width: 100%;
 max-width: 100%;
 height: auto;
}
/**
 * Link styles
 */
a,
.fa {
 color: #bbb;
 transition: color .25s ease-in-out;
 &:focus,
 &:hover {
 text-decoration: none;
  color: #666;
 }
}
.active .fa {color: #f62459;}

CSS version of the structure:

/**
 * Base styles
 */
body {
 font: 100% / 1.414 "Poiret One", "Roboto", sans-serif;
}
/**
 * Layout styles
 */
header {
 padding-top: 2rem;
 padding-bottom: 2rem;
}
/**
 * Button component
 */
.btn {
 position: relative;
 top: 0;
 margin-top: 50%;
 opacity: 0;
 font-size: 1rem;
 color: #fff;
 border-radius: 2.1875rem;
 -webkit-transform: translateY(-50%);
 -ms-transform: translateY(-50%);
 transform: translateY(-50%);
 -webkit-transition: all .25s ease-in-out;
 transition: all .25s ease-in-out;
}
.btn:focus, .btn:hover {
 color: #111;
 background-color: #fff;
 border-color: #fff;
}
/**
 * Grid styles
 */
.item__overlay {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background-color: rgba(17, 17, 17, 0.45);
 -webkit-transition: background-color .55s ease-in-out;
 transition: background-color .55s ease-in-out;
}
.item__overlay:focus, .item__overlay:hover {
 background-color: rgba(17, 17, 17, 0.05);
}
.item__overlay:focus > button, .item__overlay:hover > button {
 opacity: 1;
}
.grid__sizer,
.grid__item {
 position: relative;
 width: 20%;
 min-width: 16.25rem;
}
.grid__item {
 margin-bottom: .9375rem;
 border-radius: 0.25rem;
 overflow: hidden;
}
.grid__item--high,
.grid__item--high img {
 height: 30rem;
}
img {
 width: 100%;
 max-width: 100%;
 height: auto;
}
/**
 * Link styles
 */
a,
.fa {
 color: #bbb;
 -webkit-transition: color .25s ease-in-out;
 transition: color .25s ease-in-out;
}
a:focus, a:hover,
.fa:focus,
.fa:hover {
 text-decoration: none;
 color: #666;
}
.active .fa {
 color: #f62459;
}

Events Handling

The last step to make the gallery working is create necessary scripts to handle the dynamic part. The whole JavaScript will compose of two main blocks–one for Masonry plugin and one for image replacement inside modal dialog to show different images depending on which card (button) you will click on. These two blocks will both be nested in one self-invoking function.

We will use jQuery to select the “.grid” div and initialize the Masonry plugin. It will be initialized with four options in JSON format. First option will be “itemSelector” to tell Masonry what elements are grid childs (cells). Second option is “columnWidth” for setting the width of column. We will use the “grid__sizer” class to do that. Meaning, every column will span over 20% or at least 260px. Third option will be gutter to create some space between cards. I used the same value as for bottom margin to keep things consistent. The last option is “percentPosition” with boolean value set to “true”. This sets item positions in percent values instead of pixels.

Masonry handler:

$(".grid").masonry({
 itemSelector: ".grid__item",
 columnWidth: ".grid__sizer",
 gutter: 15,
 percentPosition: true
});

The second part of script is image replacement handler. Here, we will create a function listing to click on button inside card in gallery. When the button is clicked, it will create new variable imageSrc and save the value of src attribute of the image inside currently viewed card in it. Next, it will set the href attribute for “download” button in modal to imageSrc and also set the src attribute of img element inside modal dialog to this variable.

Image replacement handler:

$(document).on("click", ".js-button", function() {
 var imageSrc = $(this).parents(".grid__item").find("img").attr("src");
 $(".js-download").attr("href", imageSrc);
 $(".js-modal-image").attr("src", imageSrc);
 $(document).on("click", ".js-heart", function() {
  $(this).toggleClass("active");
 });
});

Complete JavaScript structure:

(function() {
 // Masonry grid setup
 $(".grid").masonry({
  itemSelector: ".grid__item",
  columnWidth: ".grid__sizer",
  gutter: 15,
  percentPosition: true
 });
 // Image replacement handler
 $(document).on("click", ".js-button", function() {
  var imageSrc = $(this).parents(".grid__item").find("img").attr("src");
  $(".js-download").attr("href", imageSrc);
  $(".js-modal-image").attr("src", imageSrc);
  $(document).on("click", ".js-heart", function() {
   $(this).toggleClass("active");
  });
 });
})();

Final Words

That’s it! You have working image gallery with masonry grid layout that will restructure itself depending on screen resolution and height of grid items. Did I mentioned this? The beauty of masonry grid is that you don’t need to use items with the same height. Masonry will do the necessary math to place all items correctly inside the grid without breaking the layout, no matter how much are items different in height or width. Now, it’s up to you to use this advantage and enhance your website with some awesome layout.

Thank you very much for your time. And, until next time, have a great day!

Did you like this article? Please subscribe.

Are you on social media? Let's connect! You can find me on Twitter and Dribbble.

5 Replies to “Masonry Image Gallery with Bootstrap 4 Modals”

  1. Using imagesloaded.3.1.8.min.js does not solve the overlapping problem Ana Belo refers to. I still have not found a solution to the problem with your css playing havoc with my header and footer images. Changing the col md from 12 to 4 makes it better, but reducing the header and footer images on small screens too much.

    A response, would certainly be appreciated. It seems foolish to have a Comment Box on your site if you’re not going to respond.

    1. Thanks for response George. I’m working on solution. This is not first time there are issues with masonry plugin. I’m thinking about using different plugin.

  2. The img css code for the masonry is playing havoc with other images in the page, Since it’s used for img-screen, I don’t know how to change it to something else i.e. img2 to keep it isolated to the masonry layout. Any suggestions?

  3. Hi,

    I found this code very useful and my gallery is working perfectly … with one little problem: the images are overlapping, but when I make refresh, all is fine. I’ve searched and it seems that ImagesLoaded resolves the problem but with me is not doing anything and I can’t tell why… can you help me? I’m new in this world, I’m probably making a dumb mistake!

    Tank you very much
    Ana

Comments are closed.