is a UITableView for the web: it speeds up scrolling through long lists and keeps your infinite feeds smooth and stable for your users. It is small, battle-tested, and highly performant. The code is hosted on Github, and distributed under the BSD License. The annotated source is available, as are demos both with Infinity turned off and on.

was built by Airbnb alongside the development of the Popular Wishlists and Friends feeds, and sees daily production use there today. Its only dependency is on jQuery.


It's easy to get started:

var $el = $('#my-infinite-container');
var listView = new infinity.ListView($el);

// ... When adding new content:

var $newContent = $('<p>Hello World</p>');
listView.append($newContent);

// ... To remove items from a list:

var listItems = listView.find('.my-items');
for(var index = 0, length = listItems.length; index < length; index++) {
  listItems[index].remove();
}

For full API documentation, keep reading.

Development version (unminified, with comments)

Download

Production version (2.3k minified & compressed)

Download

ListView API Reference

A ListView is a container that moves content in and out of the DOM on the scroll event. ListViews help keep repaint times of expensive pages down (and scrolling smooth) by making sure that there are never too many elements onscreen at a single time. ListViews excel at speeding up long lists of complex HTML elements, where new content is frequently appended to the end and existing content is rarely removed.

ListViews are simple, and have several caveats: they can't be nested inside each other, and they can't have heights set via CSS. Additionally, ListViews can't easily change sizes except by appending or removing elements, and so list items that need to slide open or change their sizing will be difficult to implement. Appending elements to a ListView is relatively fast, but removing elements is slower — so designs that need to remove elements multiple times a second at high framerates will struggle.

Note Because Firefox implements the unspecified behavior of <img> tags without src attributes differently than other browsers, if you're lazy-loading images inside of a ListView you should set their display to either block or inline-block, or Firefox will report the wrong height data to Infinity.

Table of Contents

Properties

  • $el: the jQuery element used as the parent of the ListView.
  • width: the width of the ListView.
  • height: the height of the ListView.

Methods

  • new ListView($el [, options]): creates a new ListView that will append its content inside the given jQuery element. The element passed in must be already in the DOM: otherwise, the ListView will cache incorrect size and positioning data. options is an options hash, and can be filled with the following options:
    • lazy: a function that will be called to lazily load any unloaded ListItems onscreen. The function will be called with the ListItem's jQuery element as its context.
  • append($el | listItem): appends a jQuery element or a ListItem to the ListView. Returns either the given ListItem, or the given jQuery element wrapped inside of a new ListItem.
  • find(selector | $el): given a selector or a jQuery element, returns the ListItem(s) that contain the element or selector. It's important to use the ListView to find the items you want, and not the DOM, since the items will often be removed from the DOM to save scrolling performance. Note that find() will return an array of ListItems, which are wrappers around jQuery elements that cache positioning data and can communicate easily with the parent ListView (which is taken care of behind the scenes during ListItem#remove() operations).
  • remove(): removes the ListView from the DOM, and cleans up after it.
  • cleanup(): unbinds all events, but does not remove the ListView from the DOM.

ListItem API Reference

ListItems are wrappers for individual DOM nodes placed inside a ListView. You'll normally never need to create ListItems yourself: the ListView will automatically wrap any jQuery elements you pass into it for you. However, you may find it useful to interact with them individually — for example, if you need to remove an element, you should do so by calling remove on the ListItem so that it reports the removal to the parent ListView — and so ListViews make it easy to inspect their collections of ListItems.

Table of Contents

Properties

  • $el: the jQuery element this ListItem wraps.
  • top: the top of the ListItem onscreen, relative to its ListView parent.
  • bottom: the bottom of the ListItem onscreen, relative to its ListView parent.
  • width: the width of the ListItem.
  • height: the height of the ListItem.

Methods

  • remove(): permanently removes the ListItem from the ListView and the ListItem's element from the DOM, and cleans up after everything.
  • cleanup(): unbinds all events and relinquishes all references it holds, but does not remove the ListItem from the ListView.

Configuring Infinity.js

We've tried to choose sensible defaults for Infinity, but sometimes there are special cases we haven't designed for. Infinity.js can be configured with two options to help make sure scrolling stays smooth for a variety of circumstances. The configuration options are as follows:

  • infinity.config.PAGE_TO_SCREEN_RATIO: internally, Infinity.js puts its ListItems into blocks of Pages, and this variable controls how large a page should be relative to the size of the viewport. By default, it is set to 3. Larger pages mean that Infinity has to swap pages less frequently, and smaller pages mean faster rendering times. If you have extremely complex pages, you might want to decrease this so that there's less onscreen to slow down the page; on the other hand, if you have very simple pages, you might want to increase this to minimize even further the possibility of jarring swaps or scroll flickering.
  • infinity.config.SCROLL_THROTTLE: Infinity.js throttles its scroll event: it only responds to scroll events every 350 milliseconds. If you'd like to make it respond more or less often, set this value to the desired number of milliseconds you'd like Infinity to throttle to. If you've increased the PAGE_TO_SCREEN_RATIO, you could increase the SCROLL_THROTTLE to do less frequent computation; if you've decreased the PAGE_TO_SCREEN_RATIO, you'll probably want to also decrease the SCROLL_THROTTLE to make sure the smaller pages get swapped onto the page more regularly, since your given buffer around the screen is smaller.

Future Work

  • Switch Infinity's internal data structure from a sorted array to a self-balancing binary tree. This will make remove() operations on ListItems much faster, and allow simple, fast support for prepends (or even arbitrary insertion points).
  • Make an API for notifying ListViews/ListItems when elements change size, so that the ListView can reorganize itself. This is probably a next step after switching to a self-balancing binary tree.
  • Make the buffer scroll-direction-aware, so that you can either only have a back buffer or only have a front buffer depending on the scroll direction. This would allow you to increase the PAGE_TO_SCREEN_RATIO a bit without negatively impacting paint times.