HackMD/public/vendor/jquery-scrollspy.js
2016-10-08 08:13:40 +08:00

267 lines
10 KiB
JavaScript

/*
* jQuery ScrollSpy Plugin
* Author: @sxalexander, softwarespot
* Licensed under the MIT license
*/
(function jQueryScrollspy(window, $) {
// Plugin Logic
$.fn.extend({
scrollspy: function scrollspy(options, action) {
// If the options parameter is a string, then assume it's an 'action', therefore swap the parameters around
if (_isString(options)) {
var tempOptions = action;
// Set the action as the option parameter
action = options;
// Set to be the reference action pointed to
options = tempOptions;
}
// override the default options with those passed to the plugin
options = $.extend({}, _defaults, options);
// sanitize the following option with the default value if the predicate fails
_sanitizeOption(options, _defaults, 'container', _isObject);
// cache the jQuery object
var $container = $(options.container);
// check if it's a valid jQuery selector
if ($container.length === 0) {
return this;
}
// sanitize the following option with the default value if the predicate fails
_sanitizeOption(options, _defaults, 'namespace', _isString);
// check if the action is set to DESTROY/destroy
if (_isString(action) && action.toUpperCase() === 'DESTROY') {
$container.off('scroll.' + options.namespace);
return this;
}
// sanitize the following options with the default values if the predicates fails
_sanitizeOption(options, _defaults, 'buffer', $.isNumeric);
_sanitizeOption(options, _defaults, 'max', $.isNumeric);
_sanitizeOption(options, _defaults, 'min', $.isNumeric);
// callbacks
_sanitizeOption(options, _defaults, 'onEnter', $.isFunction);
_sanitizeOption(options, _defaults, 'onLeave', $.isFunction);
_sanitizeOption(options, _defaults, 'onLeaveTop', $.isFunction);
_sanitizeOption(options, _defaults, 'onLeaveBottom', $.isFunction);
_sanitizeOption(options, _defaults, 'onTick', $.isFunction);
if ($.isFunction(options.max)) {
options.max = options.max();
}
if ($.isFunction(options.min)) {
options.min = options.min();
}
// check if the mode is set to VERTICAL/vertical
var isVertical = window.String(options.mode).toUpperCase() === 'VERTICAL';
return this.each(function each() {
// cache this
var _this = this;
// cache the jQuery object
var $element = $(_this);
// count the number of times a container is entered
var enters = 0;
// determine if the scroll is with inside the container
var inside = false;
// count the number of times a container is left
var leaves = 0;
// create a scroll listener for the container
$container.on('scroll.' + options.namespace, function onScroll() {
// cache the jQuery object
var $this = $(this);
// create a position object literal
var position = {
top: $this.scrollTop(),
left: $this.scrollLeft(),
};
var containerHeight = $container.height();
var max = options.max;
var min = options.min;
var xAndY = isVertical ? position.top + options.buffer : position.left + options.buffer;
if (max === 0) {
// get the maximum value based on either the height or the outer width
max = isVertical ? containerHeight : $container.outerWidth() + $element.outerWidth();
}
// if we have reached the minimum bound, though are below the max
if (xAndY >= min && xAndY <= max) {
// trigger the 'scrollEnter' event
if (!inside) {
inside = true;
enters++;
// trigger the 'scrollEnter' event
$element.trigger('scrollEnter', {
position: position,
});
// call the 'onEnter' function
if (options.onEnter !== null) {
options.onEnter(_this, position);
}
}
// trigger the 'scrollTick' event
$element.trigger('scrollTick', {
position: position,
inside: inside,
enters: enters,
leaves: leaves,
});
// call the 'onTick' function
if (options.onTick !== null) {
options.onTick(_this, position, inside, enters, leaves);
}
} else {
if (inside) {
inside = false;
leaves++;
// trigger the 'scrollLeave' event
$element.trigger('scrollLeave', {
position: position,
leaves: leaves,
});
// call the 'onLeave' function
if (options.onLeave !== null) {
options.onLeave(_this, position);
}
if (xAndY <= min) {
// trigger the 'scrollLeaveTop' event
$element.trigger('scrollLeaveTop', {
position: position,
leaves: leaves,
});
// call the 'onLeaveTop' function
if (options.onLeaveTop !== null) {
options.onLeaveTop(_this, position);
}
} else if (xAndY >= max) {
// trigger the 'scrollLeaveBottom' event
$element.trigger('scrollLeaveBottom', {
position: position,
leaves: leaves,
});
// call the 'onLeaveBottom' function
if (options.onLeaveBottom !== null) {
options.onLeaveBottom(_this, position);
}
}
} else {
// Idea taken from: http://stackoverflow.com/questions/5353934/check-if-element-is-visible-on-screen
var containerScrollTop = $container.scrollTop();
// Get the element height
var elementHeight = $element.height();
// Get the element offset
var elementOffsetTop = $element.offset().top;
if ((elementOffsetTop < (containerHeight + containerScrollTop)) && (elementOffsetTop > (containerScrollTop - elementHeight))) {
// trigger the 'scrollView' event
$element.trigger('scrollView', {
position: position,
});
// call the 'onView' function
if (options.onView !== null) {
options.onView(_this, position);
}
}
}
}
});
});
},
});
// Fields (Private)
// Defaults
// default options
var _defaults = {
// the offset to be applied to the left and top positions of the container
buffer: 0,
// the element to apply the 'scrolling' event to (default window)
container: window,
// the maximum value of the X or Y coordinate, depending on mode the selected
max: 0,
// the maximum value of the X or Y coordinate, depending on mode the selected
min: 0,
// whether to listen to the X (horizontal) or Y (vertical) scrolling
mode: 'vertical',
// namespace to append to the 'scroll' event
namespace: 'scrollspy',
// call the following callback function every time the user enters the min / max zone
onEnter: null,
// call the following callback function every time the user leaves the min / max zone
onLeave: null,
// call the following callback function every time the user leaves the top zone
onLeaveTop: null,
// call the following callback function every time the user leaves the bottom zone
onLeaveBottom: null,
// call the following callback function on each scroll event within the min and max parameters
onTick: null,
// call the following callback function on each scroll event when the element is inside the viewable view port
onView: null,
};
// Methods (Private)
// check if a value is an object datatype
function _isObject(value) {
return $.type(value) === 'object';
}
// check if a value is a string datatype with a length greater than zero when whitespace is stripped
function _isString(value) {
return $.type(value) === 'string' && $.trim(value).length > 0;
}
// check if an option is correctly formatted using a predicate; otherwise, return the default value
function _sanitizeOption(options, defaults, property, predicate) {
// set the property to the default value if the predicate returned false
if (!predicate(options[property])) {
options[property] = defaults[property];
}
}
}(window, window.jQuery));