How to create a Masonry Image Filter in Divi

by | Aug 25, 2021 | 20 comments

Preview

Hey Guys! One of the most recurring request I see in the Divi groups is “How can we add a simple filter on my Divi website?”. Well, today we’re going to implement that without using any plugin – just by using the default Divi modules and a bit of custom codes. Here is a preview of the final result:

Description

If you guys are like me, you are looking for solutions that will make your life easier. It means that the solution should be easy to implement and doesn’t require much maintenance in the future. The last thing you want is to create the filters manually based on what our client provides today – that may change tomorrow – and keep maintaining a giant bloated JavaScript script based on specific categories.

Instead, what you want is to create a dynamic function that will take care of all the filter attributes so you just have to say “hey Divi, this image belongs to the category X, just include it inside our filter and make it look good! Ok, thanks”. Well, we have you covered on this! Simply import our Freebie into your website and:

  • Upload your own images inside the Image modules and change the class to match your own filter categories. Your can even duplicate the image modules to add unlimited images.
  • Style the Call-to-action module so the filter buttons match your branding styles
  • Update the CSS variables inside the CSS file to style the filter buttons when they are active

And that’s it.

Now if your client asks “hey buddy, can you put this image in the category Y instead of X?”, you just have to change the module class – 7 seconds of work – and it’s fixed. If he wants to add more images to the filter or even create a new category, it’s as easy as duplicating the existing modules and adapt the class to the corresponding category. Lovely isn’t it?

Custom CSS

The CSS is pretty straightforward here. We are using it for:

  • styling our active buttons (using CSS variables).
  • creating space between our images in a responsive way (using media queries for mobiles, tablets and Desktop).
  • hiding our default Filter button on frontend and apply the styles to the visibles filters created by our JavaScript function.

Here is the custom CSS used in this layout. Whenever you need to copy/paste it to your website, you can quickly do it by pressing the button below the code window, and it will copy all the code to your clipboard.

<style>
/*
Template: Masonry Filter Images for Divi
Author: Maxime Beguin
Company: Divi Agency
URL: https://divi.agency/
Version: 1.0.0
Licence: MIT
*/

:root {
   --damfi_active-button-text-color: #ffffff;
   --damfi_active-button-bg-color: #ff8181;
   --damfi_active-button-border-color: #ff8181;
}


/* SET THE CURSOR TO POINTER WHEN HOVERING THE FILTER BUTTONS */

.damfi__section .et_pb_promo_button {
   cursor: pointer;
}


/* SET THE ACTIVE BUTTON STYLE */

body.et-db #page-container #et-boc .et-l .et_pb_section.damfi__section .et_pb_cta_0.et_pb_promo .et_pb_promo_button.et_pb_button.filter-item.is-checked {
   color: var(--damfi_active-button-text-color) !important;
   background-color: var(--damfi_active-button-bg-color) !important;
   border-color: var(--damfi_active-button-border-color) !important;
}


/* POSITIONING - SETTING THE WIDTH AND GLUTTER */


/* --MOBILE */

.damfi__section .filtered__item,
.damfi__section .grid-sizer {
   width: 100%;
   margin-bottom: calc(calc(20px/2) * 2) !important;
}


/* --TABLET */

@media screen and (min-width: 768px) {
   .damfi__section .filtered__item,
   .damfi__section .grid-sizer {
      width: calc(calc(100%/2) - calc(20px/2));
   }
}


/* --DESKTOP */

@media screen and (min-width: 981px) {
   .damfi__section .filtered__item,
   .damfi__section .grid-sizer {
      width: calc(calc(100%/3) - calc(40px/3));
      margin-bottom: calc(calc(40px/3) * 2) !important;
   }
}


/*VISUAL BUILDER ONLY*/


/* --SHOW THE IMAGES AS A GRID ON THE VB */

.et-fb-root-ancestor .et-db #et-boc .et-l .damfi__section .filtered__row .et_pb_column {
   display: grid;
   grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
   grid-gap: 20px;
}

.et-fb-root-ancestor .et-db #et-boc .et-l .damfi__section .filtered__item {
   width: 100%;
}


/* --SHOW DEFAULT BUTTON ON THE THEME EDITOR ONLY */

.et-fb-root-ancestor .et-db #et-boc .et-l .damfi__section .et_pb_promo_button,
.damfi__section .et_pb_promo_button.filter-item {
   display: inline-block !important;
}

.damfi__section .et_pb_promo_button {
   display: none !important;
}

.et-fb-root-ancestor .et-db #et-boc .et-l .visual-builder-onlly__code-module-style {
   display: block;
   color: #fff;
   background: #ef5858;
   border: 1px dashed #ffffff;
   padding: 20px;
   text-align: center;
}

.visual-builder-onlly__code-module-style {
   display: none;
}

</style>

Custom JavaScript

The main idea here is to assign a category to each image and then our JS code will automatically create the button filters and apply the correct filters on click event.

