HTML & CSS - - By Maria Antonietta Perna

Getting Bootstrap Tabs to Play Nice with Masonry

On the Masonry website, we read that Masonry is

… a JavaScript grid layout library. It works by placing elements in optimal position based on available vertical space, sort of like a mason fitting stones in a wall.

Bootstrap is one of the most widely adopted open source front-end frameworks. Include Bootstrap in your project, and you’ll be able to whip up responsive web pages in no time.

If you tried using Masonry together with the Tabs widget, one of the many JavaScript components Bootstrap has to offer, chances are you’ve stumbled on some kind of annoying behavior.

I did, and this article highlights what the issue is and what you can do to solve it.

Bootstrap Tabs Explained

Bootstrap’s Tabs component includes two key, related pieces: a tabbed navigation element and a number of content panels. On page load, the first panel has the class .active applied to it. This enables the panel to be visible by default. This class is used via JavaScript to toggle the panel’s visibility via the events triggered by the tabbed navigation links: if .active is present the panel is visible, otherwise the panel is hidden.

If you have some web content that’s best presented in individual chunks, rather than crammed all in one spot, this kind of tabs component might come in handy.

Why Masonry?

In some cases, the content inside each panel is suited to being displayed in a responsive grid layout. For instance, a range of products, services, and portfolio items are types of content that can be displayed in grid format.

However, if grid cells are not of the same height, something like what you see below can happen.

Grid layout without Masonry

A wide gap separates the two rows of content and the layout appears broken.

That’s when Masonry saves the day. Add some Masonry magic to the mix and your grid dynamically adapts to the screen real estate, eliminating all ghastly gaps.

Grid with Masonry library

Setting Up a Demo Page

Getting a demo page up and running helps to show how integrating Bootstrap’s Tabs with Masonry is not as straightforward as one expects.

This article’s demo page is based on the Starter Template, available on the Bootstrap website.

Each grid item inside the tab panels is built with the Bootstrap grid system and the Bootstrap Thumbnails component. Here’s a code snippet to illustrate its structure:

<div class="col-sm-6 col-md-4">
  <div class="thumbnail">
    <img src="http://lorempixel.com/200/200/abstract" alt="">
    <div class="caption">
      <h3>Thumbnail label</h3>
      <p>...</p>
      <p>
        <a href="#" class="btn btn-primary" role="button">Button</a> 
        <a href="#" class="btn btn-default" role="button">Button</a>
      </p>
    </div>
  </div>
</div>  

<!-- Repeat two more times ... -->

The code above builds a three-column grid on large to medium screens and a two-column grid on smaller screens. If you need a refresher on the Bootstrap grid system, Understanding Bootstrap’s Grid System by Syed Fazle Rahman is a great read.

The Tabs widget in the demo page has the following HTML structure:

<div role="tabpanel">

  <!-- Nav tabs -->
  <ul class="nav nav-tabs" role="tablist">
    <li role="presentation" class="active">
      <a href="#panel-1" aria-controls="panel-1" role="tab" data-toggle="tab">Panel 1</a>
    </li>
    <li role="presentation">
      <a href="#panel-2" aria-controls="panel-2" role="tab" data-toggle="tab">Panel 2</a>
    </li>
    <li role="presentation">
      <a href="#panel-3" aria-controls="panel-3" role="tab" data-toggle="tab">Panel 3</a>
    </li>
    <li role="presentation">
      <a href="#panel-4" aria-controls="panel-4" role="tab" data-toggle="tab">Panel 4</a>
    </li>
  </ul>

  <!-- Tab panels -->
  <div class="tab-content">

    <div role="tabpanel" class="tab-pane active" id="panel-1">
      <div class="row masonry-container">

        <div class="col-md-4 col-sm-6 item">
          <!-- Thumbnail goes here -->
        </div>

        <div class="col-md-4 col-sm-6 item">
          <!-- Thumbnail goes here -->
        </div>

        <div class="col-md-4 col-sm-6 item">
          <!-- Thumbnail goes here -->
        </div>

        ...

      </div><!--End masonry-container  -->
    </div><!--End panel-1  -->

    <div role="tabpanel" class="tab-pane" id="panel-2">
      <!-- Same as what goes inside panel-1 -->
    </div><!--End panel-2  -->

    ...

  </div><!--End tab-content  -->


</div><!--End tabpanel  -->

