How To Implement Masonry (Or Isotope) + Infinite Scroll + ImagesLoaded In WordPress

WordPress Tutorials by Allure Web Solutions

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.

Mike Doubintchik

Author Mike Doubintchik

More posts by Mike Doubintchik

Leave a Reply