Load More: a better way of viewing list items
Presenting long lists of items to a user in a web page has been a challenge to developers, designers, and UX engineers alike since the advent of the web itself. This is because web sites have to cater for a variety of user behaviour and there are technical limitations on what solutions are possible. No two websites are alike and the purpose of a list in one place may be different from another list elsewhere. For example, you might have a list of search results and a catalogue of product items. In the first instance a user most likely is interested mainly in the items that appear at the top of the list. In the second instance she is less likely to favour one item over another and likely to view more items in a session. A solution that works well in one instance may not be optimal for another. (This shows the value of custom web development!)
Much analysis has been carried out on this subject. In the course of writing this article I drew heavily on a study into product lists by the Baymard Institute, an organization that specialises in research into the usability of commercial web sites. Other sources that I consulted are listed in the links at the bottom of this page.
There are three techniques that are in common use. The most traditional technique is pagination, where content is split up into separate web pages and controls are provided for navigating through each of these.
A more recent technique is 'Infinite Scroll'. As the user scrolls down the page, content is added to the bottom on the fly so that she never reaches the end of the page until all the content has been loaded.
A third technique, aimed at addressing the shortcomings of the others, is 'Load More', a sort of hybrid, combining Infinite Scroll with pagination.
The idea of Infinite Scroll is that it removes this arbitrary interruption altogether and the user experiences the list exactly as if its entirety were loaded into the browser from the start. But it turns out that interrupting the user at certain points can be a good idea: If the user is able to constantly scroll a list of items, they may just do that, never actually stopping to focus on a particular item.
Infinite Scroll also favours results somewhere in the middle of the list over those at the top. If this list has been sorted by relevance first, this is obviously undesirable.
Infinite Scroll can be hard to implement well, particularly if you care about accessibity and performance.
The Baymard Institute recommend Load More, as it takes the best features of the preceding techniques and (supposedly) mitigates their shortcomings. Load More implementations however face many of the same technical challenges that Infinite Scroll does.
I set myself the project of trying to create a successful implementation of the Load More technique. This is an ongoing project and I will update this article as I progress. The name 'Hypatia' is abitrary, but honors the Alexandrian mathematician of that name. You can visit the demo here. The github repository is in the links below.
The main requirements are as follows:
- When navigating back to a page, the scroll position should be preserved.
- Content should be navigable using the keyboard
- Memory footprint of the page should not expand infinitely
- placeholder content should be provided whilst awaiting content from network
And some nice to haves:
- Different parts of the list should have a url associated with them so that they can be 'bookmarkable'.
When content needs to be dynamically loaded across a network, it is good practise to provide placeholder content in order to show that something is happening and that the actual content is on its way, instead of just showing a blank screen and a revolving spinner. When the content is partially loaded, the page can be made usable enough so that the user can interact with the loaded content, even if it intermixed with placeholder items. This means that the page is initially rendered with placeholder items, and as the real content is loaded from the network, the placeholder items are swapped in for items rendered with real data. I call this process 'reification'. This presents various technical challenges. For instance the reified items are likely a different height from their placeholders. This means the position of all the other items below them will have to be recalculated.
Keeping memory footprint low
There are limits to how many dom nodes can be supported at once in the browser. If the user loads a long enough list of items, this could obviously be a problem. We address this by only maintaining the nodes in the dom that are currently visible in the viewport (plus a little extra for padding). As the user scrolls, the nodes for items leaving the viewport are removed from the DOM, whilst nodes for items about to enter it are added in. This means that we have to manage the layout of elements and the behaviour of the scroll bar. All the nodes are positioned absolutely at the top of the container and are positioned manually using transforms. We keep track behind the scenes of each item's offset from the top of the container.
Preserving scroll position
When the user returns to the page via the back or forwards button from another page the content visible on the page should be the same as when she left it. In theory browsers actually do this but with SPAs it tends not to work very well. What we need to do is store the scroll state of the page when the user navigates away from it. We need to consider the ways in which she can do this: By clicking on a link or by navigating through the history using the back and forward buttons. The best place to store this information is in the history state object which is available when we navigate back to the page. We can save this in a beforeUnload event handler which will run before the user navigates away from the page.
We do not actually store the scroll position. Instead we store the index of the item that was at the top of the viewport when navigation occurred along with an offset of how much of this was visible. This is because if we are lazy loading content in, the scroll position of this item may change as content is loaded in above it. We don't want to content to move as she is reading it. To achieve this effect we use a technique called 'scroll anchoring'. As new content is loaded in above the anchored item, we alter the scroll value so that the anchored item always remains fixed in position relative to the viewport.
Keyboard navigable content
In a simple HTML page, we can set the tab order by applying tabindex to all of our items to '0' and let the browser do the work. This will not work here because the items do not necessarily appear in the DOM in tab order. We need to handle keyboard events ourselves. This is a simple case of keeping track of the item to be focused and applying the focus programmatically. The browser scrolls automatically to items with focus.
A LoadMore implementation poses many challenges which include issues of accessibility, SEO, usability, and performance. I have started a project which aims to meet these challenges and will continue to work on it and update this article as I make progress.