You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
14 KiB
587 lines
14 KiB
10 years ago
|
// jquery.event.move
|
||
|
//
|
||
|
// 1.3.6
|
||
|
//
|
||
|
// Stephen Band
|
||
|
//
|
||
|
// Triggers 'movestart', 'move' and 'moveend' events after
|
||
|
// mousemoves following a mousedown cross a distance threshold,
|
||
|
// similar to the native 'dragstart', 'drag' and 'dragend' events.
|
||
|
// Move events are throttled to animation frames. Move event objects
|
||
|
// have the properties:
|
||
|
//
|
||
|
// pageX:
|
||
|
// pageY: Page coordinates of pointer.
|
||
|
// startX:
|
||
|
// startY: Page coordinates of pointer at movestart.
|
||
|
// distX:
|
||
|
// distY: Distance the pointer has moved since movestart.
|
||
|
// deltaX:
|
||
|
// deltaY: Distance the finger has moved since last event.
|
||
|
// velocityX:
|
||
|
// velocityY: Average velocity over last few events.
|
||
|
|
||
|
|
||
|
(function (module) {
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
// AMD. Register as an anonymous module.
|
||
|
define(['jquery'], module);
|
||
|
} else {
|
||
|
// Browser globals
|
||
|
module(jQuery);
|
||
|
}
|
||
|
})(function(jQuery, undefined){
|
||
|
|
||
|
var // Number of pixels a pressed pointer travels before movestart
|
||
|
// event is fired.
|
||
|
threshold = 6,
|
||
|
|
||
|
add = jQuery.event.add,
|
||
|
|
||
|
remove = jQuery.event.remove,
|
||
|
|
||
|
// Just sugar, so we can have arguments in the same order as
|
||
|
// add and remove.
|
||
|
trigger = function(node, type, data) {
|
||
|
jQuery.event.trigger(type, data, node);
|
||
|
},
|
||
|
|
||
|
// Shim for requestAnimationFrame, falling back to timer. See:
|
||
|
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||
|
requestFrame = (function(){
|
||
|
return (
|
||
|
window.requestAnimationFrame ||
|
||
|
window.webkitRequestAnimationFrame ||
|
||
|
window.mozRequestAnimationFrame ||
|
||
|
window.oRequestAnimationFrame ||
|
||
|
window.msRequestAnimationFrame ||
|
||
|
function(fn, element){
|
||
|
return window.setTimeout(function(){
|
||
|
fn();
|
||
|
}, 25);
|
||
|
}
|
||
|
);
|
||
|
})(),
|
||
|
|
||
|
ignoreTags = {
|
||
|
textarea: true,
|
||
|
input: true,
|
||
|
select: true,
|
||
|
button: true
|
||
|
},
|
||
|
|
||
|
mouseevents = {
|
||
|
move: 'mousemove',
|
||
|
cancel: 'mouseup dragstart',
|
||
|
end: 'mouseup'
|
||
|
},
|
||
|
|
||
|
touchevents = {
|
||
|
move: 'touchmove',
|
||
|
cancel: 'touchend',
|
||
|
end: 'touchend'
|
||
|
};
|
||
|
|
||
|
|
||
|
// Constructors
|
||
|
|
||
|
function Timer(fn){
|
||
|
var callback = fn,
|
||
|
active = false,
|
||
|
running = false;
|
||
|
|
||
|
function trigger(time) {
|
||
|
if (active){
|
||
|
callback();
|
||
|
requestFrame(trigger);
|
||
|
running = true;
|
||
|
active = false;
|
||
|
}
|
||
|
else {
|
||
|
running = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.kick = function(fn) {
|
||
|
active = true;
|
||
|
if (!running) { trigger(); }
|
||
|
};
|
||
|
|
||
|
this.end = function(fn) {
|
||
|
var cb = callback;
|
||
|
|
||
|
if (!fn) { return; }
|
||
|
|
||
|
// If the timer is not running, simply call the end callback.
|
||
|
if (!running) {
|
||
|
fn();
|
||
|
}
|
||
|
// If the timer is running, and has been kicked lately, then
|
||
|
// queue up the current callback and the end callback, otherwise
|
||
|
// just the end callback.
|
||
|
else {
|
||
|
callback = active ?
|
||
|
function(){ cb(); fn(); } :
|
||
|
fn ;
|
||
|
|
||
|
active = true;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
// Functions
|
||
|
|
||
|
function returnTrue() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function returnFalse() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function preventDefault(e) {
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
|
||
|
function preventIgnoreTags(e) {
|
||
|
// Don't prevent interaction with form elements.
|
||
|
if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
|
||
|
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
|
||
|
function isLeftButton(e) {
|
||
|
// Ignore mousedowns on any button other than the left (or primary)
|
||
|
// mouse button, or when a modifier key is pressed.
|
||
|
return (e.which === 1 && !e.ctrlKey && !e.altKey);
|
||
|
}
|
||
|
|
||
|
function identifiedTouch(touchList, id) {
|
||
|
var i, l;
|
||
|
|
||
|
if (touchList.identifiedTouch) {
|
||
|
return touchList.identifiedTouch(id);
|
||
|
}
|
||
|
|
||
|
// touchList.identifiedTouch() does not exist in
|
||
|
// webkit yet… we must do the search ourselves...
|
||
|
|
||
|
i = -1;
|
||
|
l = touchList.length;
|
||
|
|
||
|
while (++i < l) {
|
||
|
if (touchList[i].identifier === id) {
|
||
|
return touchList[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function changedTouch(e, event) {
|
||
|
var touch = identifiedTouch(e.changedTouches, event.identifier);
|
||
|
|
||
|
// This isn't the touch you're looking for.
|
||
|
if (!touch) { return; }
|
||
|
|
||
|
// Chrome Android (at least) includes touches that have not
|
||
|
// changed in e.changedTouches. That's a bit annoying. Check
|
||
|
// that this touch has changed.
|
||
|
if (touch.pageX === event.pageX && touch.pageY === event.pageY) { return; }
|
||
|
|
||
|
return touch;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Handlers that decide when the first movestart is triggered
|
||
|
|
||
|
function mousedown(e){
|
||
|
var data;
|
||
|
|
||
|
if (!isLeftButton(e)) { return; }
|
||
|
|
||
|
data = {
|
||
|
target: e.target,
|
||
|
startX: e.pageX,
|
||
|
startY: e.pageY,
|
||
|
timeStamp: e.timeStamp
|
||
|
};
|
||
|
|
||
|
add(document, mouseevents.move, mousemove, data);
|
||
|
add(document, mouseevents.cancel, mouseend, data);
|
||
|
}
|
||
|
|
||
|
function mousemove(e){
|
||
|
var data = e.data;
|
||
|
|
||
|
checkThreshold(e, data, e, removeMouse);
|
||
|
}
|
||
|
|
||
|
function mouseend(e) {
|
||
|
removeMouse();
|
||
|
}
|
||
|
|
||
|
function removeMouse() {
|
||
|
remove(document, mouseevents.move, mousemove);
|
||
|
remove(document, mouseevents.cancel, mouseend);
|
||
|
}
|
||
|
|
||
|
function touchstart(e) {
|
||
|
var touch, template;
|
||
|
|
||
|
// Don't get in the way of interaction with form elements.
|
||
|
if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
|
||
|
|
||
|
touch = e.changedTouches[0];
|
||
|
|
||
|
// iOS live updates the touch objects whereas Android gives us copies.
|
||
|
// That means we can't trust the touchstart object to stay the same,
|
||
|
// so we must copy the data. This object acts as a template for
|
||
|
// movestart, move and moveend event objects.
|
||
|
template = {
|
||
|
target: touch.target,
|
||
|
startX: touch.pageX,
|
||
|
startY: touch.pageY,
|
||
|
timeStamp: e.timeStamp,
|
||
|
identifier: touch.identifier
|
||
|
};
|
||
|
|
||
|
// Use the touch identifier as a namespace, so that we can later
|
||
|
// remove handlers pertaining only to this touch.
|
||
|
add(document, touchevents.move + '.' + touch.identifier, touchmove, template);
|
||
|
add(document, touchevents.cancel + '.' + touch.identifier, touchend, template);
|
||
|
}
|
||
|
|
||
|
function touchmove(e){
|
||
|
var data = e.data,
|
||
|
touch = changedTouch(e, data);
|
||
|
|
||
|
if (!touch) { return; }
|
||
|
|
||
|
checkThreshold(e, data, touch, removeTouch);
|
||
|
}
|
||
|
|
||
|
function touchend(e) {
|
||
|
var template = e.data,
|
||
|
touch = identifiedTouch(e.changedTouches, template.identifier);
|
||
|
|
||
|
if (!touch) { return; }
|
||
|
|
||
|
removeTouch(template.identifier);
|
||
|
}
|
||
|
|
||
|
function removeTouch(identifier) {
|
||
|
remove(document, '.' + identifier, touchmove);
|
||
|
remove(document, '.' + identifier, touchend);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Logic for deciding when to trigger a movestart.
|
||
|
|
||
|
function checkThreshold(e, template, touch, fn) {
|
||
|
var distX = touch.pageX - template.startX,
|
||
|
distY = touch.pageY - template.startY;
|
||
|
|
||
|
// Do nothing if the threshold has not been crossed.
|
||
|
if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
|
||
|
|
||
|
triggerStart(e, template, touch, distX, distY, fn);
|
||
|
}
|
||
|
|
||
|
function handled() {
|
||
|
// this._handled should return false once, and after return true.
|
||
|
this._handled = returnTrue;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function flagAsHandled(e) {
|
||
|
e._handled();
|
||
|
}
|
||
|
|
||
|
function triggerStart(e, template, touch, distX, distY, fn) {
|
||
|
var node = template.target,
|
||
|
touches, time;
|
||
|
|
||
|
touches = e.targetTouches;
|
||
|
time = e.timeStamp - template.timeStamp;
|
||
|
|
||
|
// Create a movestart object with some special properties that
|
||
|
// are passed only to the movestart handlers.
|
||
|
template.type = 'movestart';
|
||
|
template.distX = distX;
|
||
|
template.distY = distY;
|
||
|
template.deltaX = distX;
|
||
|
template.deltaY = distY;
|
||
|
template.pageX = touch.pageX;
|
||
|
template.pageY = touch.pageY;
|
||
|
template.velocityX = distX / time;
|
||
|
template.velocityY = distY / time;
|
||
|
template.targetTouches = touches;
|
||
|
template.finger = touches ?
|
||
|
touches.length :
|
||
|
1 ;
|
||
|
|
||
|
// The _handled method is fired to tell the default movestart
|
||
|
// handler that one of the move events is bound.
|
||
|
template._handled = handled;
|
||
|
|
||
|
// Pass the touchmove event so it can be prevented if or when
|
||
|
// movestart is handled.
|
||
|
template._preventTouchmoveDefault = function() {
|
||
|
e.preventDefault();
|
||
|
};
|
||
|
|
||
|
// Trigger the movestart event.
|
||
|
trigger(template.target, template);
|
||
|
|
||
|
// Unbind handlers that tracked the touch or mouse up till now.
|
||
|
fn(template.identifier);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Handlers that control what happens following a movestart
|
||
|
|
||
|
function activeMousemove(e) {
|
||
|
var timer = e.data.timer;
|
||
|
|
||
|
e.data.touch = e;
|
||
|
e.data.timeStamp = e.timeStamp;
|
||
|
timer.kick();
|
||
|
}
|
||
|
|
||
|
function activeMouseend(e) {
|
||
|
var event = e.data.event,
|
||
|
timer = e.data.timer;
|
||
|
|
||
|
removeActiveMouse();
|
||
|
|
||
|
endEvent(event, timer, function() {
|
||
|
// Unbind the click suppressor, waiting until after mouseup
|
||
|
// has been handled.
|
||
|
setTimeout(function(){
|
||
|
remove(event.target, 'click', returnFalse);
|
||
|
}, 0);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function removeActiveMouse(event) {
|
||
|
remove(document, mouseevents.move, activeMousemove);
|
||
|
remove(document, mouseevents.end, activeMouseend);
|
||
|
}
|
||
|
|
||
|
function activeTouchmove(e) {
|
||
|
var event = e.data.event,
|
||
|
timer = e.data.timer,
|
||
|
touch = changedTouch(e, event);
|
||
|
|
||
|
if (!touch) { return; }
|
||
|
|
||
|
// Stop the interface from gesturing
|
||
|
e.preventDefault();
|
||
|
|
||
|
event.targetTouches = e.targetTouches;
|
||
|
e.data.touch = touch;
|
||
|
e.data.timeStamp = e.timeStamp;
|
||
|
timer.kick();
|
||
|
}
|
||
|
|
||
|
function activeTouchend(e) {
|
||
|
var event = e.data.event,
|
||
|
timer = e.data.timer,
|
||
|
touch = identifiedTouch(e.changedTouches, event.identifier);
|
||
|
|
||
|
// This isn't the touch you're looking for.
|
||
|
if (!touch) { return; }
|
||
|
|
||
|
removeActiveTouch(event);
|
||
|
endEvent(event, timer);
|
||
|
}
|
||
|
|
||
|
function removeActiveTouch(event) {
|
||
|
remove(document, '.' + event.identifier, activeTouchmove);
|
||
|
remove(document, '.' + event.identifier, activeTouchend);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Logic for triggering move and moveend events
|
||
|
|
||
|
function updateEvent(event, touch, timeStamp, timer) {
|
||
|
var time = timeStamp - event.timeStamp;
|
||
|
|
||
|
event.type = 'move';
|
||
|
event.distX = touch.pageX - event.startX;
|
||
|
event.distY = touch.pageY - event.startY;
|
||
|
event.deltaX = touch.pageX - event.pageX;
|
||
|
event.deltaY = touch.pageY - event.pageY;
|
||
|
|
||
|
// Average the velocity of the last few events using a decay
|
||
|
// curve to even out spurious jumps in values.
|
||
|
event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
|
||
|
event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
|
||
|
event.pageX = touch.pageX;
|
||
|
event.pageY = touch.pageY;
|
||
|
}
|
||
|
|
||
|
function endEvent(event, timer, fn) {
|
||
|
timer.end(function(){
|
||
|
event.type = 'moveend';
|
||
|
|
||
|
trigger(event.target, event);
|
||
|
|
||
|
return fn && fn();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
// jQuery special event definition
|
||
|
|
||
|
function setup(data, namespaces, eventHandle) {
|
||
|
// Stop the node from being dragged
|
||
|
//add(this, 'dragstart.move drag.move', preventDefault);
|
||
|
|
||
|
// Prevent text selection and touch interface scrolling
|
||
|
//add(this, 'mousedown.move', preventIgnoreTags);
|
||
|
|
||
|
// Tell movestart default handler that we've handled this
|
||
|
add(this, 'movestart.move', flagAsHandled);
|
||
|
|
||
|
// Don't bind to the DOM. For speed.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function teardown(namespaces) {
|
||
|
remove(this, 'dragstart drag', preventDefault);
|
||
|
remove(this, 'mousedown touchstart', preventIgnoreTags);
|
||
|
remove(this, 'movestart', flagAsHandled);
|
||
|
|
||
|
// Don't bind to the DOM. For speed.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function addMethod(handleObj) {
|
||
|
// We're not interested in preventing defaults for handlers that
|
||
|
// come from internal move or moveend bindings
|
||
|
if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Stop the node from being dragged
|
||
|
add(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid, preventDefault, undefined, handleObj.selector);
|
||
|
|
||
|
// Prevent text selection and touch interface scrolling
|
||
|
add(this, 'mousedown.' + handleObj.guid, preventIgnoreTags, undefined, handleObj.selector);
|
||
|
}
|
||
|
|
||
|
function removeMethod(handleObj) {
|
||
|
if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
remove(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid);
|
||
|
remove(this, 'mousedown.' + handleObj.guid);
|
||
|
}
|
||
|
|
||
|
jQuery.event.special.movestart = {
|
||
|
setup: setup,
|
||
|
teardown: teardown,
|
||
|
add: addMethod,
|
||
|
remove: removeMethod,
|
||
|
|
||
|
_default: function(e) {
|
||
|
var event, data;
|
||
|
|
||
|
// If no move events were bound to any ancestors of this
|
||
|
// target, high tail it out of here.
|
||
|
if (!e._handled()) { return; }
|
||
|
|
||
|
function update(time) {
|
||
|
updateEvent(event, data.touch, data.timeStamp);
|
||
|
trigger(e.target, event);
|
||
|
}
|
||
|
|
||
|
event = {
|
||
|
target: e.target,
|
||
|
startX: e.startX,
|
||
|
startY: e.startY,
|
||
|
pageX: e.pageX,
|
||
|
pageY: e.pageY,
|
||
|
distX: e.distX,
|
||
|
distY: e.distY,
|
||
|
deltaX: e.deltaX,
|
||
|
deltaY: e.deltaY,
|
||
|
velocityX: e.velocityX,
|
||
|
velocityY: e.velocityY,
|
||
|
timeStamp: e.timeStamp,
|
||
|
identifier: e.identifier,
|
||
|
targetTouches: e.targetTouches,
|
||
|
finger: e.finger
|
||
|
};
|
||
|
|
||
|
data = {
|
||
|
event: event,
|
||
|
timer: new Timer(update),
|
||
|
touch: undefined,
|
||
|
timeStamp: undefined
|
||
|
};
|
||
|
|
||
|
if (e.identifier === undefined) {
|
||
|
// We're dealing with a mouse
|
||
|
// Stop clicks from propagating during a move
|
||
|
add(e.target, 'click', returnFalse);
|
||
|
add(document, mouseevents.move, activeMousemove, data);
|
||
|
add(document, mouseevents.end, activeMouseend, data);
|
||
|
}
|
||
|
else {
|
||
|
// We're dealing with a touch. Stop touchmove doing
|
||
|
// anything defaulty.
|
||
|
e._preventTouchmoveDefault();
|
||
|
add(document, touchevents.move + '.' + e.identifier, activeTouchmove, data);
|
||
|
add(document, touchevents.end + '.' + e.identifier, activeTouchend, data);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
jQuery.event.special.move = {
|
||
|
setup: function() {
|
||
|
// Bind a noop to movestart. Why? It's the movestart
|
||
|
// setup that decides whether other move events are fired.
|
||
|
add(this, 'movestart.move', jQuery.noop);
|
||
|
},
|
||
|
|
||
|
teardown: function() {
|
||
|
remove(this, 'movestart.move', jQuery.noop);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
jQuery.event.special.moveend = {
|
||
|
setup: function() {
|
||
|
// Bind a noop to movestart. Why? It's the movestart
|
||
|
// setup that decides whether other move events are fired.
|
||
|
add(this, 'movestart.moveend', jQuery.noop);
|
||
|
},
|
||
|
|
||
|
teardown: function() {
|
||
|
remove(this, 'movestart.moveend', jQuery.noop);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
add(document, 'mousedown.move', mousedown);
|
||
|
add(document, 'touchstart.move', touchstart);
|
||
|
|
||
|
// Make jQuery copy touch event properties over to the jQuery event
|
||
|
// object, if they are not already listed. But only do the ones we
|
||
|
// really need. IE7/8 do not have Array#indexOf(), but nor do they
|
||
|
// have touch events, so let's assume we can ignore them.
|
||
|
if (typeof Array.prototype.indexOf === 'function') {
|
||
|
(function(jQuery, undefined){
|
||
|
var props = ["changedTouches", "targetTouches"],
|
||
|
l = props.length;
|
||
|
|
||
|
while (l--) {
|
||
|
if (jQuery.event.props.indexOf(props[l]) === -1) {
|
||
|
jQuery.event.props.push(props[l]);
|
||
|
}
|
||
|
}
|
||
|
})(jQuery);
|
||
|
};
|
||
|
});
|