Created
April 2, 2010 12:56
-
-
Save bangpound/353099 to your computer and use it in GitHub Desktop.
Infinite Scroll jQuery plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*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