diff --git a/jquery.scrollIntoView.js b/jquery.scrollIntoView.js index 4ade417..df5e503 100644 --- a/jquery.scrollIntoView.js +++ b/jquery.scrollIntoView.js @@ -3,6 +3,8 @@ * The default browser behavior always places the element at the top or bottom of its container. * This override is smart enough to not scroll if the element is already visible. * + * Fix for if the immediate offset-parent is not scrollable + * * Copyright 2011 Arwid Bancewicz * Licensed under the MIT license * http://www.opensource.org/licenses/mit-license.php @@ -11,8 +13,46 @@ * @author Arwid Bancewicz http://arwid.ca * @version 0.3 */ - (function($) { + +(function($) { + + function getViewportHeight(elem) + { + var pH = elem.clientHeight; + var wH = $(window).height(); + + if ((pH > wH && elem.tagName == "BODY") || + (pH == 0 && elem.tagName == "BODY")) + return wH; + // case: if body's elements are all absolutely/fixed positioned, use window height + else + return pH; + } + + function getScrollParent(elem) + { + //Find parent of interest, keep track of scroll offsets. + var scrollOffset = 0; + var pEl = elem; + + // go up parents until we find one that scrolls + + while (pEl) { + // Can we scroll this? (simpler check) + if (pEl.scrollHeight > getViewportHeight(pEl)) + return pEl; + + // try next parent + pEl = pEl.parentElement; + } + + return undefined; + } + $.fn.scrollIntoView = function(duration, easing, complete) { + if (this.length == 0) + return; + // The arguments are optional. // The first argment can be false for no animation or a duration. // The first argment could also be a map of options. @@ -29,39 +69,36 @@ opts.smooth = false; } - // get enclosing offsets - var elY = Infinity, elH = 0; - if (this.size()==1)((elY=this.get(0).offsetTop)==null||(elH=elY+this.get(0).offsetHeight)); - else this.each(function(i,el){(el.offsetTopelH?elH=el.offsetTop+el.offsetHeight:null)}); - elH -= elY; - // start from the common ancester + // and find nearest scrollable parent. var pEl = this.commonAncestor().get(0); - - var wH = $(window).height(); + pEl = getScrollParent(pEl); - // go up parents until we find one that scrolls - while (pEl) { - var pY = pEl.scrollTop, pH = pEl.clientHeight; - if (pH > wH) pH = wH; - - // case: if body's elements are all absolutely/fixed positioned, use window height - if (pH == 0 && pEl.tagName == "BODY") pH = wH; - - if ( - // it wiggles? - (pEl.scrollTop != ((pEl.scrollTop += 1) == null || pEl.scrollTop) && (pEl.scrollTop -= 1) != null) || - (pEl.scrollTop != ((pEl.scrollTop -= 1) == null || pEl.scrollTop) && (pEl.scrollTop += 1) != null)) { - if (elY <= pY) scrollTo(pEl, elY); // scroll up - else if ((elY + elH) > (pY + pH)) scrollTo(pEl, elY + elH - pH); // scroll down - else scrollTo(pEl, undefined) // no scroll - return; - } - - // try next parent - pEl = pEl.parentNode; + + // get enclosing offsets relative to scrollable parent. + var pY = pEl.getBoundingClientRect().top; + var pH = getViewportHeight(pEl); + var elTop = Infinity, elBot = -Infinity; + if (this.length==1) + { + var rect = this[0].getBoundingClientRect(); + elTop = rect.top - pY; + elBot = rect.bottom - pY; } - + else this.each(function(i,el){ + var rect = el.getBoundingClientRect(); + elTop = Math.min(elTop, rect.top - pY); + elBot = Math.max(elBot, rect.bottom - pY); + }); + + //Scrolling + if (elTop < 0) + scrollTo(pEl, pEl.scrollTop + elTop); // scroll down + else if (elBot > pH) + scrollTo(pEl, pEl.scrollTop + (elBot - pH)); // scroll up + else + scrollTo(pEl, undefined) // no scroll + function scrollTo(el, scrollTo) { if (scrollTo === undefined) { if ($.isFunction(opts.complete)) opts.complete.call(el); @@ -74,13 +111,13 @@ } return this; }; - + $.fn.scrollIntoView.defaults = { smooth: true, duration: null, easing: $.easing && $.easing.easeOutExpo ? 'easeOutExpo': null, // Note: easeOutExpo requires jquery.effects.core.js - // otherwise jQuery will default to use 'swing' + // otherwise jQuery will default to use 'swing' complete: $.noop(), step: null, specialEasing: {} // cannot be null in jQuery 1.8.3 @@ -93,7 +130,12 @@ // completely? whether element is out of view completely var outOfView = true; this.each(function() { - var pEl = this.parentNode, pY = pEl.scrollTop, pH = pEl.clientHeight, elY = this.offsetTop, elH = this.offsetHeight; + + var pEl = getScrollParent(this.parentNode), + pY = pEl.scrollTop, + pH = getViewportHeight(pEl), + elY = this.offsetTop, + elH = this.offsetHeight; if (completely ? (elY) > (pY + pH) : (elY + elH) > (pY + pH)) {} else if (completely ? (elY + elH) < pY: elY < pY) {} else outOfView = false; @@ -133,5 +175,4 @@ } return $([]); } - -})(jQuery); \ No newline at end of file +})(jQuery);