Here are a few things to note about the code snippet above:

  • HTML comments point to the Tab’s key components: Nav tabs marks the tabbed navigation section, and Nav panels marks the content panels.
  • The tabbed links connect to the corresponding content panel through the value of their href attribute, which is the same as the value of the id attribute of the content panel. For instance, the link with href="#panel-1" opens the content panel with id=panel-1.
  • Each anchor tag in the navigation section includes data-toggle="tab". This markup enables the tabs component to work without writing any additional JavaScript.
  • Finally, the elements that Masonry needs to target have a class of .masonry-container that apply to the wrapper div element that encompasses all the grid items and a class of .item that apply to each single grid item.

To see the full power of the Masonry library, make sure the grid items are of varying heights. For instance, delete the image on one item, shorten a paragraph on another, etc.

For the full code, check out the code panels in the CodePen demo.

Adding the Masonry Library

You can download Masonry from the official website by clicking on the Download masonry.pkgd.min.js button.

To avoid layout issues, the library’s author recommends using Masonry together with the imagesLoaded plugin.

Masonry doesn’t need the jQuery library to work. However, because the Bootstrap JavaScript components already use jQuery, I’ll be making life easier for myself and initialize Masonry the jQuery way.

Here’s the code snippet we need to initialize Masonry using jQuery and imagesLoaded.

var $container = $('.masonry-container');
$container.imagesLoaded( function () {
  $container.masonry({
    columnWidth: '.item',
    itemSelector: '.item'
  });   
});

The code above caches the div that wraps all the grid items in a variable called $container.

Next, Masonry is initialized on $container with a couple of recommended options. The columnWidth option indicates the width of a column of a horizontal grid. Here it is set to the width of the single grid item by using its class name. The itemSelector option indicates which child elements are to be used as item elements. Here, it’s also set to the single grid item.

It’s now time to test the code.

Oops! What’s up with the Hidden Panels?

On a web page that doesn’t use Bootstrap Tabs, the code above works like a charm. However, in this case, you soon realize a kind of funny behavior occurs.

First, it seems fine because the grid inside the default active tab panel is displayed correctly:

First active tab panel with Masonry included

However, if you click on a tabbed navigation link to reveal the hidden panel’s content, here’s what happens:

Grid inside panels initially hidden with Masonry included

Peeking inside the source code reveals that Masonry has fired as expected, but the position of each item is not being calculated correctly: grid items are all stacked on top of each other like a pack of cards.

And that’s not all. Resizing the browser window causes the grid items to position themselves correctly.

Let’s Fix the Layout Bug

Since the unexpected layout bug becomes apparent after clicking on a tabbed navigation link, let’s look into the events fired by Bootstrap’s Tabs a bit more closely.

The events list is quite short. Here it is.

  • show.bs.tab fires on tab show, but before the new tab has been shown.
  • shown.bs.tab fires on tab show after a tab has been shown.
  • hide.bs.tab fires when a new tab is to be shown (and thus the previous active tab is to be hidden).
  • hidden.bs.tab fires after a new tab is shown (and thus the previous active tab is hidden).

Because the grid layout gets messed up after a tab has been shown, we go for the shown.bs.tab event. Here’s the code, which we place just below the previous snippet:

$('a[data-toggle=tab]').each(function () {
  var $this = $(this);

  $this.on('shown.bs.tab', function () {
    $container.imagesLoaded( function () {
      $container.masonry({
        columnWidth: '.item',
        itemSelector: '.item'
      });   
    });  
  });
});

Here’s what happens in the code above:

The jQuery .each() function loops over each tabbed navigation link and listens for the shown.bs.tab event. As the event fires, the panel becomes visible and Masonry is re-initialized after all images have finished loading.

Let’s Test the Code

If you’ve been following along, launch your demo in the browser, or try out the CodePen demo below to check out the result:

See the Pen Getting Bootstrap Tabs to Play Nice with the Masonry Library by SitePoint (@SitePoint) on CodePen.

You might also want to visit the full page demo to test the full responsive layout.

Click on a tabbed navigation link and notice how this time the grid items fit evenly inside each content panel. Resizing the browser causes the items to reposition themselves correctly with a nice animation effect.

That’s it, job done!

Conclusion

In this article I’ve shown how to integrate Bootstrap’s Tabs component with the Masonry JavaScript library.

Both scripts are easy to use and quite powerful. However, put them together and you’ll face some annoying layout bugs affecting the hidden tabs. As shown above, the trick is to re-initialize the Masonry library after each panel becomes visible.

With this solution in your toolbox, achieving great tiled layouts will be a breeze.

I hope you enjoyed reading this article. I’d love to hear your thoughts about it in the discussion below.

Sponsors
Comments
LukeWatts85

