Skip to content

Commit

Permalink
Fixes bug where scroll was prevented in touch devices. Bumps to 1.3.
Browse files Browse the repository at this point in the history
In the process, gets rid of the dependency on using jQuery.data to count setups, instead binding a flag setting handler to movestart whenever any move events are bound, which tells the default movestart handler to prevent the default of touchmove. Man, touch events are tricky.

Also, movestart no longer passes extra data arguments that were only for internal use to handlers. This was a potential bug, although no-one has bumped into it yet. Or they didn't report it if they did. This also that movestart events should be able to be triggered programmatically - although that would require adding a bunch of properties to the event (startX, startY, etc), as subsequent events rely on the initial properties.
  • Loading branch information
stephband committed Sep 19, 2012
1 parent 5d80602 commit b4582c1
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 94 deletions.
14 changes: 8 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@

.resize, .rotate {
position: absolute;
width: 20px;
height: 20px;
width: 32px;
height: 32px;
}

.resize {
Expand Down Expand Up @@ -172,9 +172,9 @@ <h2 id="how">How to use move events</h2>
// move is complete!
});{% endhighlight %}

<h2 id="why1">Why not just use mouse or touch events?</h2>
<h2 id="why1">Why not just use raw mouse or touch events?</h2>

<p>Well, yes, you could. But jquery.event.move abstracts away the details that need attention when writing this kind of interaction model with raw mouse or touch events:</p>
<p>Well, yes, you could. But jquery.event.move abstracts away the details that need attention when writing this kind of interaction model with mouse and touch events:</p>

<ul>
<li>Supports mouse and touch devices, exposing the same event API for both</li>
Expand All @@ -195,7 +195,7 @@ <h2 id="why1">Why not just use mouse or touch events?</h2>

<h2 id="why2">What about drag events?</h2>

<p>Move events are designed to compliment drag events, where the two have different meanings: drag events are for transferring data while move events are for making responsive interfaces.
<p>Move events are designed to compliment drag events, where the two have different meanings: drag events are for transferring data while move events are for making interactive interfaces.
That said, <code>movestart</code>, <code>move</code> and <code>moveend</code> events deliberately echo <code>dragstart</code>, <code>drag</code> and <code>dragend</code> events, with one significant difference:
where the <code>drag</code> event fires continuously whether you have moved the pointer or not, the <code>move</code> event fires only after the pointer has been moved.</p>

Expand All @@ -204,12 +204,14 @@ <h2 id="why2">What about drag events?</h2>

<h2 id="where">Where is jquery.event.move being used?</h2>

<p>It's part of the <a href="stephband.info/template">template</a> project. It's used for:</p>
<p>It's part of my front-end toolkit, <a href="http://stephband.info/bolt">Bolt</a>. It's also used for:</p>

<ul>
<li><a href="http://www.webdoc.com">webdoc.com</a></li>
<li><a href="http://stephband.info/jquery.event.swipe">jquery.event.swipe</a></li>
</ul>

<p>Do let me know at <a href="http://twitter.com/stephband">@stephband</a> if you use it in your project. Cheers!</p>
</div>

<footer class="site_footer">
Expand Down
224 changes: 137 additions & 87 deletions js/jquery.event.move.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// jquery.event.move
//
// 1.2
// 1.3
//
// Stephen Band
//
Expand Down Expand Up @@ -131,7 +131,11 @@

// Functions

function returnFalse(e) {
function returnTrue() {
return true;
}

function returnFalse() {
return false;
}

Expand Down Expand Up @@ -198,8 +202,6 @@
target: e.target,
startX: e.pageX,
startY: e.pageY,
pageX: e.pageX,
pageY: e.pageY,
timeStamp: e.timeStamp
};

Expand All @@ -223,7 +225,7 @@
}

function touchstart(e) {
var touch, data;
var touch, template;

// Don't get in the way of interaction with form elements.
if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
Expand All @@ -232,22 +234,20 @@

// 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 let's copy the data. This object will act as a template for
// movestart, move and moveend events.
data = {
// 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,
pageX: touch.pageX,
pageY: 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, data);
add(document, touchevents.cancel + '.' + touch.identifier, touchend, data);
add(document, touchevents.move + '.' + touch.identifier, touchmove, template);
add(document, touchevents.cancel + '.' + touch.identifier, touchend, template);
}

