Endless pages in combination with masonry

Post featured image

LingoHub constantly changes and evolves in order to provide better service and user experience. In this article we will briefly cover our last improvement to the user interface that we think enhances the user experience: the translator search results page is now presented in a form of so-called endless pages (infinite scrolling) consisting of containers of varied height that fit into all available space (masonry). This tutorial should also help cover this technique in other contexts where endless pages are part of your web design approach.

endlesspage First, lets introduce these terms:

Endless pages

Typically, when a large chunk of data should be displayed to the user, several strategies can be applied:

  • everything can be rendered within a single page
  • it can be split into a number of different pages
  • data can be continuously appended on demand (our endless pages approach)

Putting everything in single page requires least thought and effort from the developer, but it results in longer load times and also it might deliver more then the user asked for.

Splitting the large data into multiple pages allows the user to quickly access only the part that he currently cares about, instead of having to (down)load everything. Still, it is required from the user to manually trigger the load of the next batch of data, and there will also be a delay until the user gets the response and the content is displayed. Also often the user may be too lazy to click on "next" and may miss some valuable content.

endless pagination ilustrationIn the third approach, the user is served a small amount of data that quickly loads, and as the page is scrolled down additional data is appended to the bottom (infinite scrolling). This results in a quicker and smoother user experience. It also results in some additional requests to the server (compared to the first case), but it does not matter to the user, because they are sent just before the user reaches the end of the currently available content. The result are seemingly endless pages.


float left ilustration

If the large set of data that we need to display to the user consists of many smaller subsets, rendering it in a sequential way does not spend the available view area very efficiently (there is a lot of white space to the right), so displaying it in the cells floated in the same direction can be much better. If the cells are of the same height this works great. Otherwise, there can be some left over space (see the illustration to the right). If there was something like float:up available, the problem might be solved by CSS, but there is not.

So, to assemble the cells neatly like the blocks in a wall (that's why it's called masonry), two approaches can be used: CSS and JS. A pure CSS solution does sound attractive, and one can be found in our further reading section, but older versions of Microsoft Internet Explorer (even IE9) do not do well with the CSS magic, so we opted for the JS. The problem can be solved with javascript by calculating the dimensions of each cell and neatly absolutely positioning it next to the others.

Isotope & Infinite Scroll for Endless Pages

Implementing endless pages is easy, implementing it in combination with masonry is not. The only available solution that supports both that we found out there is Isotope jQuery plugin. It offers a bunch of options, effects, transitions, filters but what we really need is its integration with another jQuery plugin - Infinite Scroll.

Here is how we set up endless pages with masonry using these two plugins:


Most of it is pretty straight forward: isotope is initialized on a container, we tell it which elements should it treat as cells and also the column width. We set that to 280, and since our cells are fixed at 250 pixels width, that produces 30px between the columns. Isotope allows setting the gutter width, but that did not work for us.

One of nice features of the infinite scroll plugin is that it can safely degrade to a page that is navigable for users with javascript support disabled or non present. In fact, the page is initially displayed just as it would be if the plugin was not present, with only the first "section" rendered, and a navigation bar with links to next sections (if available). As the user scrolls down and the plugin is triggered, the navigation bar is being hidden and the link of the next section is used to retrieve it asynchronously and it is appended in the bottom.

So, to configure the infinite scroll plugin,

  • it should be initialized on a container
  • it should know the selector of the navigation container so that it can be hidden,
  • it should know the selector of the next link so that it can use it to request the next section
  • and also the selector for the items that should be pulled from the response and appended to the content.

By default the infinite scroll is being triggered roughly as soon as the end of the current content is reached. In order to make the loading even smoother we try to pre-fetch the next section some moments before it is needed for display:

pixelsFromNavToBottom: -Math.round($(window).height() \* 0.9),
bufferPx: Math.round($(window).height() \* 0.9),

If the user reaches the end of the content before the next section is fetched, infinite scroll can display a "loading results" message, and an "end of results" message if there are no more sections. We provided our custom localized messages in the data attribute of the container.

In the end, calling the isotope in the infinite scroll callback is crucial for all of this to work:

function(newElements) {
\$container.isotope('appended', $(newElements))

This endless pages solution worked almost perfectly for us in most browsers. However, we had a small problem with Chrome. After the content is appended by infinite scroll, the cells would overlap. We realized that the reason is that the cell size was calculated before the images were loaded. Providing the image dimensions in the did not help, so we tried out another solution for this problem suggested in the isotope help section: to delay calling isotope until all the images are loaded. This did fix the overlapping, but there was a noticeable glitch (FOUC) in the rendering. So we just wrapped the img in a div with fixed dimensions and that solved the problem for Chrome.

Another issue that we had with Chrome is that on the initial display (of the first section) sometimes the vertical gutters would not be consistent, or the cells would be even glued together. We solved this by forcing isotope to recalculate the cells positions after the user first starts scrolling:

var isotope_not_fixed = /chrom(e|ium)/.test(navigator.userAgent.toLowerCase())
\$(document).scroll(function() {
  if(isotope_not_fixed) {
    isotope_not_fixed = false
    $('#translators').isotope({itemSelector: '.item'})

This setup worked for us and our users can now enjoy its benefits. You can let us know how you solved the problem with endless pages in the comments, or maybe you found a more elegant solution. Let me know.

Further reading