Isotope is a great jquery plugin for magical layout. It has features like filtering, sorting and several layout modes. In this tutorial, I’ll show you how to create responsive masonry layout and filtering items. By default, 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 responsive masonry layout using Isotope
- Add filtering to sort items
Outcomes
- Successfully create responsive masonry layout using Isotope
Organizing your project and structuring HTML
The initial step is to organize your project for the better development process. For now, we will create folders and add all necessary files into 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 this 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 projects-wrapper div block. Add 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 following codes below to 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 Isotope plugin. Add this codes below into 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 into 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 following codes to 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 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 this codes above.
Resizing Layout
So far we have everything properly and our masonry columns are showing as they 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 really don’t expect to be there. All of the spaces inside wrapper should be distributed equally for each of the columns. To fix this issue we need to add a 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 that sometime Isotope layout gets broken when it is called before image loading. If you face this issue, you should call Isotope one more time after 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 item group list then update Isotope based on item click using jQuery. At first, let’s create filter nav list and style it. Update your index.html file by replacing with 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 filter nav list item and a pointing class name inside project items. Now update your css/styles.css file with 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 me make above codes interactive we need to add 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 open your index.html file into your browser where you should see the filter navigation at the top of the contents and after clicking it is showing specific items.
Note: Chrome browser has 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