jquery.fracs-0.11.js | |
|---|---|
/*! jQuery.fracs 0.11 - //larsjung.de/fracs - MIT License */
(function (window, document, $) {
'use strict';
/*jslint browser: true, confusion: true, nomen: true, regexp: true, vars: true, white: true */
/*global jQuery */ | |
| Some often used references. | var $window = $(window),
$document = $(document),
extend = $.extend,
isFn = $.isFunction,
slice = [].slice,
mathMax = Math.max,
mathMin = Math.min,
mathRound = Math.round,
isTypeOf = function (obj, type) {
return typeof obj === type;
},
isInstanceOf = function (obj, type) {
return obj instanceof type;
},
isHTMLElement = function (obj) {
return obj && obj.nodeType;
},
getHTMLElement = function (obj) {
return isHTMLElement(obj) ? obj : (isInstanceOf(obj, $) ? obj[0] : undefined);
},
reduce = function (elements, fn, current) {
$.each(elements, function (idx, element) {
current = fn.call(element, current, idx, element);
});
return current;
},
getId = (function () {
var ids = {},
nextId = 1;
return function (element) {
if (!element) {
return 0;
}
if (!ids[element]) {
ids[element] = nextId;
nextId += 1;
}
return ids[element];
};
}()),
equal = function (obj1, obj2, props) {
var i, l, prop;
if (obj1 === obj2) {
return true;
}
if (!obj1 || !obj2 || obj1.constructor !== obj2.constructor) {
return false;
}
for (i = 0, l = props.length; i < l; i += 1) {
prop = props[i];
if (obj1[prop] && isFn(obj1[prop].equals) && !obj1[prop].equals(obj2[prop])) {
return false;
}
if (obj1[prop] !== obj2[prop]) {
return false;
}
}
return true;
}; |
Objects | |
RectHolds the position and dimensions of a rectangle. The position might be relative to document, viewport or element space. | var Rect = function (left, top, width, height) { |
| Top left corner of the rectangle rounded to integers. | this.left = mathRound(left);
this.top = mathRound(top); |
| Dimensions rounded to integers. | this.width = mathRound(width);
this.height = mathRound(height); |
| Bottom right corner of the rectangle. | this.right = this.left + this.width;
this.bottom = this.top + this.height;
}; |
Prototype | extend(Rect.prototype, { |
| Checks if this instance equals | equals: function (that) {
return equal(this, that, ['left', 'top', 'width', 'height']);
}, |
| Returns the area of this rectangle. | area: function () {
return this.width * this.height;
}, |
| Returns a new | relativeTo: function (rect) {
return new Rect(this.left - rect.left, this.top - rect.top, this.width, this.height);
}, |
| Returns a new rectangle representing the intersection of this
instance and | intersection: function (rect) {
if (!isInstanceOf(rect, Rect)) {
return null;
}
var left = mathMax(this.left, rect.left),
right = mathMin(this.right, rect.right),
top = mathMax(this.top, rect.top),
bottom = mathMin(this.bottom, rect.bottom),
width = right - left,
height = bottom - top;
return (width >= 0 && height >= 0) ? new Rect(left, top, width, height) : null;
}, |
| Returns a new rectangle representing the smallest rectangle
containing this instance and | envelope: function (rect) {
if (!isInstanceOf(rect, Rect)) {
return this;
}
var left = mathMin(this.left, rect.left),
right = mathMax(this.right, rect.right),
top = mathMin(this.top, rect.top),
bottom = mathMax(this.bottom, rect.bottom),
width = right - left,
height = bottom - top;
return new Rect(left, top, width, height);
}
}); |
Static methods | extend(Rect, { |
| Returns a new instance of | ofContent: function (element, inContentSpace) {
if (!element || element === document || element === window) {
return new Rect(0, 0, $document.width(), $document.height());
}
if (inContentSpace) {
return new Rect(0, 0, element.scrollWidth, element.scrollHeight);
} else {
return new Rect(element.offsetLeft - element.scrollLeft, element.offsetTop - element.scrollTop, element.scrollWidth, element.scrollHeight);
}
}, |
| Returns a new instance of | ofViewport: function (element, inContentSpace) {
if (!element || element === document || element === window) {
return new Rect($window.scrollLeft(), $window.scrollTop(), $window.width(), $window.height());
}
if (inContentSpace) {
return new Rect(element.scrollLeft, element.scrollTop, element.clientWidth, element.clientHeight);
} else {
return new Rect(element.offsetLeft, element.offsetTop, element.clientWidth, element.clientHeight);
}
}, |
| Returns a new instance of | ofElement: function (element) {
var $element = $(element);
if (!$element.is(':visible')) {
return null;
}
var offset = $element.offset();
return new Rect(offset.left, offset.top, $element.outerWidth(), $element.outerHeight());
}
}); |
FractionsThe heart of the library. Creates and holds the
fractions data for the two specified rects. | var Fractions = function (visible, viewport, possible, rects) {
this.visible = visible || 0;
this.viewport = viewport || 0;
this.possible = possible || 0;
this.rects = (rects && extend({}, rects)) || null;
}; |
Prototype | extend(Fractions.prototype, { |
| Checks if this instance equals | equals: function (that) {
return this.fracsEqual(that) && this.rectsEqual(that);
}, |
| Checks if this instance equals | fracsEqual: function (that) {
return equal(this, that, ['visible', 'viewport', 'possible']);
}, |
| Checks if this instance equals | rectsEqual: function (that) {
return equal(this.rects, that.rects, ['document', 'element', 'viewport']);
}
}); |
Static methods | extend(Fractions, {
of: function (rect, viewport) {
var intersection, intersectionArea, possibleArea;
rect = (isHTMLElement(rect) && Rect.ofElement(rect)) || rect;
viewport = (isHTMLElement(viewport) && Rect.ofViewport(viewport)) || viewport || Rect.ofViewport();
intersection = rect.intersection(viewport);
if (!intersection) {
return new Fractions();
}
intersectionArea = intersection.area();
possibleArea = mathMin(rect.width, viewport.width) * mathMin(rect.height, viewport.height);
return new Fractions(
intersectionArea / rect.area(),
intersectionArea / viewport.area(),
intersectionArea / possibleArea,
{
document: intersection,
element: intersection.relativeTo(rect),
viewport: intersection.relativeTo(viewport)
}
);
}
}); |
Group | var Group = function (elements, viewport) {
this.els = elements;
this.viewport = viewport;
}; |
Helpers | |
| Accepted values for | var rectProps = ['width', 'height', 'left', 'right', 'top', 'bottom'],
fracsProps = ['possible', 'visible', 'viewport'], |
| Returns the specified | getValue = function (element, viewport, property) {
var obj;
if ($.inArray(property, rectProps) >= 0) {
obj = Rect.ofElement(element);
} else if ($.inArray(property, fracsProps) >= 0) {
obj = Fractions.of(element, viewport);
}
return obj ? obj[property] : 0;
}, |
| Sorting functions. | sortAscending = function (entry1, entry2) {
return entry1.val - entry2.val;
},
sortDescending = function (entry1, entry2) {
return entry2.val - entry1.val;
}; |
Prototype | extend(Group.prototype, { |
| Returns a sorted list of objects | sorted: function (property, descending) {
var viewport = this.viewport;
return $.map(this.els, function (element) {
return {
el: element,
val: getValue(element, viewport, property)
};
})
.sort(descending ? sortDescending : sortAscending);
}, |
| Returns the first element of the sorted list returned by | best: function (property, descending) {
return this.els.length ? this.sorted(property, descending)[0] : null;
}
}); |
ScrollState | var ScrollState = function (element) {
var content = Rect.ofContent(element, true),
viewport = Rect.ofViewport(element, true),
w = content.width - viewport.width,
h = content.height - viewport.height;
this.content = content;
this.viewport = viewport;
this.width = w <= 0 ? null : viewport.left / w;
this.height = h <= 0 ? null : viewport.top / h;
this.left = viewport.left;
this.top = viewport.top;
this.right = content.right - viewport.right;
this.bottom = content.bottom - viewport.bottom;
}; |
Prototype | extend(ScrollState.prototype, { |
| Checks if this instance equals | equals: function (that) {
return equal(this, that, ['width', 'height', 'left', 'top', 'right', 'bottom', 'content', 'viewport']);
}
}); |
Viewport | var Viewport = function (element) {
this.el = element || window;
}; |
Prototype | extend(Viewport.prototype, { |
| Checks if this instance equals | equals: function (that) {
return equal(this, that, ['el']);
},
scrollState: function () {
return new ScrollState(this.el);
},
scrollTo: function (left, top, duration) {
var $el = this.el === window ? $('html,body') : $(this.el);
left = left || 0;
top = top || 0;
duration = isNaN(duration) ? 1000 : duration;
$el.stop(true).animate({scrollLeft: left, scrollTop: top}, duration);
},
scroll: function (left, top, duration) {
var $el = this.el === window ? $window : $(this.el);
left = left || 0;
top = top || 0;
this.scrollTo($el.scrollLeft() + left, $el.scrollTop() + top, duration);
},
scrollToRect: function (rect, paddingLeft, paddingTop, duration) {
paddingLeft = paddingLeft || 0;
paddingTop = paddingTop || 0;
this.scrollTo(rect.left - paddingLeft, rect.top - paddingTop, duration);
},
scrollToElement: function (element, paddingLeft, paddingTop, duration) {
var rect = Rect.ofElement(element).relativeTo(Rect.ofContent(this.el));
this.scrollToRect(rect, paddingLeft, paddingTop, duration);
}
}); |
Callbacks | |
callbacks mix-inExpects | var callbacksMixIn = { |
| Initial setup. | init: function () {
this.callbacks = $.Callbacks('memory unique');
this.currVal = null;
this.prevVal = null; |
| A proxy to make | this.checkProxy = $.proxy(this.check, this);
this.autoCheck();
}, |
| Adds a new callback function. | bind: function (callback) {
this.callbacks.add(callback);
}, |
| Removes a previously added callback function. | unbind: function (callback) {
if (callback) {
this.callbacks.remove(callback);
} else {
this.callbacks.empty();
}
}, |
| Triggers all callbacks with the current values. | trigger: function () {
this.callbacks.fireWith(this.context, [this.currVal, this.prevVal]);
}, |
| Checks if value changed, updates attributes | check: function (event) {
var value = this.updatedValue(event);
if (value === undefined) {
return false;
}
this.prevVal = this.currVal;
this.currVal = value;
this.trigger();
return true;
}, |
| Auto-check configuration. | $autoTarget: $window,
autoEvents: 'load resize scroll', |
| Enables/disables automated checking for changes on the specified | autoCheck: function (on) {
this.$autoTarget[on === false ? 'off' : 'on'](this.autoEvents, this.checkProxy);
}
}; |
FracsCallbacks | var FracsCallbacks = function (element, viewport) {
this.context = element;
this.viewport = viewport;
this.init();
}; |
Prototype | extend(FracsCallbacks.prototype, callbacksMixIn, {
updatedValue: function () {
var value = Fractions.of(this.context, this.viewport);
if (!this.currVal || !this.currVal.equals(value)) {
return value;
}
}
}); |
GroupCallbacks | var GroupCallbacks = function (elements, viewport, property, descending) {
this.context = new Group(elements, viewport);
this.property = property;
this.descending = descending;
this.init();
}; |
Prototype | extend(GroupCallbacks.prototype, callbacksMixIn, {
updatedValue: function () {
var best = this.context.best(this.property, this.descending);
if (best) {
best = best.val > 0 ? best.el : null;
if (this.currVal !== best) {
return best;
}
}
}
}); |
ScrollStateCallbacks | var ScrollStateCallbacks = function (element) {
if (!element || element === window || element === document) {
this.context = window;
} else {
this.context = element;
this.$autoTarget = $(element);
}
this.init();
}; |
Prototype | extend(ScrollStateCallbacks.prototype, callbacksMixIn, {
updatedValue: function () {
var value = new ScrollState(this.context);
if (!this.currVal || !this.currVal.equals(value)) {
return value;
}
}
}); |
modplug 0.7 | |
| Use http://larsjung.de/modplug to attach the plug-in to jQuery. | var modplug = function (namespace, options) { |
| Save the initial settings. | var settings = extend({}, options), |
| Helper function to apply default methods. | applyMethod = function (obj, args, methodName, methods) { |
| If | methodName = isFn(methodName) ? methodName.apply(obj, args) : methodName; |
| If method exists then apply it and return the result ... | if (isFn(methods[methodName])) {
return methods[methodName].apply(obj, args);
} |
| ... otherwise raise an error. | $.error('Method "' + methodName + '" does not exist on jQuery.' + namespace);
}, |
| This function gets exposed as | statics = function () { |
| Try to apply a default method. | return applyMethod(this, slice.call(arguments), settings.defaultStatic, statics);
}, |
| This function gets exposed as | methods = function (method) { |
| If | if (isFn(methods[method])) {
return methods[method].apply(this, slice.call(arguments, 1));
} |
| ... otherwise try to apply a default method. | return applyMethod(this, slice.call(arguments), settings.defaultMethod, methods);
}, |
| Adds/overwrites plug-in methods. This function gets exposed as
| plug = function (options) {
if (options) {
extend(statics, options.statics);
extend(methods, options.methods);
} |
| Make sure that | statics.modplug = plug;
}; |
| Save objects or methods previously registered to the desired
namespace. They are available via | plug.prev = {
statics: $[namespace],
methods: $.fn[namespace]
}; |
| Init the plug-in by adding the specified statics and methods. | plug(options); |
| Register the plug-in. | $[namespace] = statics;
$.fn[namespace] = methods;
}; |
Register the plug-in | |
| The namespace used to register the plug-in and to attach data to elements. | var namespace = 'fracs'; |
| The methods are sorted in alphabetical order. All methods that do not
provide a return value will return | modplug(namespace, { |
Static methodsThese methods are accessible via | statics: { |
| Build version. | version: '0.11', |
| Publish object constructors (for testing). | Rect: Rect,
Fractions: Fractions,
Group: Group,
ScrollState: ScrollState,
Viewport: Viewport,
FracsCallbacks: FracsCallbacks,
GroupCallbacks: GroupCallbacks,
ScrollStateCallbacks: ScrollStateCallbacks, |
fracsThis is the default method. So instead of calling
Returns the fractions for a given | fracs: function (rect, viewport) {
return Fractions.of(rect, viewport);
}
}, |
Instance methodsThese methods are accessible via | methods: { |
'content'Returns the content rect of the first selected element in content space. If no element is selected it returns the document rect. | content: function (inContentSpace) {
return this.length ? Rect.ofContent(this[0], inContentSpace) : null;
}, |
'envelope'Returns the smallest rectangle that containes all selected elements. | envelope: function () {
return reduce(this, function (current) {
var rect = Rect.ofElement(this);
return current ? current.envelope(rect) : rect;
});
}, |
'fracs'This is the default method. So the first parameter Returns the fractions for the first selected element.
Binds a callback function that will be invoked if fractions have changed
after a
Unbinds the specified callback function.
Unbinds all callback functions.
Checks if fractions changed and if so invokes all bound callback functions. | fracs: function (action, callback, viewport) {
if (!isTypeOf(action, 'string')) {
viewport = callback;
callback = action;
action = null;
}
if (!isFn(callback)) {
viewport = callback;
callback = null;
}
viewport = getHTMLElement(viewport);
var ns = namespace + '.fracs.' + getId(viewport);
if (action === 'unbind') {
return this.each(function () {
var cbs = $(this).data(ns);
if (cbs) {
cbs.unbind(callback);
}
});
} else if (action === 'check') {
return this.each(function () {
var cbs = $(this).data(ns);
if (cbs) {
cbs.check();
}
});
} else if (isFn(callback)) {
return this.each(function () {
var $this = $(this),
cbs = $this.data(ns);
if (!cbs) {
cbs = new FracsCallbacks(this, viewport);
$this.data(ns, cbs);
}
cbs.bind(callback);
});
}
return this.length ? Fractions.of(this[0], viewport) : null;
}, |
'intersection'Returns the greatest rectangle that is contained in all selected elements. | intersection: function () {
return reduce(this, function (current) {
var rect = Rect.ofElement(this);
return current ? current.intersection(rect) : rect;
});
}, |
'max'Reduces the set of selected elements to those with the maximum value
of the specified property.
Valid values for property are
Binds a callback function to the set of selected elements that gets triggert whenever the element with the highest value of the specified property changes. | max: function (property, callback, viewport) {
if (!isFn(callback)) {
viewport = callback;
callback = null;
}
viewport = getHTMLElement(viewport);
if (callback) {
new GroupCallbacks(this, viewport, property, true).bind(callback);
return this;
}
return this.pushStack(new Group(this, viewport).best(property, true).el);
}, |
'min'Reduces the set of selected elements to those with the minimum value
of the specified property.
Valid values for property are
Binds a callback function to the set of selected elements that gets triggert whenever the element with the lowest value of the specified property changes. | min: function (property, callback, viewport) {
if (!isFn(callback)) {
viewport = callback;
callback = null;
}
viewport = getHTMLElement(viewport);
if (callback) {
new GroupCallbacks(this, viewport, property).bind(callback);
return this;
}
return this.pushStack(new Group(this, viewport).best(property).el);
}, |
'rect'Returns the dimensions for the first selected element in document space. | rect: function () {
return this.length ? Rect.ofElement(this[0]) : null;
}, |
'scrollState'Returns the current scroll state for the first selected element.
Binds a callback function that will be invoked if scroll state has changed
after a
Unbinds the specified callback function.
Unbinds all callback functions.
Checks if scroll state changed and if so invokes all bound callback functions. | scrollState: function (action, callback) {
var ns = namespace + '.scrollState';
if (!isTypeOf(action, 'string')) {
callback = action;
action = null;
}
if (action === 'unbind') {
return this.each(function () {
var cbs = $(this).data(ns);
if (cbs) {
cbs.unbind(callback);
}
});
} else if (action === 'check') {
return this.each(function () {
var cbs = $(this).data(ns);
if (cbs) {
cbs.check();
}
});
} else if (isFn(callback)) {
return this.each(function () {
var $this = $(this),
cbs = $this.data(ns);
if (!cbs) {
cbs = new ScrollStateCallbacks(this);
$this.data(ns, cbs);
}
cbs.bind(callback);
});
}
return this.length ? new ScrollState(this[0]) : null;
}, |
'scroll'Scrolls the selected elements relative to its current position,
| scroll: function (left, top, duration) {
return this.each(function () {
new Viewport(this).scroll(left, top, duration);
});
}, |
'scrollTo'Scrolls the selected elements to the specified element or an absolute position,
| scrollTo: function (element, paddingLeft, paddingTop, duration) {
if ($.isNumeric(element)) {
duration = paddingTop;
paddingTop = paddingLeft;
paddingLeft = element;
element = null;
}
element = getHTMLElement(element);
return this.each(function () {
if (element) {
new Viewport(this).scrollToElement(element, paddingLeft, paddingTop, duration);
} else {
new Viewport(this).scrollTo(paddingLeft, paddingTop, duration);
}
});
}, |
'scrollToThis'Scrolls the viewport (window) to the first selected element in the specified time,
| scrollToThis: function (paddingLeft, paddingTop, duration, viewport) {
viewport = new Viewport(getHTMLElement(viewport));
viewport.scrollToElement(this[0], paddingLeft, paddingTop, duration);
return this;
}, |
'softLink'Converts all selected page intern links | softLink: function (paddingLeft, paddingTop, duration, viewport) {
viewport = new Viewport(getHTMLElement(viewport));
return this.filter('a[href^=#]').each(function () {
var $a = $(this);
$a.on('click', function () {
viewport.scrollToElement($($a.attr('href'))[0], paddingLeft, paddingTop, duration);
});
});
}, |
'sort'Sorts the set of selected elements by the specified property.
Valid values for property are | sort: function (property, ascending, viewport) {
if (!isTypeOf(ascending, 'boolean')) {
viewport = ascending;
ascending = null;
}
viewport = getHTMLElement(viewport);
return this.pushStack($.map(new Group(this, viewport).sorted(property, !ascending), function (entry) {
return entry.el;
}));
}, |
'viewport'Returns the current viewport of the first selected element in content space. If no element is selected it returns the document's viewport. | viewport: function (inContentSpace) {
return this.length ? Rect.ofViewport(this[0], inContentSpace) : null;
}
}, |
Defaults | defaultStatic: 'fracs',
defaultMethod: 'fracs'
});
}(window, document, jQuery));
|