A responsive thumbnail gallery using the WordPress REST API

So I was snowed in this week-end and got to think about how badly WordPress needed another gallery plugin. I KNOW, RIGHT??

Anyway. WordPress has a ton of gallery plugins… but none of them did exactly what I wanted. I wanted a responsive gallery with a cool masonry layout image wall with no gap. I wanted infinite scrolling without a huge performance tax. And, because all my images had the same size, I wanted some random image size adjustments to make it look more, well, random.

So “Cactus Masonry” and “Image Wall” came close but not quite there. The masonry layout itself is not too hard to accomplish – you can even use a plugin like Masonry to achieve it – but getting it to layout items with no gap was a bit trickier. So I started with a code pen to figure out how to accomplish that part. The merge function is the interesting part. I actually got the idea from an exercise of the Python algorithm book I am reading. I think that looks OK, other than a few glitches at the bottom, but I figured in a continuously scrolling wall they will disappear.

Not much to look at, but try to imagine with pictures!

Not much to look at, but try to imagine with pictures!

Next came the infinite scrolling part. We could use a custom Ajax call, but the WordPress REST API just became part of the core so this made it easy. One thing I found weird – even though the REST API is part of the core, you have to install the REST API plugin to be able to actually query it. OK then 2 things, well actually 3 things were needed:

  • The rest_prepare_* filter has to be used to add a field for the image thumbnail. This is a dynamic hook, the name depend on the post type so for example rest_prepare_post if you are doing post thumbnails
  • I had to use the query_vars filter to allow querying by meta values – by default it won’t allow that, and in my case the posts to be displayed were determined based on a meta value
  • For a custom post type the show_in_rest parameter needs to be set to true. On this site we were using Custom Post Types UI which conveniently has this parameter available.

And with that I had my API URL:

var today = (new Date()).toISOString(),
    apiUrl = '/wp-json/wp/v2/event?per_page=30&meta_key=event_date&filter[meta_value]=' + 
             today + '&filter[meta_key]=event_date&filter[meta_compare]=%3C',

And a displayNextPage function that could be used to provide the virtual scrolling. You can see I am cheating here by hard-coding the image size. Also I am setting it as data fields, rather than sizing the figure directly – the reason being that I want it to reset to the original size when the window is maximized.

function displayNextPage() {
 if (doingRequest) {
 return;
 }
 doingRequest = true;
 var url = apiUrl + '&page=' + currentPage;
 currentPage++;
 $.get(url, null, function (data) {
 for (var i = 0; i < data.length; i++) {
 var $brick = $('<figure data-width="720" data-height="400">' +
 '<a href="' + data[i].link + '">' +
 '<img src="' + data[i].featured_image_thumbnail_url + '" alt="' + data[i].title.rendered + '"/>' +
 '<figcaption>' + data[i].title.rendered + '</figcaption>' +
 '</a>' +
 '</figure>');
 bottom = addBrick($container, numColumns, colRange, minWidth, bottom, $brick);
 }
 resetContainerHeight($container, bottom);

 doingRequest = false;
 }, 'json');
}

With a scroll watcher function:

$(window).scroll(debounce(function () {
    var containerHeight = $container.height(),
        windowHeight = $(window).height(),
    // fixed top navigation bar
        topBar = $container.offset().top,
        scrollPosition = window.scrollY,
        remaining = containerHeight - (windowHeight - topBar) - scrollPosition;
    if (remaining < windowHeight - topBar) {
        // less than 1 page left
        displayNextPage();
    }
}, 350));

All that was needed then was a function to recalculate the parameters after a resize… trying to come up with a way for them to still look OK on the phone:

function calculateBestWidth(minWidth) {
    var w = $container.width();
    numColumns = Math.ceil(w / (minWidth / 2));
    colRange = [Math.ceil(numColumns / 5), Math.ceil(numColumns / 3)];
}

This is what I end up with:

The completed image wall

The completed image wall

I saved the full script to a Github gist… I considered making a plugin for it but there is quite a bit of stuff that is specific and would have to be abstracted out… the biggest thing is the image size – for a general purpose plugin, the REST call would have to return the width / height (as stored in the attachment metadata). Whereas in my case the sizes are not even stored in WP, due to the odd way that these images are loaded.

I can see myself coming back to this layout – being able to customize the inner logic myself rather than relying on a plugin is very useful.

Leave a Reply

Your email address will not be published. Required fields are marked *