∞ 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.
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.
$el
: the jQuery element used as the parent
of the ListView.
width
: the width of the ListView.
height
: the height of the ListView.
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.
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.
$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.
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.
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.
remove()
operations on ListItems much faster,
and allow simple, fast support for prepends (or even
arbitrary insertion points).
PAGE_TO_SCREEN_RATIO
a bit without
negatively impacting paint times.