794 lines
21 KiB
JavaScript
794 lines
21 KiB
JavaScript
|
/**
|
||
|
* Overscroll v1.7.3
|
||
|
* A jQuery Plugin that emulates the iPhone scrolling experience in a browser.
|
||
|
* http://azoffdesign.com/overscroll
|
||
|
*
|
||
|
* Intended for use with the latest jQuery
|
||
|
* http://code.jquery.com/jquery-latest.js
|
||
|
*
|
||
|
* Copyright 2013, Jonathan Azoff
|
||
|
* Licensed under the MIT license.
|
||
|
* https://github.com/azoff/overscroll/blob/master/mit.license
|
||
|
*
|
||
|
* For API documentation, see the README file
|
||
|
* http://azof.fr/pYCzuM
|
||
|
*
|
||
|
* Date: Tuesday, March 18th 2013
|
||
|
*/
|
||
|
(function(global, dom, browser, math, wait, cancel, namespace, $, none){
|
||
|
|
||
|
// We want to run this plug-in in strict-mode
|
||
|
// so that we may benefit from its optimizations
|
||
|
'use strict';
|
||
|
|
||
|
// The key used to bind-instance specific data to an object
|
||
|
var datakey = 'overscroll';
|
||
|
|
||
|
// create <body> node if there's not one present (e.g., for test runners)
|
||
|
if (dom.body === null) {
|
||
|
dom.documentElement.appendChild(
|
||
|
dom.createElement('body')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// quick fix for IE 8 and below since getComputedStyle() is not supported
|
||
|
// TODO: find a better solution
|
||
|
if (!global.getComputedStyle) {
|
||
|
global.getComputedStyle = function (el, pseudo) {
|
||
|
this.el = el;
|
||
|
this.getPropertyValue = function (prop) {
|
||
|
var re = /(\-([a-z]){1})/g;
|
||
|
if (prop == 'float') prop = 'styleFloat';
|
||
|
if (re.test(prop)) {
|
||
|
prop = prop.replace(re, function () {
|
||
|
return arguments[2].toUpperCase();
|
||
|
});
|
||
|
}
|
||
|
return el.currentStyle[prop] ? el.currentStyle[prop] : null;
|
||
|
};
|
||
|
return this;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// runs feature detection for overscroll
|
||
|
var compat = {
|
||
|
animate: (function(){
|
||
|
var fn = global.requestAnimationFrame ||
|
||
|
global.webkitRequestAnimationFrame ||
|
||
|
global.mozRequestAnimationFrame ||
|
||
|
global.oRequestAnimationFrame ||
|
||
|
global.msRequestAnimationFrame ||
|
||
|
function(callback) { wait(callback, 1000/60); };
|
||
|
return function(callback) {
|
||
|
fn.call(global, callback);
|
||
|
};
|
||
|
})(),
|
||
|
overflowScrolling: (function(){
|
||
|
var style = '';
|
||
|
var div = dom.createElement('div');
|
||
|
var prefixes = ['webkit', 'moz', 'o', 'ms'];
|
||
|
dom.body.appendChild(div);
|
||
|
$.each(prefixes, function(i, prefix){
|
||
|
div.style[prefix + 'OverflowScrolling'] = 'touch';
|
||
|
});
|
||
|
div.style.overflowScrolling = 'touch';
|
||
|
var computedStyle = global.getComputedStyle(div);
|
||
|
if (!!computedStyle.overflowScrolling) {
|
||
|
style = 'overflow-scrolling';
|
||
|
} else {
|
||
|
$.each(prefixes, function(i, prefix){
|
||
|
if (!!computedStyle[prefix + 'OverflowScrolling']) {
|
||
|
style = '-' + prefix + '-overflow-scrolling';
|
||
|
}
|
||
|
return !style;
|
||
|
});
|
||
|
}
|
||
|
div.parentNode.removeChild(div);
|
||
|
return style;
|
||
|
})(),
|
||
|
cursor: (function() {
|
||
|
var div = dom.createElement('div');
|
||
|
var prefixes = ['webkit', 'moz'];
|
||
|
var gmail = 'https://mail.google.com/mail/images/2/';
|
||
|
var style = {
|
||
|
grab: 'url('+gmail+'openhand.cur), move',
|
||
|
grabbing: 'url('+gmail+'closedhand.cur), move'
|
||
|
};
|
||
|
dom.body.appendChild(div);
|
||
|
$.each(prefixes, function(i, prefix){
|
||
|
var found, cursor = '-' + prefix + '-grab';
|
||
|
div.style.cursor = cursor;
|
||
|
var computedStyle = global.getComputedStyle(div);
|
||
|
found = computedStyle.cursor === cursor;
|
||
|
if (found) {
|
||
|
style = {
|
||
|
grab: '-' + prefix + '-grab',
|
||
|
grabbing: '-' + prefix + '-grabbing'
|
||
|
};
|
||
|
}
|
||
|
return !found;
|
||
|
});
|
||
|
div.parentNode.removeChild(div);
|
||
|
return style;
|
||
|
})()
|
||
|
};
|
||
|
|
||
|
// These are all the events that could possibly
|
||
|
// be used by the plug-in
|
||
|
var events = {
|
||
|
drag: 'mousemove touchmove',
|
||
|
end: 'mouseup mouseleave click touchend touchcancel',
|
||
|
hover: 'mouseenter mouseleave',
|
||
|
ignored: 'select dragstart drag',
|
||
|
scroll: 'scroll',
|
||
|
start: 'mousedown touchstart',
|
||
|
wheel: 'mousewheel DOMMouseScroll'
|
||
|
};
|
||
|
|
||
|
// These settings are used to tweak drift settings
|
||
|
// for the plug-in
|
||
|
var settings = {
|
||
|
captureThreshold: 3,
|
||
|
driftDecay: 1.1,
|
||
|
driftSequences: 22,
|
||
|
driftTimeout: 100,
|
||
|
scrollDelta: 15,
|
||
|
thumbOpacity: 0.7,
|
||
|
thumbThickness: 6,
|
||
|
thumbTimeout: 400,
|
||
|
wheelDelta: 20,
|
||
|
wheelTicks: 120
|
||
|
};
|
||
|
|
||
|
// These defaults are used to complement any options
|
||
|
// passed into the plug-in entry point
|
||
|
var defaults = {
|
||
|
cancelOn: 'select,input,textarea',
|
||
|
direction: 'multi',
|
||
|
dragHold: false,
|
||
|
hoverThumbs: false,
|
||
|
scrollDelta: settings.scrollDelta,
|
||
|
showThumbs: true,
|
||
|
persistThumbs: false,
|
||
|
captureWheel: true,
|
||
|
wheelDelta: settings.wheelDelta,
|
||
|
wheelDirection: 'multi',
|
||
|
zIndex: 999,
|
||
|
ignoreSizing: false
|
||
|
};
|
||
|
|
||
|
// Triggers a DOM event on the overscrolled element.
|
||
|
// All events are namespaced under the overscroll name
|
||
|
function triggerEvent(event, target) {
|
||
|
target.trigger('overscroll:' + event);
|
||
|
}
|
||
|
|
||
|
// Utility function to return a timestamp
|
||
|
function time() {
|
||
|
return (new Date()).getTime();
|
||
|
}
|
||
|
|
||
|
// Captures the position from an event, modifies the properties
|
||
|
// of the second argument to persist the position, and then
|
||
|
// returns the modified object
|
||
|
function capturePosition(event, position, index) {
|
||
|
position.x = event.pageX;
|
||
|
position.y = event.pageY;
|
||
|
position.time = time();
|
||
|
position.index = index;
|
||
|
return position;
|
||
|
}
|
||
|
|
||
|
// Used to move the thumbs around an overscrolled element
|
||
|
function moveThumbs(thumbs, sizing, left, top) {
|
||
|
|
||
|
var ml, mt;
|
||
|
|
||
|
if (thumbs && thumbs.added) {
|
||
|
if (thumbs.horizontal) {
|
||
|
ml = left * (1 + sizing.container.width / sizing.container.scrollWidth);
|
||
|
mt = top + sizing.thumbs.horizontal.top;
|
||
|
thumbs.horizontal.css('margin', mt + 'px 0 0 ' + ml + 'px');
|
||
|
}
|
||
|
if (thumbs.vertical) {
|
||
|
ml = left + sizing.thumbs.vertical.left;
|
||
|
mt = top * (1 + sizing.container.height / sizing.container.scrollHeight);
|
||
|
thumbs.vertical.css('margin', mt + 'px 0 0 ' + ml + 'px');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Used to toggle the thumbs on and off
|
||
|
// of an overscrolled element
|
||
|
function toggleThumbs(thumbs, options, dragging) {
|
||
|
if (thumbs && thumbs.added && !options.persistThumbs) {
|
||
|
if (dragging) {
|
||
|
if (thumbs.vertical) {
|
||
|
thumbs.vertical.stop(true, true).fadeTo('fast', settings.thumbOpacity);
|
||
|
}
|
||
|
if (thumbs.horizontal) {
|
||
|
thumbs.horizontal.stop(true, true).fadeTo('fast', settings.thumbOpacity);
|
||
|
}
|
||
|
} else {
|
||
|
if (thumbs.vertical) {
|
||
|
thumbs.vertical.fadeTo('fast', 0);
|
||
|
}
|
||
|
if (thumbs.horizontal) {
|
||
|
thumbs.horizontal.fadeTo('fast', 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Defers click event listeners to after a mouseup event.
|
||
|
// Used to avoid unintentional clicks
|
||
|
function deferClick(target) {
|
||
|
var clicks, key = 'events';
|
||
|
var events = $._data ? $._data(target[0], key) : target.data(key);
|
||
|
if (events && events.click) {
|
||
|
clicks = events.click.slice();
|
||
|
target.off('click').one('click', function(){
|
||
|
$.each(clicks, function(i, click){
|
||
|
target.click(click);
|
||
|
}); return false;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Toggles thumbs on hover. This event is only triggered
|
||
|
// if the hoverThumbs option is set
|
||
|
function hover(event) {
|
||
|
var data = event.data,
|
||
|
thumbs = data.thumbs,
|
||
|
options = data.options,
|
||
|
dragging = event.type === 'mouseenter';
|
||
|
toggleThumbs(thumbs, options, dragging);
|
||
|
}
|
||
|
|
||
|
// This function is only ever used when the overscrolled element
|
||
|
// scrolled outside of the scope of this plugin.
|
||
|
function scroll(event) {
|
||
|
var data = event.data;
|
||
|
if (!data.flags.dragged) {
|
||
|
/*jshint validthis:true */
|
||
|
moveThumbs(data.thumbs, data.sizing, this.scrollLeft, this.scrollTop);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// handles mouse wheel scroll events
|
||
|
function wheel(event) {
|
||
|
|
||
|
// prevent any default wheel behavior
|
||
|
event.preventDefault();
|
||
|
|
||
|
var data = event.data,
|
||
|
options = data.options,
|
||
|
sizing = data.sizing,
|
||
|
thumbs = data.thumbs,
|
||
|
dwheel = data.wheel,
|
||
|
flags = data.flags,
|
||
|
original = event.originalEvent,
|
||
|
delta = 0, deltaX = 0, deltaY = 0;
|
||
|
|
||
|
// stop any drifts
|
||
|
flags.drifting = false;
|
||
|
|
||
|
// normalize the wheel ticks
|
||
|
if (original.detail) {
|
||
|
delta = -original.detail;
|
||
|
if (original.detailX) {
|
||
|
deltaX = -original.detailX;
|
||
|
}
|
||
|
if (original.detailY) {
|
||
|
deltaY = -original.detailY;
|
||
|
}
|
||
|
} else if (original.wheelDelta) {
|
||
|
delta = original.wheelDelta / settings.wheelTicks;
|
||
|
if (original.wheelDeltaX) {
|
||
|
deltaX = original.wheelDeltaX / settings.wheelTicks;
|
||
|
}
|
||
|
if (original.wheelDeltaY) {
|
||
|
deltaY = original.wheelDeltaY / settings.wheelTicks;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// apply a pixel delta to each tick
|
||
|
delta *= options.wheelDelta;
|
||
|
deltaX *= options.wheelDelta;
|
||
|
deltaY *= options.wheelDelta;
|
||
|
|
||
|
// initialize flags if this is the first tick
|
||
|
if (!dwheel) {
|
||
|
data.target.data(datakey).dragging = flags.dragging = true;
|
||
|
data.wheel = dwheel = { timeout: null };
|
||
|
toggleThumbs(thumbs, options, true);
|
||
|
}
|
||
|
|
||
|
// actually modify scroll offsets
|
||
|
if (options.wheelDirection === 'vertical'){
|
||
|
/*jshint validthis:true */
|
||
|
this.scrollTop -= delta;
|
||
|
} else if ( options.wheelDirection === 'horizontal') {
|
||
|
this.scrollLeft -= delta;
|
||
|
} else {
|
||
|
this.scrollLeft -= deltaX;
|
||
|
this.scrollTop -= deltaY || delta;
|
||
|
}
|
||
|
|
||
|
if (dwheel.timeout) { cancel(dwheel.timeout); }
|
||
|
|
||
|
moveThumbs(thumbs, sizing, this.scrollLeft, this.scrollTop);
|
||
|
|
||
|
dwheel.timeout = wait(function() {
|
||
|
data.target.data(datakey).dragging = flags.dragging = false;
|
||
|
toggleThumbs(thumbs, options, data.wheel = null);
|
||
|
}, settings.thumbTimeout);
|
||
|
|
||
|
}
|
||
|
|
||
|
// updates the current scroll offset during a mouse move
|
||
|
function drag(event) {
|
||
|
|
||
|
event.preventDefault();
|
||
|
|
||
|
var data = event.data,
|
||
|
touches = event.originalEvent.touches,
|
||
|
options = data.options,
|
||
|
sizing = data.sizing,
|
||
|
thumbs = data.thumbs,
|
||
|
position = data.position,
|
||
|
flags = data.flags,
|
||
|
target = data.target.get(0);
|
||
|
|
||
|
|
||
|
// correct page coordinates for touch devices
|
||
|
if (touches && touches.length) {
|
||
|
event = touches[0];
|
||
|
}
|
||
|
|
||
|
if (!flags.dragged) {
|
||
|
toggleThumbs(thumbs, options, true);
|
||
|
}
|
||
|
|
||
|
flags.dragged = true;
|
||
|
|
||
|
if (options.direction !== 'vertical') {
|
||
|
target.scrollLeft -= (event.pageX - position.x);
|
||
|
}
|
||
|
|
||
|
if (data.options.direction !== 'horizontal') {
|
||
|
target.scrollTop -= (event.pageY - position.y);
|
||
|
}
|
||
|
|
||
|
capturePosition(event, data.position);
|
||
|
|
||
|
if (--data.capture.index <= 0) {
|
||
|
data.target.data(datakey).dragging = flags.dragging = true;
|
||
|
capturePosition(event, data.capture, settings.captureThreshold);
|
||
|
}
|
||
|
|
||
|
moveThumbs(thumbs, sizing, target.scrollLeft, target.scrollTop);
|
||
|
|
||
|
}
|
||
|
|
||
|
// sends the overscrolled element into a drift
|
||
|
function drift(target, event, callback) {
|
||
|
|
||
|
var data = event.data, dx, dy, xMod, yMod,
|
||
|
capture = data.capture,
|
||
|
options = data.options,
|
||
|
sizing = data.sizing,
|
||
|
thumbs = data.thumbs,
|
||
|
elapsed = time() - capture.time,
|
||
|
scrollLeft = target.scrollLeft,
|
||
|
scrollTop = target.scrollTop,
|
||
|
decay = settings.driftDecay;
|
||
|
|
||
|
// only drift if enough time has passed since
|
||
|
// the last capture event
|
||
|
if (elapsed > settings.driftTimeout) {
|
||
|
callback(data); return;
|
||
|
}
|
||
|
|
||
|
// determine offset between last capture and current time
|
||
|
dx = options.scrollDelta * (event.pageX - capture.x);
|
||
|
dy = options.scrollDelta * (event.pageY - capture.y);
|
||
|
|
||
|
// update target scroll offsets
|
||
|
if (options.direction !== 'vertical') {
|
||
|
scrollLeft -= dx;
|
||
|
} if (options.direction !== 'horizontal') {
|
||
|
scrollTop -= dy;
|
||
|
}
|
||
|
|
||
|
// split the distance to travel into a set of sequences
|
||
|
xMod = dx / settings.driftSequences;
|
||
|
yMod = dy / settings.driftSequences;
|
||
|
|
||
|
triggerEvent('driftstart', data.target);
|
||
|
|
||
|
data.drifting = true;
|
||
|
|
||
|
// animate the drift sequence
|
||
|
compat.animate(function render() {
|
||
|
if (data.drifting) {
|
||
|
var min = 1, max = -1;
|
||
|
data.drifting = false;
|
||
|
if (yMod > min && target.scrollTop > scrollTop || yMod < max && target.scrollTop < scrollTop) {
|
||
|
data.drifting = true;
|
||
|
target.scrollTop -= yMod;
|
||
|
yMod /= decay;
|
||
|
}
|
||
|
if (xMod > min && target.scrollLeft > scrollLeft || xMod < max && target.scrollLeft < scrollLeft) {
|
||
|
data.drifting = true;
|
||
|
target.scrollLeft -= xMod;
|
||
|
xMod /= decay;
|
||
|
}
|
||
|
moveThumbs(thumbs, sizing, target.scrollLeft, target.scrollTop);
|
||
|
compat.animate(render);
|
||
|
} else {
|
||
|
triggerEvent('driftend', data.target);
|
||
|
callback(data);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
// starts the drag operation and binds the mouse move handler
|
||
|
function start(event) {
|
||
|
|
||
|
var data = event.data,
|
||
|
touches = event.originalEvent.touches,
|
||
|
target = data.target,
|
||
|
dstart = data.start = $(event.target),
|
||
|
flags = data.flags;
|
||
|
|
||
|
// stop any drifts
|
||
|
flags.drifting = false;
|
||
|
|
||
|
// only start drag if the user has not explictly banned it.
|
||
|
if (dstart.size() && !dstart.is(data.options.cancelOn)) {
|
||
|
|
||
|
// without this the simple "click" event won't be recognized on touch clients
|
||
|
if (!touches) { event.preventDefault(); }
|
||
|
|
||
|
if (!compat.overflowScrolling) {
|
||
|
target.css('cursor', compat.cursor.grabbing);
|
||
|
target.data(datakey).dragging = flags.dragging = flags.dragged = false;
|
||
|
|
||
|
// apply the drag listeners to the doc or target
|
||
|
if(data.options.dragHold) {
|
||
|
$(document).on(events.drag, data, drag);
|
||
|
} else {
|
||
|
target.on(events.drag, data, drag);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
data.position = capturePosition(event, {});
|
||
|
data.capture = capturePosition(event, {}, settings.captureThreshold);
|
||
|
triggerEvent('dragstart', target);
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// ends the drag operation and unbinds the mouse move handler
|
||
|
function stop(event) {
|
||
|
|
||
|
var data = event.data,
|
||
|
target = data.target,
|
||
|
options = data.options,
|
||
|
flags = data.flags,
|
||
|
thumbs = data.thumbs,
|
||
|
|
||
|
// hides the thumbs after the animation is done
|
||
|
done = function () {
|
||
|
if (thumbs && !options.hoverThumbs) {
|
||
|
toggleThumbs(thumbs, options, false);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// remove drag listeners from doc or target
|
||
|
if(options.dragHold) {
|
||
|
$(document).unbind(events.drag, drag);
|
||
|
} else {
|
||
|
target.unbind(events.drag, drag);
|
||
|
}
|
||
|
|
||
|
// only fire events and drift if we started with a
|
||
|
// valid position
|
||
|
if (data.position) {
|
||
|
|
||
|
triggerEvent('dragend', target);
|
||
|
|
||
|
// only drift if a drag passed our threshold
|
||
|
if (flags.dragging && !compat.overflowScrolling) {
|
||
|
drift(target.get(0), event, done);
|
||
|
} else {
|
||
|
done();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// only if we moved, and the mouse down is the same as
|
||
|
// the mouse up target do we defer the event
|
||
|
if (flags.dragging && !compat.overflowScrolling && data.start && data.start.is(event.target)) {
|
||
|
deferClick(data.start);
|
||
|
}
|
||
|
|
||
|
// clear all internal flags and settings
|
||
|
target.data(datakey).dragging =
|
||
|
data.start =
|
||
|
data.capture =
|
||
|
data.position =
|
||
|
flags.dragged =
|
||
|
flags.dragging = false;
|
||
|
|
||
|
// set the cursor back to normal
|
||
|
target.css('cursor', compat.cursor.grab);
|
||
|
|
||
|
}
|
||
|
|
||
|
// Ensures that a full set of options are provided
|
||
|
// for the plug-in. Also does some validation
|
||
|
function getOptions(options) {
|
||
|
|
||
|
// fill in missing values with defaults
|
||
|
options = $.extend({}, defaults, options);
|
||
|
|
||
|
// check for inconsistent directional restrictions
|
||
|
if (options.direction !== 'multi' && options.direction !== options.wheelDirection) {
|
||
|
options.wheelDirection = options.direction;
|
||
|
}
|
||
|
|
||
|
// ensure positive values for deltas
|
||
|
options.scrollDelta = math.abs(parseFloat(options.scrollDelta));
|
||
|
options.wheelDelta = math.abs(parseFloat(options.wheelDelta));
|
||
|
|
||
|
// fix values for scroll offset
|
||
|
options.scrollLeft = options.scrollLeft === none ? null : math.abs(parseFloat(options.scrollLeft));
|
||
|
options.scrollTop = options.scrollTop === none ? null : math.abs(parseFloat(options.scrollTop));
|
||
|
|
||
|
return options;
|
||
|
|
||
|
}
|
||
|
|
||
|
// Returns the sizing information (bounding box) for the
|
||
|
// target DOM element
|
||
|
function getSizing(target) {
|
||
|
|
||
|
var $target = $(target),
|
||
|
width = $target.width(),
|
||
|
height = $target.height(),
|
||
|
scrollWidth = width >= target.scrollWidth ? width : target.scrollWidth,
|
||
|
scrollHeight = height >= target.scrollHeight ? height : target.scrollHeight,
|
||
|
hasScroll = scrollWidth > width || scrollHeight > height;
|
||
|
|
||
|
return {
|
||
|
valid: hasScroll,
|
||
|
container: {
|
||
|
width: width,
|
||
|
height: height,
|
||
|
scrollWidth: scrollWidth,
|
||
|
scrollHeight: scrollHeight
|
||
|
},
|
||
|
thumbs: {
|
||
|
horizontal: {
|
||
|
width: width * width / scrollWidth,
|
||
|
height: settings.thumbThickness,
|
||
|
corner: settings.thumbThickness / 2,
|
||
|
left: 0,
|
||
|
top: height - settings.thumbThickness
|
||
|
},
|
||
|
vertical: {
|
||
|
width: settings.thumbThickness,
|
||
|
height: height * height / scrollHeight,
|
||
|
corner: settings.thumbThickness / 2,
|
||
|
left: width - settings.thumbThickness,
|
||
|
top: 0
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
}
|
||
|
|
||
|
// Attempts to get (or implicitly creates) the
|
||
|
// remover function for the target passed
|
||
|
// in as an argument
|
||
|
function getRemover(target, orCreate) {
|
||
|
|
||
|
var $target = $(target), thumbs,
|
||
|
data = $target.data(datakey) || {},
|
||
|
style = $target.attr('style'),
|
||
|
fallback = orCreate ? function () {
|
||
|
|
||
|
data = $target.data(datakey);
|
||
|
thumbs = data.thumbs;
|
||
|
|
||
|
// restore original styles (if any)
|
||
|
if (style) {
|
||
|
$target.attr('style', style);
|
||
|
} else {
|
||
|
$target.removeAttr('style');
|
||
|
}
|
||
|
|
||
|
// remove any created thumbs
|
||
|
if (thumbs) {
|
||
|
if (thumbs.horizontal) { thumbs.horizontal.remove(); }
|
||
|
if (thumbs.vertical) { thumbs.vertical.remove(); }
|
||
|
}
|
||
|
|
||
|
// remove any bound overscroll events and data
|
||
|
$target
|
||
|
.removeData(datakey)
|
||
|
.off(events.wheel, wheel)
|
||
|
.off(events.start, start)
|
||
|
.off(events.end, stop)
|
||
|
.off(events.ignored, ignore);
|
||
|
|
||
|
} : $.noop;
|
||
|
|
||
|
return $.isFunction(data.remover) ? data.remover : fallback;
|
||
|
|
||
|
}
|
||
|
|
||
|
// Genterates CSS specific to a particular thumb.
|
||
|
// It requires sizing data and options
|
||
|
function getThumbCss(size, options) {
|
||
|
return {
|
||
|
position: 'absolute',
|
||
|
opacity: options.persistThumbs ? settings.thumbOpacity : 0,
|
||
|
'background-color': 'black',
|
||
|
width: size.width + 'px',
|
||
|
height: size.height + 'px',
|
||
|
'border-radius': size.corner + 'px',
|
||
|
'margin': size.top + 'px 0 0 ' + size.left + 'px',
|
||
|
'z-index': options.zIndex
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Creates the DOM elements used as "thumbs" within
|
||
|
// the target container.
|
||
|
function createThumbs(target, sizing, options) {
|
||
|
|
||
|
var div = '<div/>',
|
||
|
thumbs = {},
|
||
|
css = false;
|
||
|
|
||
|
if (sizing.container.scrollWidth > 0 && options.direction !== 'vertical') {
|
||
|
css = getThumbCss(sizing.thumbs.horizontal, options);
|
||
|
thumbs.horizontal = $(div).css(css).prependTo(target);
|
||
|
}
|
||
|
|
||
|
if (sizing.container.scrollHeight > 0 && options.direction !== 'horizontal') {
|
||
|
css = getThumbCss(sizing.thumbs.vertical, options);
|
||
|
thumbs.vertical = $(div).css(css).prependTo(target);
|
||
|
}
|
||
|
|
||
|
thumbs.added = !!css;
|
||
|
|
||
|
return thumbs;
|
||
|
|
||
|
}
|
||
|
|
||
|
// ignores events on the overscroll element
|
||
|
function ignore(event) {
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
|
||
|
// This function takes a jQuery element, some
|
||
|
// (optional) options, and sets up event metadata
|
||
|
// for each instance the plug-in affects
|
||
|
function setup(target, options) {
|
||
|
|
||
|
// create initial data properties for this instance
|
||
|
options = getOptions(options);
|
||
|
var sizing = getSizing(target),
|
||
|
thumbs, data = {
|
||
|
options: options, sizing: sizing,
|
||
|
flags: { dragging: false },
|
||
|
remover: getRemover(target, true)
|
||
|
};
|
||
|
|
||
|
// only apply handlers if the overscrolled element
|
||
|
// actually has an area to scroll
|
||
|
if (sizing.valid || options.ignoreSizing) {
|
||
|
// provide a circular-reference, enable events, and
|
||
|
// apply any required CSS
|
||
|
data.target = target = $(target).css({
|
||
|
position: 'relative',
|
||
|
cursor: compat.cursor.grab
|
||
|
}).on(events.start, data, start)
|
||
|
.on(events.end, data, stop)
|
||
|
.on(events.ignored, data, ignore);
|
||
|
|
||
|
// apply the stop listeners for drag end
|
||
|
if(options.dragHold) {
|
||
|
$(document).on(events.end, data, stop);
|
||
|
} else {
|
||
|
data.target.on(events.end, data, stop);
|
||
|
}
|
||
|
|
||
|
// apply any user-provided scroll offsets
|
||
|
if (options.scrollLeft !== null) {
|
||
|
target.scrollLeft(options.scrollLeft);
|
||
|
} if (options.scrollTop !== null) {
|
||
|
target.scrollTop(options.scrollTop);
|
||
|
}
|
||
|
|
||
|
// use native oversroll, if it exists
|
||
|
if (compat.overflowScrolling) {
|
||
|
target.css(compat.overflowScrolling, 'touch');
|
||
|
} else {
|
||
|
target.on(events.scroll, data, scroll);
|
||
|
}
|
||
|
|
||
|
// check to see if the user would like mousewheel support
|
||
|
if (options.captureWheel) {
|
||
|
target.on(events.wheel, data, wheel);
|
||
|
}
|
||
|
|
||
|
// add thumbs and listeners (if we're showing them)
|
||
|
if (options.showThumbs) {
|
||
|
if (compat.overflowScrolling) {
|
||
|
target.css('overflow', 'scroll');
|
||
|
} else {
|
||
|
target.css('overflow', 'hidden');
|
||
|
data.thumbs = thumbs = createThumbs(target, sizing, options);
|
||
|
if (thumbs.added) {
|
||
|
moveThumbs(thumbs, sizing, target.scrollLeft(), target.scrollTop());
|
||
|
if (options.hoverThumbs) {
|
||
|
target.on(events.hover, data, hover);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
target.css('overflow', 'hidden');
|
||
|
}
|
||
|
|
||
|
target.data(datakey, data);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Removes any event listeners and other instance-specific
|
||
|
// data from the target. It attempts to leave the target
|
||
|
// at the state it found it.
|
||
|
function teardown(target) {
|
||
|
getRemover(target)();
|
||
|
}
|
||
|
|
||
|
// This is the entry-point for enabling the plug-in;
|
||
|
// You can find it's exposure point at the end
|
||
|
// of this closure
|
||
|
function overscroll(options) {
|
||
|
/*jshint validthis:true */
|
||
|
return this.removeOverscroll().each(function() {
|
||
|
setup(this, options);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// This is the entry-point for disabling the plug-in;
|
||
|
// You can find it's exposure point at the end
|
||
|
// of this closure
|
||
|
function removeOverscroll() {
|
||
|
/*jshint validthis:true */
|
||
|
return this.each(function () {
|
||
|
teardown(this);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Extend overscroll to expose settings to the user
|
||
|
overscroll.settings = settings;
|
||
|
|
||
|
// Extend jQuery's prototype to expose the plug-in.
|
||
|
// If the supports native overflowScrolling, overscroll will not
|
||
|
// attempt to override the browser's built in support
|
||
|
$.extend(namespace, {
|
||
|
overscroll: overscroll,
|
||
|
removeOverscroll: removeOverscroll
|
||
|
});
|
||
|
|
||
|
})(window, document, navigator, Math, setTimeout, clearTimeout, jQuery.fn, jQuery);
|