Isotope is a great jQuery plugin for magical layouts. It has features like filtering, sorting, and several layout modes. In this tutorial, I’ll show you how to create a responsive masonry layout and filter items. By default, the isotope masonry layout does not work properly in terms of responsiveness. I will explain how we can fix that and make our layout smooth for all devices.
Requirements
Goals
- Create a responsive masonry layout using Isotope
- Add filtering to sort items
Outcomes
- Successfully created responsive masonry layout using Isotope
Organizing your project and structuring HTML
The initial step is to organize your project for a better development process. We will now create folders and add all necessary files to our project. I usually follow this below when I start a mini-project.
Now it’s time to add HTML structure and include Isotope into our index.html file. Add the codes below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Isotope Masonry Layout</title> <!-- StyleSheets --> <link rel="stylesheet" href="css/styles.css"> </head> <body> <!-- Isotope Projects Wrapper --> <div class="projects-wrapper"> </div> <!-- JavaScipts --> <script src="js/jquery.min.js"></script> <script src="js/isotope.pkgd.min.js"></script> <script src="js/functions.js"></script> </body> </html> |
Next, we will add our project item HTML into the projects-wrapper div block. Add the following codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
<!-- Isotope Projects Wrapper --> <div class="projects-wrapper"> <div class="projects-list"> <div class="project-item"> <!-- Item Image --> <img src="img/1.jpg" alt="Item 1"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/2.jpg" alt="Item 2"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/3.jpg" alt="Item 3"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/4.jpg" alt="Item 4"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/5.jpg" alt="Item 5"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/6.jpg" alt="Item 6"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/7.jpg" alt="Item 7"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/8.jpg" alt="Item 8"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/9.jpg" alt="Item 9"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/10.jpg" alt="Item 10"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/11.jpg" alt="Item 11"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/12.jpg" alt="Item 12"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/1.jpg" alt="Item 1"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/6.jpg" alt="Item 6"> </div> <div class="project-item"> <!-- Item Image --> <img src="img/4.jpg" alt="Item 4"> </div> </div> </div> |
As our HTML is ready, let’s move to styling our layout. Add the following codes below to the css/styles.css file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/* Projects */ .projects-wrapper { background: transparent url("../img/projects_main_bg.jpg") repeat-y center top; padding: 20px; } .projects-list { margin: auto; left: 7px; } .project-item { width: 20%; margin-bottom: 15px; position: relative; -webkit-box-shadow: 2px 2px 5px rgba(0,0,0, .5); -moz-box-shadow: 2px 2px 5px rgba(0,0,0, .5); box-shadow: 2px 2px 5px rgba(0,0,0, .5); } .project-item img { width: 100%; height: auto; vertical-align: top; } |
Note: I’ve used basic CSS reset for this project here. You can use normalize.css or twitter bootstrap.css as well.
Initiate Isotope
Now we are all set to initiate the Isotope plugin. Add the codes below to your js/functions.js file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
(function ($) { "use strict"; // Initiating Isotope var $container = $('.projects-list'); var isotope = function () { $container.isotope({ resizable: false, itemSelector: '.project-item', masonry: { columnWidth: 50, gutter: 10 } }); }; // Calling Isotope isotope(); })(jQuery); |
It’s time to open our index.html file in the browser. You should see the Isotope masonry layout. But there is an issue as the masonry column contents are not looking good. It has so many extra spaces around item images. So we need to create a dynamic column width function to fix this issue. Let’s add the following codes to the js/functions.js file by replacing older codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
(function ($) { "use strict"; // Initiating Isotope var $container = $('.projects-list'); var colWidth = function () { var w = $container.width(), columnNum = 1, columnWidth = 0; if (w > 1200) { columnNum = 5; } else if (w > 900) { columnNum = 4; } else if (w > 600) { columnNum = 3; } else if (w > 300) { columnNum = 1; } columnWidth = Math.floor(w/columnNum); columnWidth = columnWidth - 10; // Item width $container.find('.project-item').each(function() { var $item = $(this); var multiplier_w = $item.attr('class').match(/item-w(\d)/); var width = multiplier_w ? columnWidth*multiplier_w[1]-4 : columnWidth-4; // Update item width $item.css({ width: width }); }); return columnWidth; }; var isotope = function () { $container.isotope({ resizable: false, itemSelector: '.project-item', masonry: { columnWidth: colWidth(), gutter: 10 } }); }; // Calling Isotope isotope(); })(jQuery); |
colWidth() function will calculate column width based on your screen size and then update column width using the jQuery CSS function. This is we will get the exact amount of pixels for each column width. Also, you will notice that I’ve defined columns based on device screen width so when the width is greater than 1200px it will show 5 masonry column layout and so on. You control your columns by updating the codes above.
Resizing Layout
So far we have everything properly and our masonry columns are showing the way we wanted but still, we have a problem. When you resize your window you will see the projects wrapper has extra spaces on the right side which we don’t expect to be there. All of the spaces inside the wrapper should be distributed equally for each of the columns. To fix this issue we need to add very small jQuery plugin codes called smartresize from here. You have to update your js/functions.js file with the following codes below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
(function($,sr){ // http://paulirish.com/2009/throttled-smartresize-jquery-event-handler/ // debouncing function from John Hann // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ var debounce = function(func, threshold, execAsap) { var timeout; return function debounced () { var obj = this, args = arguments; function delayed () { if (!execAsap) func.apply(obj, args); timeout = null; } if (timeout) clearTimeout(timeout); else if (execAsap) func.apply(obj, args); timeout = setTimeout(delayed, threshold || 50); }; }; // smartresize jQuery.fn[sr] = function(fn){ return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); }; })(jQuery,'smartresize'); (function ($) { "use strict"; // Initiating Isotope var $container = $('.projects-list'); var colWidth = function () { var w = $container.width(), columnNum = 1, columnWidth = 0; if (w > 1200) { columnNum = 5; } else if (w > 900) { columnNum = 4; } else if (w > 600) { columnNum = 3; } else if (w > 300) { columnNum = 1; } columnWidth = Math.floor(w/columnNum); columnWidth = columnWidth - 10; // Item width $container.find('.project-item').each(function() { var $item = $(this); var multiplier_w = $item.attr('class').match(/item-w(\d)/); var width = multiplier_w ? columnWidth*multiplier_w[1]-4 : columnWidth-4; // Update item width $item.css({ width: width }); }); return columnWidth; }; var isotope = function () { $container.isotope({ resizable: false, itemSelector: '.project-item', masonry: { columnWidth: colWidth(), gutter: 10 } }); }; // Calling Isotope isotope(); $(window).smartresize(isotope); })(jQuery); |
We are all set now, everything should be working perfectly. There is one more thing you may notice Sometimes isotope layout gets broken when it is called before image loading. If you face this issue, you should call Isotope one more time after the window load. Add the following codes below to your js/functions.js file:
1 2 3 4 |
// Call after content loading $(window).load(function () { isotope(); }); |
Add Filtering
It’s time to add filtering to sort project items. This step is optional, if you don’t need filtering then you may skip it. Initially, we will create an item group list and then update Isotope based on item click using jQuery. First, let’s create a filter nav list and style it. Update your index.html file by replacing it with the following codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<!-- Isotope Projects Wrapper --> <div class="projects-wrapper"> <!-- Project Filtering --> <ul id="filterNav" class="filters-list"> <li data-filter="*">All Items</li> <li data-filter=".landscape">Landscape</li> <li data-filter=".portrait">Portrait</li> <li data-filter=".wedding">Wedding</li> <li data-filter=".food">Food</li> </ul> <!-- Project Items List --> <div class="projects-list"> <div class="project-item portrait"> <!-- Item Image --> <img src="img/1.jpg" alt="Item 1"> </div> <div class="project-item portrait"> <!-- Item Image --> <img src="img/2.jpg" alt="Item 2"> </div> <div class="project-item landscape"> <!-- Item Image --> <img src="img/3.jpg" alt="Item 3"> </div> <div class="project-item landscape"> <!-- Item Image --> <img src="img/4.jpg" alt="Item 4"> </div> <div class="project-item food"> <!-- Item Image --> <img src="img/5.jpg" alt="Item 5"> </div> <div class="project-item food"> <!-- Item Image --> <img src="img/6.jpg" alt="Item 6"> </div> <div class="project-item landscape"> <!-- Item Image --> <img src="img/7.jpg" alt="Item 7"> </div> <div class="project-item wedding"> <!-- Item Image --> <img src="img/8.jpg" alt="Item 8"> </div> <div class="project-item wedding"> <!-- Item Image --> <img src="img/9.jpg" alt="Item 9"> </div> <div class="project-item portrait"> <!-- Item Image --> <img src="img/10.jpg" alt="Item 10"> </div> <div class="project-item landscape"> <!-- Item Image --> <img src="img/11.jpg" alt="Item 11"> </div> <div class="project-item portrait"> <!-- Item Image --> <img src="img/12.jpg" alt="Item 12"> </div> <div class="project-item portrait"> <!-- Item Image --> <img src="img/1.jpg" alt="Item 1"> </div> <div class="project-item food"> <!-- Item Image --> <img src="img/6.jpg" alt="Item 6"> </div> <div class="project-item landscape"> <!-- Item Image --> <img src="img/4.jpg" alt="Item 4"> </div> </div> </div> |
You will notice that on top we have a filter nav list item and a pointing class name inside project items. Now update your css/styles.css file with the following codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/* Projects */ .projects-wrapper { background: transparent url("../img/projects_main_bg.jpg") repeat-y center top; padding: 20px; text-align: center; min-height: 100vh; } .projects-list { margin: auto; left: 7px; } .project-item { width: 20%; margin-bottom: 15px; position: relative; -webkit-box-shadow: 2px 2px 5px rgba(0,0,0, .5); -moz-box-shadow: 2px 2px 5px rgba(0,0,0, .5); box-shadow: 2px 2px 5px rgba(0,0,0, .5); } .project-item img { width: 100%; height: auto; vertical-align: top; } .filters-list { border: 1px solid rgba(197, 32, 56, 0.5); background-color: rgba(255, 255, 255, .5); padding: 5px; display: inline-block; text-align: center; margin-bottom: 25px; } .filters-list li { font-family: sans-serif; display: inline-block; background-color: rgba(255, 255, 255, .75); padding: 10px; border: 1px solid #ddd; cursor: pointer; -webkit-transition: all .3s ease-in-out; -moz-transition: all .3s ease-in-out; -ms-transition: all .3s ease-in-out; -o-transition: all .3s ease-in-out; transition: all .3s ease-in-out; } .filters-list li:hover, .filters-list li.active { background-color: rgba(255, 255, 255, 1); border-color: #000; } |
To make the above codes interactive we need to add the following codes to our js/functions.js file:
1 2 3 4 5 6 7 8 9 10 11 |
// Activating Isotope Filter Navigation $('#filterNav').on('click', 'li', function () { // remove active previous $('#filterNav').find('li').removeClass('active'); // Add active class $(this).addClass('active'); var selector = $(this).attr('data-filter'); $container.isotope({ filter: selector }); }); |
Let’s try opening your index.html file in your browser where you should see the filter navigation at the top of the contents and after clicking it shows specific items.
Note: Chrome browser has a cache issue for static files. So it is recommended to use ctrl / command+shift+r to reload the content to see the new changes.
Conclusion
At this stage, everything should be running properly. You can do whatever you wish with items like adding hover overlay, lightbox effect on images etc.
Happy coding 🙂 🙂
Thank you so much! This is so great! Your code worked for me with my adjustments and I couldn’t be more thrilled!
Thanks, Denice Hailes for jewelfarazi.me