/*!
* 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);