/*! * * portamento v1.1.1 - 2011-09-02 * http://simianstudios.com/portamento * * copyright 2011 kris noble except where noted. * * dual-licensed under the gplv3 and apache 2.0 licenses: * http://www.gnu.org/licenses/gpl-3.0.html * http://www.apache.org/licenses/license-2.0 * */ /** * * creates a sliding panel that respects the boundaries of * a given wrapper, and also has sensible behaviour if the * viewport is too small to display the whole panel. * * full documentation at http://simianstudios.com/portamento * * ---- * * uses the viewportoffset plugin by ben alman aka cowboy: * http://benalman.com/projects/jquery-misc-plugins/#viewportoffset * * uses a portion of cft by juriy zaytsev aka kangax: * http://kangax.github.com/cft/#is_position_fixed_supported * * uses code by matthew eernisse: * http://www.fleegix.org/articles/2006-05-30-getting-the-scrollbar-width-in-pixels * * builds on work by remy sharp: * http://jqueryfordesigners.com/fixed-floating-elements/ * */ (function($){ $.fn.portamento = function(options) { // we'll use the window and document objects a lot, so // saving them as variables now saves a lot of function calls var thiswindow = $(window); var thisdocument = $(document); /** * note by kris - included here so as to avoid namespace clashes. * * jquery viewportoffset - v0.3 - 2/3/2010 * http://benalman.com/projects/jquery-misc-plugins/ * * copyright (c) 2010 "cowboy" ben alman * dual licensed under the mit and gpl licenses. * http://benalman.com/about/license/ */ $.fn.viewportoffset = function() { var win = $(window); var offset = $(this).offset(); return { left: offset.left - win.scrollleft(), top: offset.top - win.scrolltop() }; }; /** * * a test to see if position:fixed is supported. * taken from cft by kangax - http://kangax.github.com/cft/#is_position_fixed_supported * included here so as to avoid namespace clashes. * */ function positionfixedsupported () { var container = document.body; if (document.createelement && container && container.appendchild && container.removechild) { var el = document.createelement("div"); if (!el.getboundingclientrect) { return null; } el.innerhtml = "x"; el.style.csstext = "position:fixed;top:100px;"; container.appendchild(el); var originalheight = container.style.height, originalscrolltop = container.scrolltop; container.style.height = "3000px"; container.scrolltop = 500; var elementtop = el.getboundingclientrect().top; container.style.height = originalheight; var issupported = elementtop === 100; container.removechild(el); container.scrolltop = originalscrolltop; return issupported; } return null; } /** * * get the scrollbar width by matthew eernisse. * http://www.fleegix.org/articles/2006-05-30-getting-the-scrollbar-width-in-pixels * included here so as to avoid namespace clashes. * */ function getscrollerwidth() { var scr = null; var inn = null; var wnoscroll = 0; var wscroll = 0; // outer scrolling div scr = document.createelement('div'); scr.style.position = 'absolute'; scr.style.top = '-1000px'; scr.style.left = '-1000px'; scr.style.width = '100px'; scr.style.height = '50px'; // start with no scrollbar scr.style.overflow = 'hidden'; // inner content div inn = document.createelement('div'); inn.style.width = '100%'; inn.style.height = '200px'; // put the inner div in the scrolling div scr.appendchild(inn); // append the scrolling div to the doc document.body.appendchild(scr); // width of the inner div sans scrollbar wnoscroll = inn.offsetwidth; // add the scrollbar scr.style.overflow = 'auto'; // width of the inner div width scrollbar wscroll = inn.offsetwidth; // remove the scrolling div from the doc document.body.removechild(document.body.lastchild); // pixel width of the scroller return (wnoscroll - wscroll); } // --------------------------------------------------------------------------------------------------- // get the definitive options var opts = $.extend({}, $.fn.portamento.defaults, options); // setup the vars accordingly var panel = this; var wrapper = opts.wrapper; var gap = opts.gap; var disableworkaround = opts.disableworkaround; var fullycapablebrowser = positionfixedsupported(); if(panel.length != 1) { // die gracefully if the user has tried to pass multiple elements // (multiple element support is on the todo list!) or no elements... return this; } if(!fullycapablebrowser && disableworkaround) { // just stop here, as the dev doesn't want to use the workaround return this; } // wrap the floating panel in a div, then set a sensible min-height and width panel.wrap('
'); var float_container = $('#portamento_container'); float_container.css({ 'min-height': panel.outerheight(), 'width': panel.outerwidth() }); // calculate the upper scrolling boundary var paneloffset = panel.offset().top; var panelmargin = parsefloat(panel.css('margintop').replace(/auto/, 0)); var realpaneloffset = paneloffset - panelmargin; var topscrollboundary = realpaneloffset - gap; // a couple of numbers to account for margins and padding on the relevant elements var wrapperpaddingfix = parsefloat(wrapper.css('paddingtop').replace(/auto/, 0)); var containermarginfix = parsefloat(float_container.css('margintop').replace(/auto/, 0)); // do some work to fix ie misreporting the document width var iefix = 0; var ismsie = /*@cc_on!@*/0; if (ismsie) { iefix = getscrollerwidth() + 4; } // --------------------------------------------------------------------------------------------------- thiswindow.bind("scroll.portamento", function () { if(thiswindow.height() > panel.outerheight() && thiswindow.width() >= (thisdocument.width() - iefix)) { // don't scroll if the window isn't big enough var y = thisdocument.scrolltop(); // current scroll position of the document if (y >= (topscrollboundary)) { // if we're at or past the upper scrolling boundary if((panel.innerheight() - wrapper.viewportoffset().top) - wrapperpaddingfix + gap >= wrapper.height()) { // if we're at or past the bottom scrolling boundary if(panel.hasclass('fixed') || thiswindow.height() >= panel.outerheight()) { // check that there's work to do panel.removeclass('fixed'); panel.css('top', (wrapper.height() - panel.innerheight()) + 'px'); } } else { // if we're somewhere in the middle panel.addclass('fixed'); if(fullycapablebrowser) { // supports position:fixed panel.css('top', gap + 'px'); // to keep the gap } else { panel.clearqueue(); panel.css('position', 'absolute').animate({top: (0 - float_container.viewportoffset().top + gap)}); } } } else { // if we're above the top scroll boundary panel.removeclass('fixed'); panel.css('top', '0'); // remove any added gap } } else { panel.removeclass('fixed'); } }); // --------------------------------------------------------------------------------------------------- thiswindow.bind("resize.portamento", function () { // stop users getting undesirable behaviour if they resize the window too small if(thiswindow.height() <= panel.outerheight() || thiswindow.width() < thisdocument.width()) { if(panel.hasclass('fixed')) { panel.removeclass('fixed'); panel.css('top', '0'); } } else { thiswindow.trigger('scroll.portamento'); // trigger the scroll event to place the panel correctly } }); // --------------------------------------------------------------------------------------------------- thiswindow.bind("orientationchange.portamento", function () { // if device orientation changes, trigger the resize event thiswindow.trigger('resize.portamento'); }); // --------------------------------------------------------------------------------------------------- // trigger the scroll event immediately so that the panel is positioned correctly if the page loads anywhere other than the top. thiswindow.trigger('scroll.portamento'); // return this to maintain chainability return this; }; // set some sensible defaults $.fn.portamento.defaults = { 'wrapper' : $('body'), // the element that will act as the sliding panel's boundaries 'gap' : 10, // the gap (in pixels) left between the top of the viewport and the top of the panel 'disableworkaround' : false // option to disable the workaround for not-quite capable browsers }; })(jquery);