@qooxdoo/framework
Version:
The JS Framework for Coders
453 lines (369 loc) • 14.2 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2012 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Martin Wittemann (martinwittemann)
* Tino Butz (tbtz)
* Christian Hagendorn (chris_schmidt)
* Daniel Wagner (danielwagner)
************************************************************************ */
/**
* Listens for native touch events and fires composite events like "tap" and
* "swipe"
*
* @ignore(qx.event.*)
*/
qx.Bootstrap.define("qx.event.handler.TouchCore", {
extend : Object,
implement: [ qx.core.IDisposable ],
statics :
{
/** @type {Integer} The maximum distance of a tap. Only if the x or y distance of
* the performed tap is less or equal the value of this constant, a tap
* event is fired.
*/
TAP_MAX_DISTANCE : qx.core.Environment.get("os.name") != "android" ? 10 : 40,
/** @type {Map} The direction of a swipe relative to the axis */
SWIPE_DIRECTION :
{
x : ["left", "right"],
y : ["up", "down"]
},
/** @type {Integer} The minimum distance of a swipe. Only if the x or y distance
* of the performed swipe is greater as or equal the value of this
* constant, a swipe event is fired.
*/
SWIPE_MIN_DISTANCE : qx.core.Environment.get("os.name") != "android" ? 11 : 41,
/** @type {Integer} The minimum velocity of a swipe. Only if the velocity of the
* performed swipe is greater as or equal the value of this constant, a
* swipe event is fired.
*/
SWIPE_MIN_VELOCITY : 0,
/**
* @type {Integer} The time delta in milliseconds to fire a long tap event.
*/
LONGTAP_TIME : 500
},
/**
* Create a new instance
*
* @param target {Element} element on which to listen for native touch events
* @param emitter {qx.event.Emitter} Event emitter object
*/
construct : function(target, emitter)
{
this.__target = target;
this.__emitter = emitter;
this._initTouchObserver();
this.__pointers = [];
this.__touchStartPosition = {};
},
members :
{
__target : null,
__emitter : null,
__onTouchEventWrapper : null,
__originalTarget : null,
__touchStartPosition : null,
__startTime : null,
__beginScalingDistance : null,
__beginRotation : null,
__pointers : null,
__touchEventNames : null,
/*
---------------------------------------------------------------------------
OBSERVER INIT
---------------------------------------------------------------------------
*/
/**
* Initializes the native touch event listeners.
*/
_initTouchObserver : function()
{
this.__onTouchEventWrapper = qx.lang.Function.listener(this._onTouchEvent, this);
this.__touchEventNames = ["touchstart", "touchmove", "touchend", "touchcancel"];
if (qx.core.Environment.get("event.mspointer")) {
var engineVersion = parseInt(qx.core.Environment.get("engine.version"), 10);
if (engineVersion == 10) {
// IE 10
this.__touchEventNames = ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerCancel"];
} else {
// IE 11+
this.__touchEventNames = ["pointerdown", "pointermove", "pointerup", "pointercancel"];
}
}
for (var i = 0; i < this.__touchEventNames.length; i++) {
qx.bom.Event.addNativeListener(this.__target, this.__touchEventNames[i], this.__onTouchEventWrapper);
}
},
/*
---------------------------------------------------------------------------
OBSERVER STOP
---------------------------------------------------------------------------
*/
/**
* Disconnects the native touch event listeners.
*/
_stopTouchObserver : function()
{
for (var i = 0; i < this.__touchEventNames.length; i++) {
qx.bom.Event.removeNativeListener(this.__target, this.__touchEventNames[i], this.__onTouchEventWrapper);
}
},
/*
---------------------------------------------------------------------------
NATIVE EVENT OBSERVERS
---------------------------------------------------------------------------
*/
/**
* Handler for native touch events.
*
* @param domEvent {Event} The touch event from the browser.
*/
_onTouchEvent : function(domEvent)
{
this._commonTouchEventHandler(domEvent);
},
/**
* Calculates the scaling distance between two touches.
* @param touch0 {Event} The touch event from the browser.
* @param touch1 {Event} The touch event from the browser.
* @return {Number} the calculated distance.
*/
_getScalingDistance : function(touch0, touch1) {
return(Math.sqrt( Math.pow(touch0.pageX - touch1.pageX, 2) + Math.pow(touch0.pageY - touch1.pageY, 2) ));
},
/**
* Calculates the rotation between two touches.
* @param touch0 {Event} The touch event from the browser.
* @param touch1 {Event} The touch event from the browser.
* @return {Number} the calculated rotation.
*/
_getRotationAngle : function(touch0, touch1) {
var x = touch0.pageX - touch1.pageX;
var y = touch0.pageY - touch1.pageY;
return(Math.atan2(y, x)*180/Math.PI);
},
/**
* Calculates the delta of the touch position relative to its position when <code>touchstart/code> event occurred.
* @param touches {Array} an array with the current active touches, provided by <code>touchmove/code> event.
* @return {Array} an array containing objects with the calculated delta as <code>x</code>,
* <code>y</code> and the identifier of the corresponding touch.
*/
_calcTouchesDelta : function(touches) {
var delta = [];
for (var i = 0; i < touches.length; i++) {
delta.push(this._calcSingleTouchDelta(touches[i]));
}
return delta;
},
/**
* Calculates the delta of one single touch position relative to its position when <code>touchstart/code> event occurred.
* @param touch {Event} the current active touch, provided by <code>touchmove/code> event.
* @return {Map} a map containing deltaX as <code>x</code>, deltaY as <code>y</code>, the direction of the movement as <code>axis</code> and the touch identifier as <code>identifier</code>.
*/
_calcSingleTouchDelta : function(touch) {
if (this.__touchStartPosition.hasOwnProperty(touch.identifier)) {
var touchStartPosition = this.__touchStartPosition[touch.identifier];
var deltaX = Math.floor(touch.clientX - touchStartPosition[0]);
var deltaY = Math.floor(touch.clientY - touchStartPosition[1]);
var axis = "x";
if (Math.abs(deltaX / deltaY) < 1) {
axis = "y";
}
return {
"x": deltaX,
"y": deltaY,
"axis": axis,
"identifier": touch.identifier
};
} else {
return {
"x": 0,
"y": 0,
"axis": null,
"identifier": touch.identifier
};
}
},
/**
* Called by an event handler.
*
* @param domEvent {Event} DOM event
* @param type {String ? null} type of the event
*/
_commonTouchEventHandler : function(domEvent, type)
{
var type = type || domEvent.type;
if (qx.core.Environment.get("event.mspointer")) {
type = this._mapPointerEvent(type);
var touches = this._detectTouchesByPointer(domEvent, type);
domEvent.changedTouches = touches;
domEvent.targetTouches = touches;
domEvent.touches = touches;
}
domEvent.delta = [];
if (type == "touchstart") {
this.__originalTarget = this._getTarget(domEvent);
if(domEvent.touches && domEvent.touches.length > 1) {
this.__beginScalingDistance = this._getScalingDistance(domEvent.touches[0],domEvent.touches[1]);
this.__beginRotation = this._getRotationAngle(domEvent.touches[0], domEvent.touches[1]);
}
for (var i = 0; i < domEvent.changedTouches.length; i++) {
var touch = domEvent.changedTouches[i];
this.__touchStartPosition[touch.identifier] = [touch.clientX,touch.clientY];
}
}
if(type == "touchmove") {
// Polyfill for scale
if(typeof domEvent.scale == "undefined" && domEvent.targetTouches.length > 1) {
var currentScalingDistance = this._getScalingDistance(domEvent.targetTouches[0],domEvent.targetTouches[1]);
domEvent.scale = currentScalingDistance / this.__beginScalingDistance;
}
// Polyfill for rotation
if((typeof domEvent.rotation == "undefined" || qx.core.Environment.get("event.mspointer")) && domEvent.targetTouches.length > 1) {
var currentRotation = this._getRotationAngle(domEvent.targetTouches[0], domEvent.targetTouches[1]);
domEvent._rotation = currentRotation - this.__beginRotation;
}
domEvent.delta = this._calcTouchesDelta(domEvent.targetTouches);
}
this._fireEvent(domEvent, type, this.__originalTarget);
if (qx.core.Environment.get("event.mspointer")) {
if (type == "touchend" || type == "touchcancel") {
delete this.__pointers[domEvent.pointerId];
}
}
if ((type == "touchend" || type == "touchcancel") && domEvent.changedTouches[0]) {
delete this.__touchStartPosition[domEvent.changedTouches[0].identifier];
}
},
/**
* Creates an array with all current used touches out of multiple serial pointer events.
* Needed because pointerEvents do not provide a touch list.
* @param domEvent {Event} DOM event
* @param type {String ? null} type of the event
* @return {Array} touch list array.
*/
_detectTouchesByPointer : function(domEvent, type) {
var touches = [];
if (type == "touchstart") {
this.__pointers[domEvent.pointerId] = domEvent;
} else if (type == "touchmove") {
this.__pointers[domEvent.pointerId] = domEvent;
}
for (var pointerId in this.__pointers) {
var pointer = this.__pointers[pointerId];
touches.push(pointer);
}
return touches;
},
/**
* Maps a pointer event type to the corresponding touch event type.
* @param type {String} the event type to parse.
* @return {String} the parsed event name.
*/
_mapPointerEvent : function(type)
{
type = type.toLowerCase();
if (type.indexOf("pointerdown") !== -1) {
return "touchstart";
} else if (type.indexOf("pointerup") !== -1) {
return "touchend";
} else if (type.indexOf("pointermove") !== -1) {
return "touchmove";
} else if (type.indexOf("pointercancel") !== -1) {
return "touchcancel";
}
return type;
},
/**
* Return the target of the event.
*
* @param domEvent {Event} DOM event
* @return {Element} Event target
*/
_getTarget : function(domEvent)
{
var target = qx.bom.Event.getTarget(domEvent);
// Text node. Fix Safari Bug, see http://www.quirksmode.org/js/events_properties.html
if (qx.core.Environment.get("engine.name") == "webkit")
{
if (target && target.nodeType == 3) {
target = target.parentNode;
}
} else if(qx.core.Environment.get("engine.name") == "mshtml" &&
qx.core.Environment.get("browser.documentmode") < 11) {
// Fix for IE10 and pointer-events:none
//
// Changed the condition above to match exactly those browsers
// for which the fix was intended
// See: https://github.com/qooxdoo/qooxdoo/issues/9481
//
var targetForIE = this.__evaluateTarget(domEvent);
if(targetForIE) {
target = targetForIE;
}
}
return target;
},
/**
* This method fixes "pointer-events:none" for Internet Explorer 10.
* Checks which elements are placed to position x/y and traverses the array
* till one element has no "pointer-events:none" inside its style attribute.
* @param domEvent {Event} DOM event
* @return {Element | null} Event target
*/
__evaluateTarget : function(domEvent) {
var clientX = null;
var clientY = null;
if(domEvent && domEvent.touches && domEvent.touches.length !== 0){
clientX = domEvent.touches[0].clientX;
clientY = domEvent.touches[0].clientY;
}
// Retrieve an array with elements on point X/Y.
var hitTargets = document.msElementsFromPoint(clientX, clientY);
if(hitTargets) {
// Traverse this array for the elements which has no pointer-events:none inside.
for (var i = 0; i < hitTargets.length; i++) {
var currentTarget = hitTargets[i];
var pointerEvents = qx.bom.element.Style.get(currentTarget, "pointer-events", 3);
if (pointerEvents != "none") {
return currentTarget;
}
}
}
return null;
},
/**
* Fire a touch event with the given parameters
*
* @param domEvent {Event} DOM event
* @param type {String ? null} type of the event
* @param target {Element ? null} event target
*/
_fireEvent : function(domEvent, type, target)
{
if (!target) {
target = this._getTarget(domEvent);
}
var type = type || domEvent.type;
if (target && target.nodeType && this.__emitter)
{
this.__emitter.emit(type, domEvent);
}
},
/**
* Dispose this object
*/
dispose : function()
{
this._stopTouchObserver();
this.__originalTarget = this.__target = this.__touchEventNames = this.__pointers = this.__emitter = this.__beginScalingDistance = this.__beginRotation = null;
}
}
});