var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined; // magical touchAction value var TOUCH_ACTION_COMPUTE = 'compute'; var TOUCH_ACTION_AUTO = 'auto'; var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented var TOUCH_ACTION_NONE = 'none'; var TOUCH_ACTION_PAN_X = 'pan-x'; var TOUCH_ACTION_PAN_Y = 'pan-y'; var TOUCH_ACTION_MAP = getTouchActionProps(); /** * Touch Action * sets the touchAction property or uses the js alternative * @param {Manager} manager * @param {String} value * @constructor */ function TouchAction(manager, value) { this.manager = manager; this.set(value); } TouchAction.prototype = { /** * set the touchAction value on the element or enable the polyfill * @param {String} value */ set: function(value) { // find out the touch-action by the event handlers if (value == TOUCH_ACTION_COMPUTE) { value = this.compute(); } if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) { this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; } this.actions = value.toLowerCase().trim(); }, /** * just re-set the touchAction value */ update: function() { this.set(this.manager.options.touchAction); }, /** * compute the value for the touchAction property based on the recognizer's settings * @returns {String} value */ compute: function() { var actions = []; each(this.manager.recognizers, function(recognizer) { if (boolOrFn(recognizer.options.enable, [recognizer])) { actions = actions.concat(recognizer.getTouchAction()); } }); return cleanTouchActions(actions.join(' ')); }, /** * this method is called on each input cycle and provides the preventing of the browser behavior * @param {Object} input */ preventDefaults: function(input) { var srcEvent = input.srcEvent; var direction = input.offsetDirection; // if the touch action did prevented once this session if (this.manager.session.prevented) { srcEvent.preventDefault(); return; } var actions = this.actions; var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE]; var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y]; var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X]; if (hasNone) { //do not prevent defaults if this is a tap gesture var isTapPointer = input.pointers.length === 1; var isTapMovement = input.distance < 2; var isTapTouchTime = input.deltaTime < 250; if (isTapPointer && isTapMovement && isTapTouchTime) { return; } } if (hasPanX && hasPanY) { // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent return; } if (hasNone || (hasPanY && direction & DIRECTION_HORIZONTAL) || (hasPanX && direction & DIRECTION_VERTICAL)) { return this.preventSrc(srcEvent); } }, /** * call preventDefault to prevent the browser's default behavior (scrolling in most cases) * @param {Object} srcEvent */ preventSrc: function(srcEvent) { this.manager.session.prevented = true; srcEvent.preventDefault(); } }; /** * when the touchActions are collected they are not a valid value, so we need to clean things up. * * @param {String} actions * @returns {*} */ function cleanTouchActions(actions) { // none if (inStr(actions, TOUCH_ACTION_NONE)) { return TOUCH_ACTION_NONE; } var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); // if both pan-x and pan-y are set (different recognizers // for different directions, e.g. horizontal pan but vertical swipe?) // we need none (as otherwise with pan-x pan-y combined none of these // recognizers will work, since the browser would handle all panning if (hasPanX && hasPanY) { return TOUCH_ACTION_NONE; } // pan-x OR pan-y if (hasPanX || hasPanY) { return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; } // manipulation if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { return TOUCH_ACTION_MANIPULATION; } return TOUCH_ACTION_AUTO; } function getTouchActionProps() { if (!NATIVE_TOUCH_ACTION) { return false; } var touchMap = {}; var cssSupports = window.CSS && window.CSS.supports; ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) { // If css.supports is not supported but there is native touch-action assume it supports // all values. This is the case for IE 10 and 11. touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true; }); return touchMap; }