A standard way to apply the filter functionality is to use a data attribute in our HTML structure. Unfortunately, Divi has no options yet to add data attributes to specific modules. So we are going to use a workaround and apply our categories to the class field instead. Once each image module has been assigned to a specific category, our JavaScript function will:

  • automatically pick up the class in each image module, and use it to create our data attributes
  • create a button filter for each category and append them inside the Call-to-action module in order to be able to style them using the Visual builder.
  • activate the filter function using the correct data attributes when each button get clicked. We are going to use an external JS library called Isotope JS to manage both the filter and the animation.
  • Manage the active status when a filter button get clicked.

Here is the custom JavaScript used. Again, if you just want to copy/paste the code, feel free to use your Copy to Clipboard button below the code window.

<script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
<script>
/*
Template: Masonry Filter Images for Divi
Author: Maxime Beguin
Company: Divi Agency
URL: https://divi.agency/
Version: 1.0.0
Licence: MIT
*/
  
jQuery(document).ready(function ($) {

   // PREPEND THE GRID-SIZER IN ORDER TO MAKE OUR FILTERED GRID RESPONSIVE
   var $newdiv = $("<div class='grid-sizer'></div>");
   $('.damfi__section .masonry-grid').prepend($newdiv);

   // ADD CLASS TO THE FILTER BUTTONS WRAPPER
   $('.damfi__section .et_pb_button_wrapper').addClass('button-group filters-button-group');

   // ISOTOPE INIT
   var $grid = $('.damfi__section .masonry-grid').isotope({
      itemSelector: '.damfi__section .filtered__item',
      filter: '*',
      masonry: {
         columnWidth: '.grid-sizer',
         gutter: 20
      }
   });

   // AVOID UNLOADED IMAGE TO CREATE ELEMENTS OVERLAP
   $grid.imagesLoaded().progress(function () {
      $grid.isotope('layout');
   });

   // FILTER
   let arrCat = ['All'];
   const cat = $('.damfi__section .filtered__item');

   // CREATE THE ARRAY AND ADD DATA-CATEGORY
   cat.each(function () {
      let ArrcatName = $(this).attr('class').split(' ');
      let catName = ArrcatName[ArrcatName.indexOf(ArrcatName.find(element => element === 'filtered__item')) + 1];
      if (arrCat.indexOf(catName) === -1) arrCat.push(catName);
      $(this).attr('data-category', catName);
   });

   // ADD FILTER ITEMS
   for (var i = 0; i < arrCat.length; i++) {
      let menuItem = "<a class='et_pb_button et_pb_promo_button filter-item' data-filter='." + arrCat[i] + "'>" + arrCat[i].replace('_', ' ') + "</a>";
      $('.damfi__section #filter .et_pb_button_wrapper').append(menuItem);
   };
   $('.damfi__section .button-group a.filter-item').first().attr('data-filter', '*');

   // FILTER THE IMAGES ON CLICK
   $('.damfi__section .filters-button-group').on('click', 'a', function () {
      var filterValue = $(this).attr('data-filter');
      $grid.isotope({
         filter: filterValue
      });
   });

   // SET FILTER BUTTON AS ACTIVE ON CLICK
   $('.damfi__section .button-group').each(function (i, buttonGroup) {
      var $buttonGroup = $(buttonGroup);
      $buttonGroup.on('click', 'a', function () {
         $buttonGroup.find('.is-checked').removeClass('is-checked');
         $(this).addClass('is-checked');
      });
   });

   // SET THE FIRST FILTER BUTTON AS ACTIVE ON LOAD
   $('.damfi__section .button-group a.filter-item').first().addClass('is-checked');
});
</script>

Additional Notes

At the moment, the filter doesn’t support multiple categories assigned to a single Image module. So use this solution if your images belong to a unique category. If we get enough requests in the comments, we might fix that. But we need some sort of encouragement!

We are also aware of a bug where images won’t be positioned correctly inside the grid when using the “lazyload images” function of WP Rocket. We recommend disabling lazyload options on the pages where this filter is running.

Final Thoughts

Well, that’s all folks. It should be quite straightforward now to include a nice, lightweight, and responsive image filter to your Divi website and make your clients amazed at how fast you are able to include such custom functions into Divi.

Let me know what you think about it in the comments below. And if you want to show some love, I’d really appreciate a review on our facebook page.

Maxime Beguin

Maxime Beguin

Founder & CEO of Divi Agency

My journey as a self-taught Full Stack Developer started in 2011. Back in the days, I was mainly building HTML/CSS websites using Notepad++ and Joomla (tears of nostalgy…). I started using WordPress a few years later while I was learning PHP and JavaScript, and decided to completely embrace the Divi Community. I’m now the CEO of Divi Agency – a white label agency based In Bologna (Italy) 100% focused on helping other agencies and freelancers growing their Divi Business – but I still try to dedicate time to help people in the various Divi groups on Facebook and to write free tutorials on this blog.

For agencies

Looking for a White Label partner?

We’re happy to work with other agency owners, from freelancers looking to diversify income, to any companies looking to outsource workflow. We love connecting with like-minded individuals and collaborating to exceed client expectations together.

Suscribe to our Newsletter

