From 54523943476a95157b3c3b7655472305691a58f2 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 5 Jun 2016 01:08:15 +0200 Subject: [PATCH] fix(gestures): detect touch action and provide polyfill. * 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 #7311. --- src/core/services/gesture/gesture.js | 42 ++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/core/services/gesture/gesture.js b/src/core/services/gesture/gesture.js index 4ed6893a226..5b51d9dfcdd 100644 --- a/src/core/services/gesture/gesture.js +++ b/src/core/services/gesture/gesture.js @@ -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 = { @@ -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 pixels, stop the hold timer // set in onStart @@ -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 * px in the x or y direction, depending on options.horizontal. * The drag will be cancelled if the user moves his finger greater than * in - * the perpindicular direction. Eg if the drag is horizontal and the user moves his finger * + * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger * * pixels vertically, this handler won't consider the move part of a drag. */ .handler('drag', { @@ -243,6 +244,18 @@ function MdGesture($$MdGestureHandler, $$rAF, $timeout) { horizontal: true, cancelMultiplier: 1.5 }, + onSetup: function(element, options) { + 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] = options.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(); @@ -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) { @@ -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) { @@ -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; + } + } + } + } /** @@ -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, @@ -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) { @@ -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 || {}); } } };