Skip to content

Commit

Permalink
fix(gestures): detect touch action and provide polyfill.
Browse files Browse the repository at this point in the history
* On non-android browsers, touchmove events were cancelled and native scroll was disabled as well.
* touchAction is supported by the most modern-browsers and drops the cancellation of the touchmove event
* If touchAction is not supported, then we're able to use the polyfill (cancelling the touchMove event)

Fixes angular#7856
  • Loading branch information
devversion committed Apr 2, 2016
1 parent f6cf82d commit 7da7628
Showing 1 changed file with 37 additions and 5 deletions.
42 changes: 37 additions & 5 deletions src/core/services/gesture/gesture.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
var isIos = userAgent.match(/ipad|iphone|ipod/i);
var isAndroid = userAgent.match(/android/i);
var touchActionProperty = getTouchAction();
var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);

var self = {
Expand Down Expand Up @@ -215,7 +216,7 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
// If we don't preventDefault touchmove events here, Android will assume we don't
// want to listen to anymore touch events. It will start scrolling and stop sending
// touchmove events.
ev.preventDefault();
if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();

// If the user moves greater than <maxDistance> pixels, stop the hold timer
// set in onStart
Expand All @@ -234,7 +235,7 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
* The drag handler dispatches a drag event if the user holds and moves his finger greater than
* <minDistance> px in the x or y direction, depending on options.horizontal.
* The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
* the perpindicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
* the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
* pixels vertically, this handler won't consider the move part of a drag.
*/
.handler('drag', {
Expand All @@ -243,6 +244,18 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
horizontal: true,
cancelMultiplier: 1.5
},
onSetup: function(element, opts) {
if (touchActionProperty) {
// We check for horizontal to be false, because otherwise we would overwrite the default opts.
this.oldTouchAction = element[0].style[touchActionProperty];
element[0].style[touchActionProperty] = opts.horizontal === false ? 'pan-y' : 'pan-x';
}
},
onCleanup: function(element) {
if (this.oldTouchAction) {
element[0].style[touchActionProperty] = this.oldTouchAction;
}
},
onStart: function (ev) {
// For drag, require a parent to be registered with $mdGesture.register()
if (!this.state.registeredParent) this.cancel();
Expand All @@ -253,7 +266,7 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
// If we don't preventDefault touchmove events here, Android will assume we don't
// want to listen to anymore touch events. It will start scrolling and stop sending
// touchmove events.
ev.preventDefault();
if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();

if (!this.state.dragPointer) {
if (this.state.options.horizontal) {
Expand All @@ -277,7 +290,7 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
this.dispatchDragMove(ev);
}
},
// Only dispatch dragmove events every frame; any more is unnecessray
// Only dispatch dragmove events every frame; any more is unnecessary
dispatchDragMove: $$rAF.throttle(function (ev) {
// Make sure the drag didn't stop while waiting for the next frame
if (this.state.isRunning) {
Expand Down Expand Up @@ -318,6 +331,19 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
}
});

function getTouchAction() {
var testEl = document.createElement('div');
var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];

for (var i = 0; i < vendorPrefixes.length; i++) {
var prefix = vendorPrefixes[i];
var property = prefix ? prefix + 'TouchAction' : 'touchAction';
if (angular.isDefined(testEl.style[property])) {
return property;
}
}
}

}

/**
Expand Down Expand Up @@ -346,6 +372,8 @@ function MdGestureHandler() {
dispatchEvent: hasJQuery ? jQueryDispatchEvent : nativeDispatchEvent,

// These are overridden by the registered handler
onSetup: angular.noop,
onCleanup: angular.noop,
onStart: angular.noop,
onMove: angular.noop,
onEnd: angular.noop,
Expand Down Expand Up @@ -395,7 +423,7 @@ function MdGestureHandler() {
return null;
},

// Called from $mdGesture.register when an element reigsters itself with a handler.
// Called from $mdGesture.register when an element registers itself with a handler.
// Store the options the user gave on the DOMElement itself. These options will
// be retrieved with getNearestParent when the handler starts.
registerElement: function (element, options) {
Expand All @@ -404,11 +432,15 @@ function MdGestureHandler() {
element[0].$mdGesture[this.name] = options || {};
element.on('$destroy', onDestroy);

self.onSetup(element, options);

return onDestroy;

function onDestroy() {
delete element[0].$mdGesture[self.name];
element.off('$destroy', onDestroy);

self.onCleanup(element, options);
}
}
};
Expand Down

0 comments on commit 7da7628

Please sign in to comment.