Couldn't you just use MixItUp or Isotope to achieve the same functionality? Both have a Masonry effect and tab like navigation.

antonella

Hi Luke,
Thanks for your comment. I could have used different plugins, but that would have been a different topic for a different tutorial perhaps. I've used both the plugins you mentioned on occasion and they're very good. However, they're not free to use for commercial projects. Also the BS tabs have a bit of a different look and implementation from the MixItUp plugin that some people might like to try in their projects, and Masonry would allow to implement the tiled layout effortlessly and for free.

LukeWatts85

That is true. Just some additional options for someone looking to get this effect and might not have known of MixItUp or Isotope. Good to know of as many tools and libs as possible to better find the right ones for the right jobs.

nigeljohnwade1

This is a somewhat mis-titled article. Any tabs plugin, or accordion, or anything that creates hidden content areas will produce the same effect. It would be the same with JQueryUI for example. And any javascript that relies on reading CSS from the DOM to style layout on the fly will encounter the same problem. The problem is that hidden content has no height or width so the proportional calculations can't be made. In any similar system layout javascript needs calling when the size of the content area changes, which in effect is what happens when you show the tab, it's size changes from 0:0 to whatever its shown state is.

antonella

Hi nigeljohnwade1,
Thank you for making a great point and an absolutely valid one as far as hidden content is concerned.

However, although the problem can be generalized in the way you explain, it doesn't necessarily follow that examining one particular case and showing the solution to the problem as it's exemplified in that particular case, is not worth doing.

As for the article being mis-titled, I can't see how this is: the article does exactly what it says in the title: it takes the particular case of using two plugins, it highlights the problem that might arises by doing that, and applies a solution that relates to those two plugins.

Although the cause of the problem (hidden content), and also the remedy that eliminates the cause (reinitializing the plugin), can to a certain extent
be generalized to other cases, the How, that is the specific way in which it's done in that situation, is not always clear to someone facing that particular problem.

Cesar_Viana

Thaks Maria, it was a really helpful tutorial; I'm using Masonry in a proyect and was having this problem.

Since Bootstrap has changed a little bit I think you can update the tutorial with the new code availale on the Bootstrap documentation.

I used this code and brought me the same results:

$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
e.preventDefault()
  $container.imagesLoaded( function () {
		$container.masonry({
			itemSelector: '.item',
			columnWidth: 195,
		});
            });
})
antonella

Hi Cesar_Viana, thanks for your comment. I'm happy you found the tutorial helpful smile

taieb_baccouch

Thank you so much for this great tutorial. It helped me a lot.

antonella

Hi Taieb,
Thank you for your feedback, I'm so glad the article was helpful smile

Ravi_Kasireddy

it seems good article nice info..thanks

antonella

Thank you for your comment, Ravi

jgibba

Thanks for this tutorial - I bought a theme on themeforest that uses this and wasn't quite sure what it was, why it was being used, and how it could be integrated with Bootstrap, but this completely clarified that for me!

antonella

Awesome! Thank you Jgibba for stopping by and leaving your feedback smile

Peter_Nguyen

HI Maria, it seems that if I click on a tab and then click on another tab before masonry finishes to layout, the layout does not finish and remains stuck in it's incomplete position. Even with imagesLoaded, this problem happens.

UPDATE: I'm using Masonry 3.3.1 and Bootstrap 3.3.5. Maria's article currently uses Masonry 3.2.2 and Bootstrap 3.3.1. I found a workaround to the problem for those who need a solution to it. It seems that during the transition/animation of masonry layout, if the user switches tabs before masonry completely finishes, the content is stuck at the incomplete position. The masonry that Maria uses seems to have a transitionDuration of .1 second which is so fast that it's humanly impossible to encounter this issue. The version that I uses has a default transitionDuration of .4 second which is slow enough for me to switch tabs before masonry finishes its layout. My fix was to simply specify transitionDuration to 0 seconds (no transition) or 0.1 seconds (fast transition like Maria's). This seems to work, but I suspect this is more of a workaround than an actual solution. This workaround is sufficient for me.

antonella

Hi Peter,
I've updated Bootstrap and Masonry on a local version of my demo, but the animation is still fast enough to make it difficult to replicate this problem. I should have to slow it down on purpose to see the issue (just as you had to speed it up to see the issue).

Just come back after rebuilding my demo on my CodePen account. I've purposefully slowed down the transition on the .item element (both transition-delay and transition-duration have been delayed). It looks to me that the items reposition themselves even if I don't let the animation complete. Only, it takes a bit longer because of the increased timing, which makes it look like it's stuck.

Thank you so much for your feedback.