/*! * jquery scrollintoview() plugin and :scrollable selector filter * * version 1.8 (14 jul 2011) * requires jquery 1.4 or newer * * copyright (c) 2011 robert koritnik * licensed under the terms of the mit license * http://www.opensource.org/licenses/mit-license.php */ (function ($) { var converter = { vertical: { x: false, y: true }, horizontal: { x: true, y: false }, both: { x: true, y: true }, x: { x: true, y: false }, y: { x: false, y: true } }; var settings = { duration: "fast", direction: "both" }; var rootrx = /^(?:html)$/i; // gets border dimensions var borders = function (domelement, styles) { styles = styles || (document.defaultview && document.defaultview.getcomputedstyle ? document.defaultview.getcomputedstyle(domelement, null) : domelement.currentstyle); var px = document.defaultview && document.defaultview.getcomputedstyle ? true : false; var b = { top: (parsefloat(px ? styles.bordertopwidth : $.css(domelement, "bordertopwidth")) || 0), left: (parsefloat(px ? styles.borderleftwidth : $.css(domelement, "borderleftwidth")) || 0), bottom: (parsefloat(px ? styles.borderbottomwidth : $.css(domelement, "borderbottomwidth")) || 0), right: (parsefloat(px ? styles.borderrightwidth : $.css(domelement, "borderrightwidth")) || 0) }; return { top: b.top, left: b.left, bottom: b.bottom, right: b.right, vertical: b.top + b.bottom, horizontal: b.left + b.right }; }; var dimensions = function ($element) { var win = $(window); var isroot = rootrx.test($element[0].nodename); return { border: isroot ? { top: 0, left: 0, bottom: 0, right: 0} : borders($element[0]), scroll: { top: (isroot ? win : $element).scrolltop(), left: (isroot ? win : $element).scrollleft() }, scrollbar: { right: isroot ? 0 : $element.innerwidth() - $element[0].clientwidth, bottom: isroot ? 0 : $element.innerheight() - $element[0].clientheight }, rect: (function () { var r = $element[0].getboundingclientrect(); return { top: isroot ? 0 : r.top, left: isroot ? 0 : r.left, bottom: isroot ? $element[0].clientheight : r.bottom, right: isroot ? $element[0].clientwidth : r.right }; })() }; }; $.fn.extend({ scrollintoview: function (options) { /// scrolls the first element in the set into view by scrolling its closest scrollable parent. /// additional options that can configure scrolling: /// duration (default: "fast") - jquery animation speed (can be a duration string or number of milliseconds) /// direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both") /// complete (default: none) - a function to call when scrolling completes (called in context of the dom element being scrolled) /// /// returns the same jquery set that this function was run on. options = $.extend({}, settings, options); options.direction = converter[typeof (options.direction) === "string" && options.direction.tolowercase()] || converter.both; var dirstr = ""; if (options.direction.x === true) dirstr = "horizontal"; if (options.direction.y === true) dirstr = dirstr ? "both" : "vertical"; var el = this.eq(0); var scroller = el.closest(":scrollable(" + dirstr + ")"); // check if there's anything to scroll in the first place if (scroller.length > 0) { scroller = scroller.eq(0); var dim = { e: dimensions(el), s: dimensions(scroller) }; var rel = { top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top), bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom, left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left), right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right }; var animoptions = {}; // vertical scroll if (options.direction.y === true) { if (rel.top < 0) { animoptions.scrolltop = dim.s.scroll.top + rel.top; } else if (rel.top > 0 && rel.bottom < 0) { animoptions.scrolltop = dim.s.scroll.top + math.min(rel.top, -rel.bottom); } } // horizontal scroll if (options.direction.x === true) { if (rel.left < 0) { animoptions.scrollleft = dim.s.scroll.left + rel.left; } else if (rel.left > 0 && rel.right < 0) { animoptions.scrollleft = dim.s.scroll.left + math.min(rel.left, -rel.right); } } // scroll if needed if (!$.isemptyobject(animoptions)) { if (rootrx.test(scroller[0].nodename)) { scroller = $("html,body"); } scroller .animate(animoptions, options.duration) .eq(0) // we want function to be called just once (ref. "html,body") .queue(function (next) { $.isfunction(options.complete) && options.complete.call(scroller[0]); next(); }); } else { // when there's nothing to scroll, just call the "complete" function $.isfunction(options.complete) && options.complete.call(scroller[0]); } } // return set back return this; } }); var scrollvalue = { auto: true, scroll: true, visible: false, hidden: false }; $.extend($.expr[":"], { scrollable: function (element, index, meta, stack) { var direction = converter[typeof (meta[3]) === "string" && meta[3].tolowercase()] || converter.both; var styles = (document.defaultview && document.defaultview.getcomputedstyle ? document.defaultview.getcomputedstyle(element, null) : element.currentstyle); var overflow = { x: scrollvalue[styles.overflowx.tolowercase()] || false, y: scrollvalue[styles.overflowy.tolowercase()] || false, isroot: rootrx.test(element.nodename) }; // check if completely unscrollable (exclude html element because it's special) if (!overflow.x && !overflow.y && !overflow.isroot) { return false; } var size = { height: { scroll: element.scrollheight, client: element.clientheight }, width: { scroll: element.scrollwidth, client: element.clientwidth }, // check overflow.x/y because ipad (and possibly other tablets) don't dislay scrollbars scrollablex: function () { return (overflow.x || overflow.isroot) && this.width.scroll > this.width.client; }, scrollabley: function () { return (overflow.y || overflow.isroot) && this.height.scroll > this.height.client; } }; return direction.y && size.scrollabley() || direction.x && size.scrollablex(); } }); })(jquery);