In this post, I show how to implement masonry, isotope, infinite scroll, and imagesloaded in WordPress. I go more into detail about isotope because it’s a more robust library than masonry.
Masonry/Isotope are used for creating a dynamic grid that shuffles the position of grid elements dynamically.
Infinite Scroll allows you to load additional posts based on pagination, which significantly decreases page load time.
ImagesLoaded allows you to wait until all images are fully loaded so that the grid doesn’t behave strangely as the page is loading.
Add Masonry (Included with WordPress or CDN) + Infinite Scroll + ImagesLoaded
add_action( 'wp_enqueue_scripts', function () { // Pull Masonry from the core of WordPress wp_enqueue_script( 'masonry' ); // OR Pull Masonry from a cdn wp_enqueue_script( 'masonry', '//cdnjs.cloudflare.com/ajax/libs/masonry/3.3.2/masonry.pkgd.min.js' ); wp_enqueue_script( 'imagesloaded', '//cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.3/imagesloaded.min.js', [ 'jquery' ], null, true ); wp_enqueue_script( 'infinitescroll', '//cdnjs.cloudflare.com/ajax/libs/jquery-infinitescroll/2.1.0/jquery.infinitescroll.min.js', [ 'jquery' ], null, true ); }, 100 );
Or Isotope + Infinite Scroll + ImagesLoaded
add_action( 'wp_enqueue_scripts', function () { wp_enqueue_script( 'imagesloaded', '//cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.3/imagesloaded.min.js', [ 'jquery' ], null, true ); wp_enqueue_script( 'isotope', '//cdnjs.cloudflare.com/ajax/libs/jquery.isotope/3.0.4/isotope.pkgd.min.js', [ 'jquery' ], null, true ); wp_enqueue_script( 'infinitescroll', '//cdnjs.cloudflare.com/ajax/libs/jquery-infinitescroll/2.1.0/jquery.infinitescroll.min.js', [ 'jquery' ], null, true ); }, 100 );
Activate Masonry On A Specific Container
I’m not going to go through this code in detail, but if you have any issues in your specific implementation just hit me up.
jQuery(window).on('load', function () { var $ = jQuery; // Initiate Masonry var $container = $('#masonry-wrapper'); $container.imagesLoaded(function () { $container.masonry({ itemSelector: '.masonry-item', columnWidth: '.masonry-item' }); }); $.extend($.infinitescroll.prototype, { _nearbottom_local: function () { var opts = this.options, $max = 0; $('.masonry-item').each(function () { $btm = $(this).position().top + jQuery(this).height(); if ($max < $btm) { $max = $btm; } }); this._debug('math:', $(window).scrollTop() + $(window).height() - opts.bufferPx, $max); return ( $(window).scrollTop() + $(window).height() - opts.bufferPx > $max ); } }); $container.infinitescroll({ debug: true, navSelector: '.navigation', // selector for the paged navigation nextSelector: '.navigation a', // selector for the NEXT link (to page 2) itemSelector: '.masonry-item', // selector for all items you'll retrieve behavior: 'local', container: '#main', loading: { finishedMsg: 'No more pages to load.', img: 'http://i.imgur.com/6RMhx.gif' }, }, // trigger Masonry as a callback function (newElements) { // hide new items while they are loading var $newElems = $(newElements).css({opacity: 0}); // ensure that images load before adding to masonry layout $newElems.imagesLoaded(function () { // show elems now they're ready $newElems.animate({opacity: 1}); $container.masonry('appended', $newElems, true); }); } ); }); /* end of as elements loads event */
Or Using Isotope
var $container = $('#posts'); $(window).on('load', function () { // Fire Isotope only when images are loaded $container.imagesLoaded(function () { $container.isotope({ itemSelector: '.post', }); }); // Filter $('#filters').on('click', 'a', function () { var filterValue = $(this).attr('data-filter-value'); $container.isotope({filter: filterValue}); }); // Infinite Scroll $('#posts').infinitescroll({ navSelector: 'div.pagination', nextSelector: 'div.pagination a:first', itemSelector: '.post', bufferPx: 200, loading: { finishedMsg: 'We\'re done here.', //img: +templateUrl+'ajax-loader.gif' }, }, // Infinite Scroll Callback function (newElements) { var $newElems = jQuery(newElements).hide(); $newElems.imagesLoaded(function () { $newElems.fadeIn(); $container.isotope('appended', $newElems); }); }); });
The Post Loop (Isotope Example)
<div class="container-fluid"> <div id="posts" class="row"> <?php if ( $query->have_posts() ) : while ( $query->have_posts() ) : $query->the_post(); ?> <a href="<?php the_permalink(); ?>"> <div class="post"> <h4><?php the_title(); ?></h4> <div class="image"> <?php post_thumb( get_the_ID() ); ?> </div> <div class="content"> <?php the_content(); ?> </div> </div> </a> <?php endwhile; endif; ?> </div><!-- /#posts --> <div class="pagination">; <?php previous_posts_link( '« Newer posts' ); ?> <?php next_posts_link( 'Older posts »', $query->max_num_pages ); ?> </div> </div><!-- /.container-fluid -->
The Filter Code (for combination filter)
/** * Isotope ready post filter */ function post_filter() { ?> <div id="post-filter"> <h4>Property Type</h4> <ul class="filter option-set property-type" data-filter-group="project-type"> <li><a href="#" data-filter-value="*" class="selected">Everything</a></li> <?php $parent = get_category_by_slug( 'project-type' )->term_id; $args = array( 'parent' => $parent, ); $terms = get_terms( 'category', $args ); // get all categories, but you can use any taxonomy if ( count( $terms ) > 0 ) { //If there are more than 0 terms foreach ( $terms as $term ) { //for each term: echo "<li><a href='#' data-filter-value='." . $term->slug . "'>" . $term->name . "</a></li>\n"; //create a list item with the current term slug for sorting, and name for label } } ?> </ul> <h4>Services</h4> <ul class="filter option-set services" data-filter-group="services"> <li><a href="#" data-filter-value="*" class="selected">Everything</a></li> <?php $parent = get_category_by_slug( 'service' )->term_id; $args = array( 'parent' => $parent, ); $terms = get_terms( 'category', $args ); // get all categories, but you can use any taxonomy if ( count( $terms ) > 0 ) { //If there are more than 0 terms foreach ( $terms as $term ) { //for each term: echo "<li><a href='#' data-filter-value='." . $term->slug . "'>" . $term->name . "</a></li>\n"; //create a list item with the current term slug for sorting, and name for label } } ?> </ul> <button class="btn btn-primary">Reset</button> </div> <?php }
Mini Explanation
- We are targeting the
#posts
container on initiation of isotope — if you are using bootstrap, you should add your.row
class to this container - We are targeting the
.post
item selector, which means that we need to wrap each post in the WordPress loop with this class - We must have valid pagination for infinite scrolling to work and there must be an action of scrolling to “reach” the pagination in order for new posts to be loaded — so push it down below the window if you have to.
Adding a Combination Filter to Isotope
// filter buttons $('#post-filter').on('click', 'a', function (e) { e.preventDefault(); var $this = $(this); // don't proceed if already selected if ($this.hasClass('selected')) { return; } var $optionSet = $this.parents('.option-set'); // change selected class $optionSet.find('.selected').removeClass('selected'); $this.addClass('selected'); // store filter value in object // i.e. filters.color = 'red' var group = $optionSet.attr('data-filter-group'); filters[group] = $this.attr('data-filter-value'); // convert object into array var isoFilters = []; for (var prop in filters) { isoFilters.push(filters[prop]) } var selector = isoFilters.join(''); console.log(selector); $container.isotope({filter: selector}); return false; }); $('#post-filter').on('click', '.reset-post-filter', function () { $container.isotope({ filter: '*' }); });
See A Non-WordPress Example In Action
See the Pen jQuery Masonry + Infinite Scroll + ImagesLoaded (WordPress Compatible) by Mike Doubintchik (@allurewebsolutions) on CodePen.
Hi Mike,
great and helpful tutorial. but how to stop the infinite scroll? the part “finishedMsg: ‘We\’re done here.’,” doesn’t work. it maybe should have to do something with the 404.php?
Greetings from germany
Daniel – this is quite an old solution. There are much better ways to implement this these days. I was hoping to find an article online on how to do it, but no one seems to have written a full tutorial. When I have some time, I’ll write an updated tutorial.
If you’d like help with your specific implementation, I would need to see your code. You can contact me through our contact page.