js/Mobilizing/input/Touch.js
import Component from '../core/Component';
import Time from '../time/Time';
import * as debug from '../core/util/Debug';
import * as _Math from '../core/util/Math';
import * as _DOM from '../core/util/Dom';
/**
Object that represent what defines a Touch. Touch Class returns this type of objets when events are fired.
This class is used internally and is documented for consultation purpose. Users should not make new instances from it.
*/
class TouchObject {
/**
* @param {Object} params The parameters object
* @param {Number} [params.x = 0] the x coordinate of the touch
* @param {Number} [params.y = 0] the y coordinate of the touch
* @param {Number} [params.index = 0] the index of the touch
*/
constructor({
x = 0,
y = 0,
index = 0,
} = {}) {
this.y = y;
this.x = x;
this.index = index;
/**
pX pevious x coordinate of the touch
@property {Number} pX
*/
this.pX = this.x;
/**
pY previous y coordinate of the touch
@property {Number} pY
*/
this.pY = this.y;
/**
id used internally to manage the touch list in Touch Class
@property {Object} id
*/
this.id = undefined;
/**
xDelta delta on x coordinate (difference between pevious and current x)
@property {Number} xDelta
*/
this.xDelta = 0;
/**
yDelta delta on y coordinate (difference between pevious and current y)
@property {Number} yDelta
*/
this.yDelta = 0;
/**
startX x coordinate where the touch began
@property {Number} startX
*/
this.startX = this.x;
/**
startY x coordinate where the touch began
@property {Number} startY
*/
this.startY = this.y;
/**
offset on the X coordinate (difference between startX and current x)
@property {Number} offsetX
*/
this.offsetX = 0;
/**
offset on the Y coordinate (difference between startY and current y)
@property {Number} offsetY
*/
this.offsetY = 0;
}
/**
Set a new x coordinate and compute the new xDelta and offsetX
*
@param {Object} x
*/
setX(x) {
this.x = x;
this.xDelta = this.x - this.pX;
this.offsetX = this.x - this.startX;
}
/**
Set a new y coordinate and compute the new yDelta and offsetY
*
@param {Object} y
*/
setY(y) {
this.y = y;
this.yDelta = this.y - this.pY;
this.offsetY = this.y - this.startY;
}
setPX() {
this.pX = this.x;
}
setPY() {
this.pY = this.y;
}
}
/**
Fired when a touch starts
@event touchstart
*/
const EVT_TOUCH_START = 'touchstart';
/**
Fired when a touch ends
@event touchend
*/
const EVT_TOUCH_END = 'touchend';
/**
Fired when a touch moved
@event touchmoved
*/
const EVT_TOUCH_MOVED = 'touchmoved';
/**
Fired when the tap count changed
@event tapchanged
*/
const EVT_TAP_CHANGED = 'tapchanged';
/**
Fired when the pressed state changed (ie a touch is on screen)
@event pressedchanged
*/
const EVT_PRESSED_CHANGED = 'pressedchanged';
const EVT_SWIPE_UP = "swipeup";
const EVT_SWIPE_DOWN = "swipedown";
const EVT_SWIPE_LEFT = "swipeleft";
const EVT_SWIPE_RIGHT = "swiperight";
/**
Touch give an interface to access the multitouch events of the device. It holds a list of currently active touches and various ways to access their coordinates.
@extends Component
*/
export default class Touch extends Component {
/**
* @param {Object} params Parameters object, given by the constructor.
* @param {DOMElement} params.target The DOM element that will be used to attach touch events on
*/
constructor({
target = window,
} = {}) {
super(...arguments);
this.target = target;
this._time = new Time();
this.chain(this._time);
}
/**
Initialization method
*/
setup() {
if (!this._setupDone) {
this._time.setup();
this._time.on();
/**
Gives the number of touch currently active
@property {Number} count
*/
this.count = 0;
/**
true if the active touch on screen > 1, false otherwise
@property {Boolean} pressed
*/
this.pressed = false;
this.touches = {};
this.taps = 0;
this.tapsMaxInterval = 500; //millsec but depends on timeScale (!?)
this.oldTapTime = 0;
this.pinchTouches = [];
this.pinchStart = 0;
this.pinch = 0;
this.pinchActive = false;
this.swipeMaxTime = 250;
this.swipeMinDist = 100;
// this.touchState;
// this.touchDown;
// this.touchUp;
super.setup();
}
}
on() {
super.on();
this.target.addEventListener("touchstart", (event) => this.onTouchStart(event), { passive: false });
this.target.addEventListener("touchend", (event) => this.onTouchEnd(event), { passive: false });
this.target.addEventListener("touchcancel", (event) => this.onTouchEnd(event), { passive: false });
this.target.addEventListener("touchleave", (event) => this.onTouchEnd(event), { passive: false });
this.target.addEventListener("touchmove", (event) => this.onTouchMove(event), { passive: false });
}
off() {
super.off();
this.target.removeEventListener("touchstart", (event) => this.onTouchStart(event), { passive: false });
this.target.removeEventListener("touchend", (event) => this.onTouchEnd(event), { passive: false });
this.target.removeEventListener("touchcancel", (event) => this.onTouchEnd(event), { passive: false });
this.target.removeEventListener("touchleave", (event) => this.onTouchEnd(event), { passive: false });
this.target.removeEventListener("touchmove", (event) => this.onTouchMove(event), { passive: false });
}
update() {
if (this.pressed && !this.touchState) {
this.touchDown = true;
}
else {
this.touchDown = false;
}
if (!this.pressed && this.touchState) {
this.touchUp = true;
}
else {
this.touchUp = false;
}
this.touchState = this.pressed;
Object.values(this.touches).forEach((touch) => {
touch.setPX();
touch.setPY();
});
}
/**
onTouchStart listener
Manage a new touch and organize it in the main touch list Input.touches
@private
*/
onTouchStart(event) {
let position;
if (this.target !== window) {
position = _DOM.getElementPosition(this.target);
}
else {
position = { "x": 0, "y": 0 };
}
//console.log(_DOM, position);
let newTouch = null;
if (event.changedTouches !== null && event.changedTouches !== undefined) {//touch
for (let i = 0; i < event.changedTouches.length; i++) {
const changed = event.changedTouches[i];
if (!(changed.identifier in this.touches)) {
//no touch in memory, build it
const x = changed.pageX - position.x;
const y = changed.pageY - position.x;
newTouch = new TouchObject({ x, y, "index": this.count });
debug.log("newTouch", newTouch);
newTouch.setX(x);
newTouch.setY(y);
newTouch.id = changed.identifier;
//à laisser ici!
this.touches[changed.identifier] = newTouch;
this.count += 1;
}
else {
//touch is already there, update
const touch = this.touches[changed.identifier];
const x = touch.pageX - position.x;
const y = touch.pageY - position.x;
touch.setX(x);
touch.setY(y);
}
//Events
this.events.trigger(EVT_TOUCH_START, newTouch);
}
}
//tap management, add a tap after a time interval
if (this._time.currentTime - this.oldTapTime < this.tapsMaxInterval) {
this.taps += 1;
//Events
this.events.trigger(EVT_TAP_CHANGED, this.taps);
}
else {
this.taps = 1;
//Events
this.events.trigger(EVT_TAP_CHANGED, this.taps);
}
//current time memory for next tap
this.oldTapTime = this._time.currentTime;
//pressed state
this.pressed = true;
this.events.trigger(EVT_PRESSED_CHANGED, this.pressed);
//swipe management
this.swipeStartTime = this._time.currentTime;
//avoid the browser's defaults interactions
event.preventDefault();
}
/**
onTouchEnd listener
Manage a touch removal and organize it in the main touch list Input.touches
@private
*/
onTouchEnd(event) {
if (event.changedTouches !== null && event.changedTouches !== undefined) {
//touch
for (let i = 0; i < event.changedTouches.length; i++) {
const touch = event.changedTouches[i];
if (touch.identifier in this.touches) {
//touch is there!
//Events
this.events.trigger(EVT_TOUCH_END, this.touches[touch.identifier]);
//swipe event generator (for the 1st touche only!)
if (i === 0) {
this.swipeCurrentTime = this._time.currentTime;
this.swipeTime = this.swipeCurrentTime - this.swipeStartTime;
if (this.swipeTime < this.swipeMaxTime) {
let xAxisValidated = false;
//x + axis validation
if (this.getOffsetX(0) > this.getOffsetY(0)) {
if (this.getOffsetX(0) > this.swipeMinDist) {
//console.log("swipeX+");
this.events.trigger(EVT_SWIPE_RIGHT);
xAxisValidated = true;
}
}
//x - axis validated
else if (this.getOffsetX(0) < this.getOffsetY(0)) {
if (this.getOffsetX(0) < -this.swipeMinDist) {
//console.log("swipeX-");
this.events.trigger(EVT_SWIPE_LEFT);
xAxisValidated = true;
}
}
//avoid double axis swipe
if (!xAxisValidated) {
//y + axis validation
if (this.getOffsetY(0) > this.getOffsetX(0)) {
if (this.getOffsetY(0) > this.swipeMinDist) {
console.log("swipeY+");
this.events.trigger(EVT_SWIPE_DOWN);
}
}
//y - axis validated
else if (this.getOffsetY(0) < -this.getOffsetX(0)) {
if (this.getOffsetY(0) < -this.swipeMinDist) {
console.log("swipeY-");
this.events.trigger(EVT_SWIPE_UP);
}
}
}
}
}
//erase
delete this.touches[touch.identifier];
this.count -= 1;
}
}
//reset pinch value when no fingers
if (this.count <= 1) {
this.pinchTouches = [];
this.pinchStart = 0;
this.pinch = 0;
this.pinchActive = false;
}
}
if (this.count === 0) {
this.pressed = false;
this.events.trigger(EVT_PRESSED_CHANGED, this.pressed);
}
event.preventDefault();
}
/**
onTouchMove listener
*
Manage a touch move and organize it in the main touch list Input.touches
*
@private
*/
onTouchMove(event) {
let position;
if (this.target !== window) {
position = _DOM.getElementPosition(this.target);
}
else {
position = { "x": 0, "y": 0 };
}
if (event.changedTouches !== null && event.changedTouches !== undefined) {
//touch
for (let i = 0; i < event.changedTouches.length; i++) {
const touch = event.changedTouches[i];
if (touch.identifier in this.touches) {
//touch is there!
const myTouch = this.touches[touch.identifier];
const x = touch.pageX - position.x;
const y = touch.pageY - position.y;
myTouch.setX(x);
myTouch.setY(y);
this.events.trigger(EVT_TOUCH_MOVED, myTouch);
}
}
}
this.taps = 0;
event.preventDefault();
}
/**
returns the x coordinate of the touch given as paramater
@param {Number:Int} index the index of the touch to get x coordinate from
@return {Number:Int} x coordinate of the touch if active, -1 if not active
*/
getX(index) {
let val;
if (typeof index === "number") {
for (const obj in this.touches) {
if (this.touches[obj].index === index) {
val = this.touches[obj].x;
}
}
}
return val;
}
/**
returns the y coordinate of the touch given as paramater
*
@param {Number:Int} index the index of the touch to get y coordinate from
@return {Number:Int} y coordinate of the touch if active, -1 if not active
*/
getY(index) {
let val;
if (typeof index === "number") {
for (const obj in this.touches) {
if (this.touches[obj].index === index) {
val = this.touches[obj].y;
}
}
}
return val;
}
/**
returns a TouchObject
*
@param {Number:Int} index the index of the touchObject to get
@return {TouchObject}
*/
get(index) {
let val;
if (typeof index === "number") {
for (const obj in this.touches) {
if (this.touches[obj].index === index) {
val = this.touches[obj];
}
}
}
return val;
}
/**
Returns a Number that represents the coordinate of the touch delta,
that is the numerical difference between the previous state in time and the actual one.
@param {Number:Int|Object:Touch} index the index of the touch to get x coordinate from, or the touch object
@return {Number:Int}, the x coordinates of the touch delta;
*/
getDeltaX(index) {
let val;
if (typeof index === "number") {
Object.keys(this.touches).forEach((identifier) => {
const touch = this.touches[identifier];
if (touch.index === index) {
val = touch.xDelta;
}
});
}
return val;
}
/**
Returns a Number that represents the coordinate of the touch delta,
that is the numerical difference between the previous state in time and the actual one.
*
@param {Number:Int|Object:Touch} index the index of the touch to get y coordinate from, or the touch object
@return {Number:Int}, the y coordinates of the touch delta;
*/
getDeltaY(index) {
let val;
if (typeof index === "number") {
Object.keys(this.touches).forEach((identifier) => {
const touch = this.touches[identifier];
if (touch.index === index) {
val = touch.yDelta;
}
});
}
return val;
}
/**
Returns an object {x:Number, y:Number} that represents the coordinate of the touch delta,
that is the numerical difference between the previous state in time and the actual one.
*
@param {Number:Int|Object:Touch} index the index of the touch to get coordinates from, or the touch object
@return {Object} {x:Number, y:Number}, the x & y coordinates of the touch delta;
*/
getDelta(index) {
let val;
if (typeof index === "number") {
Object.keys(this.touches).forEach((identifier) => {
const touch = this.touches[identifier];
if (touch.index === index) {
val = { "x": touch.xDelta, "y": touch.yDelta };
}
});
}
return val;
}
/**
Returns a Number that represents the x coordinate of the touch offset,
that is the numerical difference between the start point of the touch and the actual one.
*
@param {Number:Int|Object:Touch} index the index of the touch to get x offset coordinate from, or the touch object
@return {Number} the x coordinates of the touch offset;
*/
getOffsetX(index) {
let val;
Object.keys(this.touches).forEach((identifier) => {
const touch = this.touches[identifier];
if (touch.index === index) {
val = touch.offsetX;
}
});
return val;
}
/**
Returns a Number that represents the y coordinate of the touch offset,
that is the numerical difference between the start point of the touch and the actual one.
*
@param {Number:Int|Object:Touch} index the index of the touch to get y offset coordinate from, or the touch object
@return {Number} the y coordinates of the touch offset;
*/
getOffsetY(index) {
let val;
Object.keys(this.touches).forEach((identifier) => {
const touch = this.touches[identifier];
if (touch.index === index) {
val = touch.offsetY;
}
});
return val;
}
/**
Returns an object {x:Number, y:Number} that represents the coordinate of the touch offset,
that is the numerical difference between the start point of the touch and the actual one.
*
@param {Number:Int|Object:Touch} index the index of the touch to get offset coordinate from, or the touch object
@return {Object} {x:Number, y:Number}, the x & y coordinates of the touch offset;
*/
getOffset(index) {
let val;
Object.keys(this.touches).forEach((identifier) => {
const touch = this.touches[identifier];
if (touch.index === index) {
val = { "x": touch.offsetX, "y": touch.offsetY };
}
});
return val;
}
/**
*Returns a Number that represents the pinch touch move,
*that is the numerical difference between the start point of 2 touches and the actual one.
*
@return {Number} the pinch delta value;
*/
getPinch() {
//We must use a min of 2 touches
if (this.count >= 2) {
//if don't have the touches, find and save them, else compute start point
if (typeof this.pinchTouches[1] === "undefined") {
let touchNb = 0;
//loop through touches to find at least 2
Object.values(this.touches).some((touch) => {
if (touchNb < 2) {
//gather 2 touch if not already in memory
this.pinchTouches[touchNb] = touch;
touchNb++;
}
if (touchNb === 2) {
this.pinchActive = true;
//we have 2 touches in memory, compute startpoint
this.pinchStart = _Math.dist(this.pinchTouches[0].x, this.pinchTouches[0].y, this.pinchTouches[1].x, this.pinchTouches[1].y);
//debug.log("pinchStart at break",this.pinchStart);
return true; //2 touches found, break loop;
}
return false;
});
}
//pinch computation's ready to be done
this.pinch =
_Math.dist(this.pinchTouches[0].x, this.pinchTouches[0].y, this.pinchTouches[1].x, this.pinchTouches[1].y)
- this.pinchStart;
//we have 2 touches in memory, compute startpoint
this.pinchStart = _Math.dist(this.pinchTouches[0].x, this.pinchTouches[0].y, this.pinchTouches[1].x, this.pinchTouches[1].y);
}
else {
this.pinchStart = 0;
this.pinch = 0;
this.pinchTouches = [];
}
return this.pinch;
}
}