Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit f4c6b2c

Browse files
bshepherdsonjeffbcross
authored andcommitted
feat($swipe): Refactor swipe logic from ngSwipe to $swipe service.
This new service is used by the ngSwipeLeft/Right directives, and by the separate ngCarousel and swipe-to-delete directives which are under development.
1 parent 05772e1 commit f4c6b2c

File tree

5 files changed

+437
-79
lines changed

5 files changed

+437
-79
lines changed

Gruntfile.js

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ module.exports = function(grunt) {
8989
dest: 'build/angular-mobile.js',
9090
src: util.wrap([
9191
'src/ngMobile/mobile.js',
92+
'src/ngMobile/swipe.js',
9293
'src/ngMobile/directive/ngClick.js',
9394
'src/ngMobile/directive/ngSwipe.js'
9495
], 'module')

angularFiles.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ angularFiles = {
7373
'src/ngSanitize/filter/linky.js',
7474
'src/ngMock/angular-mocks.js',
7575
'src/ngMobile/mobile.js',
76+
'src/ngMobile/swipe.js',
7677
'src/ngMobile/directive/ngClick.js',
7778
'src/ngMobile/directive/ngSwipe.js',
7879

@@ -113,6 +114,7 @@ angularFiles = {
113114
'test/ngSanitize/directive/*.js',
114115
'test/ngSanitize/filter/*.js',
115116
'test/ngMock/*.js',
117+
'test/ngMobile/*.js',
116118
'test/ngMobile/directive/*.js',
117119
'docs/component-spec/bootstrap/*.js',
118120
'docs/component-spec/*.js'
@@ -154,6 +156,7 @@ angularFiles = {
154156
'src/ngCookies/cookies.js',
155157
'src/ngResource/resource.js',
156158
'src/ngMobile/mobile.js',
159+
'src/ngMobile/swipe.js',
157160
'src/ngMobile/directive/ngClick.js',
158161
'src/ngMobile/directive/ngSwipe.js',
159162
'src/ngSanitize/sanitize.js',
@@ -168,7 +171,7 @@ angularFiles = {
168171
'test/ngSanitize/*.js',
169172
'test/ngSanitize/directive/*.js',
170173
'test/ngSanitize/filter/*.js',
171-
'test/ngMobile/directive/*.js'
174+
'test/ngMobile/**/*.js'
172175
],
173176

174177
'jstdPerf': [

src/ngMobile/directive/ngSwipe.js

+16-78
Original file line numberDiff line numberDiff line change
@@ -55,36 +55,20 @@
5555
*/
5656

5757
function makeSwipeDirective(directiveName, direction) {
58-
ngMobile.directive(directiveName, ['$parse', function($parse) {
58+
ngMobile.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) {
5959
// The maximum vertical delta for a swipe should be less than 75px.
6060
var MAX_VERTICAL_DISTANCE = 75;
6161
// Vertical distance should not be more than a fraction of the horizontal distance.
6262
var MAX_VERTICAL_RATIO = 0.3;
6363
// At least a 30px lateral motion is necessary for a swipe.
6464
var MIN_HORIZONTAL_DISTANCE = 30;
65-
// The total distance in any direction before we make the call on swipe vs. scroll.
66-
var MOVE_BUFFER_RADIUS = 10;
67-
68-
function getCoordinates(event) {
69-
var touches = event.touches && event.touches.length ? event.touches : [event];
70-
var e = (event.changedTouches && event.changedTouches[0]) ||
71-
(event.originalEvent && event.originalEvent.changedTouches &&
72-
event.originalEvent.changedTouches[0]) ||
73-
touches[0].originalEvent || touches[0];
74-
75-
return {
76-
x: e.clientX,
77-
y: e.clientY
78-
};
79-
}
8065

8166
return function(scope, element, attr) {
8267
var swipeHandler = $parse(attr[directiveName]);
68+
8369
var startCoords, valid;
84-
var totalX, totalY;
85-
var lastX, lastY;
8670

87-
function validSwipe(event) {
71+
function validSwipe(coords) {
8872
// Check that it's within the coordinates.
8973
// Absolute vertical distance must be within tolerances.
9074
// Horizontal distance, we take the current X - the starting X.
@@ -94,7 +78,6 @@ function makeSwipeDirective(directiveName, direction) {
9478
// illegal ones a negative delta.
9579
// Therefore this delta must be positive, and larger than the minimum.
9680
if (!startCoords) return false;
97-
var coords = getCoordinates(event);
9881
var deltaY = Math.abs(coords.y - startCoords.y);
9982
var deltaX = (coords.x - startCoords.x) * direction;
10083
return valid && // Short circuit for already-invalidated swipes.
@@ -104,65 +87,20 @@ function makeSwipeDirective(directiveName, direction) {
10487
deltaY / deltaX < MAX_VERTICAL_RATIO;
10588
}
10689

107-
element.bind('touchstart mousedown', function(event) {
108-
startCoords = getCoordinates(event);
109-
valid = true;
110-
totalX = 0;
111-
totalY = 0;
112-
lastX = startCoords.x;
113-
lastY = startCoords.y;
114-
});
115-
116-
element.bind('touchcancel', function(event) {
117-
valid = false;
118-
});
119-
120-
element.bind('touchmove mousemove', function(event) {
121-
if (!valid) return;
122-
123-
// Android will send a touchcancel if it thinks we're starting to scroll.
124-
// So when the total distance (+ or - or both) exceeds 10px in either direction,
125-
// we either:
126-
// - On totalX > totalY, we send preventDefault() and treat this as a swipe.
127-
// - On totalY > totalX, we let the browser handle it as a scroll.
128-
129-
// Invalidate a touch while it's in progress if it strays too far away vertically.
130-
// We don't want a scroll down and back up while drifting sideways to be a swipe just
131-
// because you happened to end up vertically close in the end.
132-
if (!startCoords) return;
133-
var coords = getCoordinates(event);
134-
135-
if (Math.abs(coords.y - startCoords.y) > MAX_VERTICAL_DISTANCE) {
90+
$swipe.bind(element, {
91+
'start': function(coords) {
92+
startCoords = coords;
93+
valid = true;
94+
},
95+
'cancel': function() {
13696
valid = false;
137-
return;
138-
}
139-
140-
totalX += Math.abs(coords.x - lastX);
141-
totalY += Math.abs(coords.y - lastY);
142-
143-
lastX = coords.x;
144-
lastY = coords.y;
145-
146-
if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
147-
return;
148-
}
149-
150-
// One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
151-
if (totalY > totalX) {
152-
valid = false;
153-
return;
154-
} else {
155-
event.preventDefault();
156-
}
157-
});
158-
159-
element.bind('touchend mouseup', function(event) {
160-
if (validSwipe(event)) {
161-
// Prevent this swipe from bubbling up to any other elements with ngSwipes.
162-
event.stopPropagation();
163-
scope.$apply(function() {
164-
swipeHandler(scope, {$event:event});
165-
});
97+
},
98+
'end': function(coords) {
99+
if (validSwipe(coords)) {
100+
scope.$apply(function() {
101+
swipeHandler(scope);
102+
});
103+
}
166104
}
167105
});
168106
};

src/ngMobile/swipe.js

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc object
5+
* @name ngMobile.$swipe
6+
*
7+
* @description
8+
* The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe
9+
* behavior, to make implementing swipe-related directives more convenient.
10+
*
11+
* It is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngMobile`, and by
12+
* `ngCarousel` in a separate component.
13+
*
14+
* # Usage
15+
* The `$swipe` service is an object with a single method: `bind`. `bind` takes an element
16+
* which is to be watched for swipes, and an object with four handler functions. See the
17+
* documentation for `bind` below.
18+
*/
19+
20+
ngMobile.factory('$swipe', [function() {
21+
// The total distance in any direction before we make the call on swipe vs. scroll.
22+
var MOVE_BUFFER_RADIUS = 10;
23+
24+
function getCoordinates(event) {
25+
var touches = event.touches && event.touches.length ? event.touches : [event];
26+
var e = (event.changedTouches && event.changedTouches[0]) ||
27+
(event.originalEvent && event.originalEvent.changedTouches &&
28+
event.originalEvent.changedTouches[0]) ||
29+
touches[0].originalEvent || touches[0];
30+
31+
return {
32+
x: e.clientX,
33+
y: e.clientY
34+
};
35+
}
36+
37+
return {
38+
/**
39+
* @ngdoc method
40+
* @name ngMobile.$swipe#bind
41+
* @methodOf ngMobile.$swipe
42+
*
43+
* @description
44+
* The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
45+
* object containing event handlers.
46+
*
47+
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
48+
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
49+
*
50+
* `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
51+
* watching for `touchmove` or `mousemove` events. These events are ignored until the total
52+
* distance moved in either dimension exceeds a small threshold.
53+
*
54+
* Once this threshold is exceeded, either the horizontal or vertical delta is greater.
55+
* - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow.
56+
* - If the vertical distance is greater, this is a scroll, and we let the browser take over.
57+
* A `cancel` event is sent.
58+
*
59+
* `move` is called on `mousemove` and `touchmove` after the above logic has determined that
60+
* a swipe is in progress.
61+
*
62+
* `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
63+
*
64+
* `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
65+
* as described above.
66+
*
67+
*/
68+
bind: function(element, eventHandlers) {
69+
// Absolute total movement, used to control swipe vs. scroll.
70+
var totalX, totalY;
71+
// Coordinates of the start position.
72+
var startCoords;
73+
// Last event's position.
74+
var lastPos;
75+
// Whether a swipe is active.
76+
var active = false;
77+
78+
element.bind('touchstart mousedown', function(event) {
79+
startCoords = getCoordinates(event);
80+
active = true;
81+
totalX = 0;
82+
totalY = 0;
83+
lastPos = startCoords;
84+
eventHandlers['start'] && eventHandlers['start'](startCoords);
85+
});
86+
87+
element.bind('touchcancel', function(event) {
88+
active = false;
89+
eventHandlers['cancel'] && eventHandlers['cancel']();
90+
});
91+
92+
element.bind('touchmove mousemove', function(event) {
93+
if (!active) return;
94+
95+
// Android will send a touchcancel if it thinks we're starting to scroll.
96+
// So when the total distance (+ or - or both) exceeds 10px in either direction,
97+
// we either:
98+
// - On totalX > totalY, we send preventDefault() and treat this as a swipe.
99+
// - On totalY > totalX, we let the browser handle it as a scroll.
100+
101+
if (!startCoords) return;
102+
var coords = getCoordinates(event);
103+
104+
totalX += Math.abs(coords.x - lastPos.x);
105+
totalY += Math.abs(coords.y - lastPos.y);
106+
107+
lastPos = coords;
108+
109+
if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
110+
return;
111+
}
112+
113+
// One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
114+
if (totalY > totalX) {
115+
// Allow native scrolling to take over.
116+
active = false;
117+
eventHandlers['cancel'] && eventHandlers['cancel']();
118+
return;
119+
} else {
120+
// Prevent the browser from scrolling.
121+
event.preventDefault();
122+
123+
eventHandlers['move'] && eventHandlers['move'](coords);
124+
}
125+
});
126+
127+
element.bind('touchend mouseup', function(event) {
128+
if (!active) return;
129+
active = false;
130+
eventHandlers['end'] && eventHandlers['end'](getCoordinates(event));
131+
});
132+
}
133+
};
134+
}]);
135+
136+

0 commit comments

Comments
 (0)