20 Comments

  1. Denise

    Hello. I ask for your assistance in the “.is-checked” is not showing. Thank you. Sincerely, Denise

    Reply
    • Maxime Beguin

      Hey Denise, could you share a link where you are experiencing the issue?

      Reply
  2. Jonas

    This is brilliant!
    Just one question – is it possible in any way to have multiple words in the CSS Class / Buttons.

    “Yellow Orange Red” “Black and White” “Neon Bright Glowing” for exemple.

    Two words seems to be max, but I need three :/

    Anyhow, this is nothing but awesome!

    Reply
    • Maxime Beguin

      Hey Jonas, could you try to insert your classes as follow: “Yellow_Orange_Red” “Black_and_White” “Neon_Bright_Glowing”. The script should replace the “_” with spaces. Let me know if it works!

      Reply
  3. Jonas

    Wow, that was a fast reply!

    Unfortunatly the advised method presents “Yellow Orange_Red” “Black and_White” “Neon Bright_Glowing”

    Seems that the script allows only two words.

    Reply
    • Maxime Beguin

      Ok. I think I know why. You’ll have to look into the JavaScript file and look for:

      arrCat[i].replace('_', ' ')

      and replace it with:

      arrCat[i].replaceAll('_', ' ')

      Let me know if that works!

      Reply
  4. Jonas

    Sorry for spamming your comment field.

    I found a working way just after posting that.
    “Yellow Orange Red” seems to be working for 3 or more words 🙂

    Once again, superb script – thank you so much!

    Reply
    • Maxime Beguin

      No problem for the spam, It can’t be bad for SEO 😀

      Reply
  5. Jonas

    Ah, comment fields interprets the code as blank space.
    “& n b s p ;” <– Clear the spaces for a working blank space. If anyone else needs this 🙂

    Reply
    • Maxime Beguin

      That’s also a good alternative, thanks for the suggestion!

      Reply
  6. Ory

    Hi Maxime, this works beautifully, thanks for that!

    Is there any simple way to add pagination to it as well for the case the gallery is pretty big?

    Thanks!

    Reply
    • Maxime Beguin

      Hey Ory, I’m afraid there is no easy way to do that. You’d need to reload/recalculate the isotope container on each AJAX call. That would require a bit of work to get it done 😂

      Reply
      • Ory

        Hi Maxime, thanks for the quick reply.

        I see..pity! 🙁 haha…

        In that case, it’s probably possible to go around that and present one of the categories as the ‘default’ that is first presented (and not ‘all’). Any suggestion what to change in order for example that ‘Red’ would be presented as the default?

        Thanks again, Ory

        Reply
        • Maxime Beguin

          yes! Please open the javascript code and replace the following lines:

          row 26:
          filter: ‘*’,
          replace it with: filter: ‘.Red’,

          row 42:
          let arrCat = [‘All’];
          replace it with: let arrCat = [];

          row 58:
          $(‘.damfi__section .button-group a.filter-item’).first().attr(‘data-filter’, ‘*’);
          you can delete the entire row – or better add ‘//’ in front of it to comment the code (make it inactive).

          That should do the job.

          Reply
          • Ory

            Nice! Thanks Maxime!

            That worked and has removed the ‘All’ category completely. So I’ve just changed row 26 and chose the category I wish to present as the default.

            For some reason though I can’t control the active category colors as shown in your video. Still looking what might cause it not to react…

          • Maxime Beguin

            Make sure to put a dot in front of the category like “.Red” cause the script is grabbing the class you assign to each image.

  7. Lechelle

    Hi Maxime
    This seems like the exact solution I need for a filterable image portfolio where the images are linked to outside domains and not a project page. I cannot, however, get the JSON to load as an import on the page.

    I am uploading straight to page – should I be uploading to a library?

    Thanks so much!

    Reply
    • Maxime Beguin

      Hey Lechelle,

      You should be able to import it as a page – not as a library item. Do you use any security plugins like WordFence? In some cases, it can block the JSON import. If so, I’d suggest deactivating it and retrying to import the page.

      Let me know!

      Maxime

      Reply
  8. Alexandre

    Great tutorial. Is it possible to make this with any module beyond images. For example, masonry blurb filter?

    Reply
    • Maxime Beguin

      Hey Alexandre,

      It should work with any module that allows you to assign custom classes for each item. It may require minor CSS tweaks but the primary function shouldn’t be altered. Let me know if you face any issues, I’d be happy to have a look at it – make sure to share a live link.

      Cheers,
      Maxime

      Reply

Submit a Comment

Your email address will not be published.

More Freebies

Adding a subtle animation to your menu can increase the curiosity of your visitors and the overall user experience. This tutorial will show you how to create a fullwidth menu using the trendy circular reveal animation.

GSAP has been extremely popular in the last years to create awesome animations on both website and mobile apps environments. Now is the perfect time to fully integrate it into your next Divi projects as a good alternative to popups.

GSAP has been extremely popular in the last years to create awesome animations on both website and mobile apps environments. Now is the perfect time to fully integrate it into your next Divi projects as a good alternative to popups.