Skip to content

Instantly share code, notes, and snippets.

@bangpound
Created April 2, 2010 12:56
Show Gist options
  • Save bangpound/353099 to your computer and use it in GitHub Desktop.
Save bangpound/353099 to your computer and use it in GitHub Desktop.
Infinite Scroll jQuery plugin
/*global Image, console, document, jQuery, window */
/*!
// Infinite Scroll jQuery plugin
// copyright Paul Irish, licensed GPL & MIT
// version 1.4.100211
// home and docs: http://www.infinite-scroll.com
*/
"use strict";
(function ($) {
$.fn.infinitescroll = function (options, callback) {
var opts = $.extend({}, $.infinitescroll.defaults, options),
props = $.infinitescroll, // shorthand
path;
// console log wrapper.
function debug() {
if (opts.debug && window.console) {
console.log.call(console, arguments);
}
}
// grab each selector option and see if any fail.
function areSelectorsValid(opts) {
for (var key in opts) {
if (opts.hasOwnProperty(key)) {
if (key.indexOf && key.indexOf('Selector') > -1 && $(opts[key]).length === 0) {
debug('Your ' + key + ' found no elements.');
return false;
}
return true;
}
}
}
// 'document' means the full document usually, but sometimes the content of the overflow'd div in local mode
function getDocumentHeight() {
// weird doubletouch of scrollheight because http://soulpass.com/2006/07/24/ie-and-scrollheight/
return opts.localMode ? ($(props.container)[0].scrollHeight && $(props.container)[0].scrollHeight)
// needs to be document's height. (not props.container's) html's height is wrong in IE.
: $(document).height();
}
function isNearBottom() {
// distance remaining in the scroll
// computed as: document height - distance already scroll - viewport height - buffer
var pixelsFromWindowBottomToBottom = 0 +
getDocumentHeight() - (
opts.localMode ? $(props.container).scrollTop() :
// have to do this bs because safari doesnt report a scrollTop on the html element
($(props.container).scrollTop() || $(props.container.ownerDocument.body).scrollTop())
) - $(opts.localMode ? props.container : window).height();
debug('math:', pixelsFromWindowBottomToBottom, props.pixelsFromNavToBottom);
// if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
return (pixelsFromWindowBottomToBottom - opts.bufferPx < props.pixelsFromNavToBottom);
}
function showDoneMsg() {
props.loadingMsg
.find('img').hide()
.parent()
.find('div').html(opts.donetext).animate({opacity: 1}, 2000).fadeOut('normal');
// user provided callback when done
opts.errorCallback();
}
function infscrSetup() {
if (props.isDuringAjax || props.isInvalidPage || props.isDone) {
return;
}
if (!isNearBottom(opts, props)) {
return;
}
$(document).trigger('retrieve.infscr');
} // end of infscrSetup()
function loadCallback(data, status, xhr) {
var box, scrollTo;
// if we've hit the last page...
if (props.isDone) {
showDoneMsg();
return false;
} else {
// if it didn't return anything
if ($(opts.itemSelector, data).length === 0) {
// fake an ajaxError so we can quit.
$.event.trigger("ajaxError", [{status: 404}]);
}
box = $('<div/>').appendTo(opts.contentSelector);
$(opts.itemSelector, data).appendTo(box);
if ($(opts.nextSelector, data).length === 0) {
props.isDone = true;
} else {
path = $(opts.nextSelector, data).attr('href');
}
// fadeout currently makes the <em>'d text ugly in IE6
props.loadingMsg.fadeOut('normal');
// smooth scroll to ease in the new content
if (opts.animate) {
scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px';
$('html,body').animate({scrollTop: scrollTo}, 800, function () {
props.isDuringAjax = false;
});
}
// pass in the new DOM element as context for the callback
callback.call(box[0]);
if (!opts.animate) {
props.isDuringAjax = false;
} // once the call is done, we can allow it again.
}
}
function kickOffAjax() {
// we dont want to fire the ajax multiple times
props.isDuringAjax = true;
// show the loading message and hide the previous/next links
props.loadingMsg.appendTo(opts.contentSelector).show();
$(opts.navSelector).hide();
debug('heading into ajax', path);
$.get(path, null, loadCallback);
}
// lets get started.
$.browser.ie6 = $.browser.msie && $.browser.version < 7;
callback = callback || function () {};
if (!areSelectorsValid(opts)) {
return false;
}
// we doing this on an overflow:auto div?
props.container = opts.localMode ? this : document.documentElement;
// contentSelector we'll use for our .load()
opts.contentSelector = opts.contentSelector || this;
// get the relative URL - everything past the domain name.
path = $(opts.nextSelector).attr('href');
if (!path) {
debug('Navigation selector not found');
return;
}
// reset scrollTop in case of page refresh:
if (opts.localMode) {
$(props.container)[0].scrollTop = 0;
}
// distance from nav links to bottom
// computed as: height of the document + top offset of container - top offset of nav link
props.pixelsFromNavToBottom = getDocumentHeight() +
(props.container === document.documentElement ? 0 : $(props.container).offset().top) -
$(opts.navSelector).offset().top;
// define loading msg
props.loadingMsg = $('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="' +
opts.loadingImg + '" /><div>' + opts.loadingText + '</div></div>');
// preload the image
(new Image()).src = opts.loadingImg;
// set up our bindings
$(document).ajaxError(function (e, xhr, opt) {
debug('Page not found. Self-destructing...');
// die if we're out of pages.
if (xhr.status === 404) {
showDoneMsg();
props.isDone = true;
$(opts.localMode ? this : window).unbind('scroll.infscr');
}
});
// bind scroll handler to element (if its a local scroll) or window
$(opts.localMode ? this : window)
.bind('scroll.infscr', infscrSetup)
.trigger('scroll.infscr'); // trigger the event, in case it's a short page
$(document).bind('retrieve.infscr', kickOffAjax);
return this;
}; // end of $.fn.infinitescroll()
// options and read-only properties object
$.infinitescroll = {
defaults : {
debug : false,
preload : false,
nextSelector : "div.navigation a:first",
loadingImg : "http://www.infinite-scroll.com/loading.gif",
loadingText : "<em>Loading the next set of posts...</em>",
donetext : "<em>Congratulations, you've reached the end of the internet.</em>",
navSelector : "div.navigation",
contentSelector : null, // not really a selector. :) it's whatever the method was called on..
extraScrollPx : 150,
itemSelector : "div.post",
animate : false,
localMode : false,
bufferPx : 40,
errorCallback : function () {}
},
loadingImg : undefined,
loadingMsg : undefined,
container : undefined,
isDuringAjax : false,
isInvalidPage : false,
isDone : false // for when it goes all the way through the archive.
};
}(jQuery));
/*jslint passfail: true, white: true, browser: true, devel: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true, indent: 2 */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment