1074 lines
27 KiB
JavaScript
1074 lines
27 KiB
JavaScript
/*! 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
|
|
// =======
|
|
|
|
// Rect
|
|
// ----
|
|
// Holds 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 `that` in position and dimensions.
|
|
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 `Rect` representig this rect relative to `rect`.
|
|
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 `rect`. If there is no intersection the return value
|
|
// is `null`.
|
|
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 `rect`.
|
|
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 `Rect` representing the content of the
|
|
// specified element. Since the coordinates are in content space the
|
|
// `left` and `top` values are always set to `0`. If `inDocSpace` is
|
|
// `true` the rect gets returned in document space.
|
|
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 `Rect` representing the viewport of the
|
|
// specified element. If `inDocSpace` is `true` the rect gets returned
|
|
// in document space instead of content space.
|
|
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 `Rect` representing a given
|
|
// `HTMLElement`. The dimensions respect padding and border widths. If
|
|
// the element is invisible (as determined by jQuery) the return value
|
|
// is null.
|
|
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());
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// Fractions
|
|
// ---------
|
|
// The heart of the library. Creates and holds the
|
|
// fractions data for the two specified rects. `viewport` defaults to
|
|
// `Rect.ofViewport()`.
|
|
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 `that` in all attributes.
|
|
equals: function (that) {
|
|
|
|
return this.fracsEqual(that) && this.rectsEqual(that);
|
|
},
|
|
|
|
// Checks if this instance equals `that` in all fraction attributes.
|
|
fracsEqual: function (that) {
|
|
|
|
return equal(this, that, ['visible', 'viewport', 'possible']);
|
|
},
|
|
|
|
// Checks if this instance equals `that` in all rectangle attributes.
|
|
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 `property` parameters below.
|
|
var rectProps = ['width', 'height', 'left', 'right', 'top', 'bottom'],
|
|
fracsProps = ['possible', 'visible', 'viewport'],
|
|
|
|
// Returns the specified `property` for `HTMLElement element` or `0`
|
|
// if `property` is invalid.
|
|
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 `{el: HTMLElement, val: Number}`
|
|
// for the specified `property`. `descending` defaults to `false`.
|
|
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 `sorted` above,
|
|
// or `null` if this list is empty.
|
|
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 `that`.
|
|
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 `that`.
|
|
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-in
|
|
// ----------------
|
|
// Expects `context: HTMLElement` and `updatedValue: function`.
|
|
var callbacksMixIn = {
|
|
|
|
// Initial setup.
|
|
init: function () {
|
|
|
|
this.callbacks = $.Callbacks('memory unique');
|
|
this.currVal = null;
|
|
this.prevVal = null;
|
|
|
|
// A proxy to make `check` bindable to events.
|
|
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 `currVal` and
|
|
// `prevVal` accordingly and triggers the callbacks. Returns
|
|
// `true` if value changed, otherwise `false`.
|
|
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 `window`
|
|
// events.
|
|
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` is a function apply it to get the actual
|
|
// method name.
|
|
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 `$.<namespace>`.
|
|
statics = function () {
|
|
|
|
// Try to apply a default method.
|
|
return applyMethod(this, slice.call(arguments), settings.defaultStatic, statics);
|
|
},
|
|
|
|
// This function gets exposed as `$(selector).<namespace>`.
|
|
methods = function (method) {
|
|
|
|
// If `method` exists then apply it ...
|
|
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
|
|
// `$.<namespace>.modplug` to make the plug-in extendable.
|
|
plug = function (options) {
|
|
|
|
if (options) {
|
|
extend(statics, options.statics);
|
|
extend(methods, options.methods);
|
|
}
|
|
|
|
// Make sure that `$.<namespace>.modplug` points to this
|
|
// function after adding new methods.
|
|
statics.modplug = plug;
|
|
};
|
|
|
|
// Save objects or methods previously registered to the desired
|
|
// namespace. They are available via `$.<namespace>.modplug.prev`.
|
|
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 `this` to enable method chaining.
|
|
modplug(namespace, {
|
|
|
|
// Static methods
|
|
// --------------
|
|
// These methods are accessible via `$.fracs.<methodname>`.
|
|
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,
|
|
|
|
// ### fracs
|
|
// This is the **default method**. So instead of calling
|
|
// `$.fracs.fracs(...)` simply call `$.fracs(...)`.
|
|
//
|
|
// Returns the fractions for a given `Rect` and `viewport`,
|
|
// viewport defaults to `$.fracs.viewport()`.
|
|
//
|
|
// $.fracs(rect: Rect, [viewport: Rect]): Fractions
|
|
fracs: function (rect, viewport) {
|
|
|
|
return Fractions.of(rect, viewport);
|
|
}
|
|
},
|
|
|
|
// Instance methods
|
|
// ----------------
|
|
// These methods are accessible via `$(selector).fracs('<methodname>', ...)`.
|
|
methods: {
|
|
|
|
// ### 'content'
|
|
// Returns the content rect of the first selected element in content space.
|
|
// If no element is selected it returns the document rect.
|
|
//
|
|
// .fracs('content'): Rect
|
|
content: function (inContentSpace) {
|
|
|
|
return this.length ? Rect.ofContent(this[0], inContentSpace) : null;
|
|
},
|
|
|
|
// ### 'envelope'
|
|
// Returns the smallest rectangle that containes all selected elements.
|
|
//
|
|
// .fracs('envelope'): Rect
|
|
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 `'fracs'`
|
|
// can be omitted.
|
|
//
|
|
// Returns the fractions for the first selected element.
|
|
//
|
|
// .fracs(): Fractions
|
|
//
|
|
// Binds a callback function that will be invoked if fractions have changed
|
|
// after a `window resize` or `window scroll` event.
|
|
//
|
|
// .fracs(callback(fracs: Fractions, prevFracs: Fractions)): jQuery
|
|
//
|
|
// Unbinds the specified callback function.
|
|
//
|
|
// .fracs('unbind', callback): jQuery
|
|
//
|
|
// Unbinds all callback functions.
|
|
//
|
|
// .fracs('unbind'): jQuery
|
|
//
|
|
// Checks if fractions changed and if so invokes all bound callback functions.
|
|
//
|
|
// .fracs('check'): jQuery
|
|
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.
|
|
//
|
|
// .fracs('intersection'): Rect
|
|
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 `possible`, `visible`, `viewport`,
|
|
// `width`, `height`, `left`, `right`, `top`, `bottom`.
|
|
//
|
|
// .fracs('max', property: String): jQuery
|
|
//
|
|
// 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.
|
|
//
|
|
// .fracs('max', property: String, callback(best: Element, prevBest: Element)): jQuery
|
|
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 `possible`, `visible`, `viewport`,
|
|
// `width`, `height`, `left`, `right`, `top`, `bottom`.
|
|
//
|
|
// .fracs('min', property: String): jQuery
|
|
//
|
|
// 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.
|
|
//
|
|
// .fracs('min', property: String, callback(best: Element, prevBest: Element)): jQuery
|
|
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.
|
|
//
|
|
// .fracs('rect'): Rect
|
|
rect: function () {
|
|
|
|
return this.length ? Rect.ofElement(this[0]) : null;
|
|
},
|
|
|
|
// ### 'scrollState'
|
|
// Returns the current scroll state for the first selected element.
|
|
//
|
|
// .fracs('scrollState'): ScrollState
|
|
//
|
|
// Binds a callback function that will be invoked if scroll state has changed
|
|
// after a `resize` or `scroll` event.
|
|
//
|
|
// .fracs('scrollState', callback(scrollState: scrollState, prevScrollState: scrollState)): jQuery
|
|
//
|
|
// Unbinds the specified callback function.
|
|
//
|
|
// .fracs('scrollState', 'unbind', callback): jQuery
|
|
//
|
|
// Unbinds all callback functions.
|
|
//
|
|
// .fracs('scrollState', 'unbind'): jQuery
|
|
//
|
|
// Checks if scroll state changed and if so invokes all bound callback functions.
|
|
//
|
|
// .fracs('scrollState', 'check'): jQuery
|
|
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,
|
|
// `padding` defaults to `0`, `duration` to `1000`.
|
|
//
|
|
// .fracs('scroll', element: HTMLElement/jQuery, [paddingLeft: int,] [paddingTop: int,] [duration: int]): jQuery
|
|
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,
|
|
// `padding` defaults to `0`, `duration` to `1000`.
|
|
//
|
|
// .fracs('scrollTo', element: HTMLElement/jQuery, [paddingLeft: int,] [paddingTop: int,] [duration: int]): jQuery
|
|
// .fracs('scrollTo', [left: int,] [top: int,] [duration: int]): jQuery
|
|
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,
|
|
// `padding` defaults to `0`, `duration` to `1000`.
|
|
//
|
|
// .fracs('scrollToThis', [paddingLeft: int,] [paddingTop: int,] [duration: int,] [viewport: HTMLElement/jQuery]): jQuery
|
|
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 `<a href="#...">` into soft links.
|
|
// Uses `scrollTo` to scroll to the location.
|
|
//
|
|
// .fracs('softLink', [paddingLeft: int,] [paddingTop: int,] [duration: int,] [viewport: HTMLElement/jQuery]): jQuery
|
|
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 `possible`, `visible`, `viewport`,
|
|
// `width`, `height`, `left`, `right`, `top`, `bottom`. The default
|
|
// sort order is descending.
|
|
//
|
|
// .fracs('sort', property: String, [ascending: boolean]): jQuery
|
|
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.
|
|
//
|
|
// .fracs('viewport'): Rect
|
|
viewport: function (inContentSpace) {
|
|
|
|
return this.length ? Rect.ofViewport(this[0], inContentSpace) : null;
|
|
}
|
|
},
|
|
|
|
// Defaults
|
|
// --------
|
|
defaultStatic: 'fracs',
|
|
defaultMethod: 'fracs'
|
|
});
|
|
|
|
}(window, document, jQuery));
|