function touchmove(e){
Expand All @@ -260,58 +260,80 @@
}

function touchend(e) {
var data = e.data,
touch = identifiedTouch(e.changedTouches, data.identifier);
var template = e.data,
touch = identifiedTouch(e.changedTouches, template.identifier);

if (!touch) { return; }

removeTouch(data);
removeTouch(template.identifier);
}

function removeTouch(touchstart) {
remove(document, '.' + touchstart.identifier, touchmove);
remove(document, '.' + touchstart.identifier, touchend);
function removeTouch(identifier) {
remove(document, '.' + identifier, touchmove);
remove(document, '.' + identifier, touchend);
}


// Logic for deciding when to trigger a movestart.

function checkThreshold(e, data, touch, fn) {
var distX = touch.pageX - data.startX,
distY = touch.pageY - data.startY;
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; }

return triggerStart(e, data, touch, distX, distY, fn);
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, data, touch, distX, distY, fn) {
var node = data.target,
events, touches, time;
function triggerStart(e, template, touch, distX, distY, fn) {
var node = template.target,
touches, time;

touches = e.targetTouches;
time = e.timeStamp - data.timeStamp;

data.type = 'movestart';
data.distX = distX;
data.distY = distY;
data.deltaX = distX;
data.deltaY = distY;
data.pageX = touch.pageX;
data.pageY = touch.pageY;
data.velocityX = distX / time;
data.velocityY = distY / time;
data.targetTouches = touches;
data.finger = touches ? touches.length : 1;

// Trigger the movestart event using data as a template, and pass
// a clean copy of data for use as a template for the move and
// moveend events. Also, pass the touchmove event so it can be
// prevented when movestart is fired.
trigger(data.target, data, [data, e]);

return fn(data);
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);
}


Expand Down Expand Up @@ -386,8 +408,9 @@
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 over a decay curve
// to even out spurious jumps in values.

// 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;
Expand All @@ -408,42 +431,26 @@


// jQuery special event definition

function getData(node) {
var data = jQuery.data(node, 'movexxx');

if (!data) {
data = { count: 0 };
jQuery.data(node, 'movexxx', data);
}

return data;
}

function setup(data, namespaces, eventHandle) {
var data = getData(this);

// If another move event is already setup, don't setup again.
if (data.count++ > 0) { return; }

// 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) {
var data = getData(this);

// If another move event is still setup, don't teardown.
if (--data.count > 0) { return; }

remove(this, 'dragstart drag', preventDefault);
remove(this, 'mousedown touchstart', preventIgnoreTags);

remove(this, 'movestart', flagAsHandled);

// Don't bind to the DOM. For speed.
return true;
}
Expand All @@ -452,34 +459,77 @@
setup: setup,
teardown: teardown,

_default: function(e, event, touchmove) {
var data = {
event: event,
timer: new Timer(function(time){
trigger(e.target, event);
})
};

if (event.identifier === undefined) {
_default: function(e) {
var template, data;

// If no move events were bound to any ancestors of this
// target, high tail it out of here.
if (!e._handled()) { return; }

template = {
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: template,
timer: new Timer(function(time){
trigger(e.target, template);
})
};

if (e.identifier === undefined) {
// We're dealing with a mouse
// Stop clicks from propagating during a move
add(event.target, 'click', returnFalse);
add(e.target, 'click', returnFalse);
add(document, mouseevents.move, activeMousemove, data);
add(document, mouseevents.end, activeMouseend, data);
}
else {
touchmove.preventDefault();

add(document, touchevents.move + '.' + event.identifier, activeTouchmove, data);
add(document, touchevents.end + '.' + event.identifier, activeTouchend, data);
// 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 =
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: setup,
teardown: teardown
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);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jquery.event.move",
"title": "jquery.event.move",
"version": "1.2",
"version": "1.3",
"author": {
"name": "Stephen Band",
"url": "http://stephband.info",
Expand Down

0 comments on commit b4582c1

Please sign in to comment.