511 lines
15 KiB
JavaScript
511 lines
15 KiB
JavaScript
|
(function() {
|
||
|
/**
|
||
|
* return unchanged if array passed, otherwise wrap in an array
|
||
|
* @param {Object|Array|Null} arr any object
|
||
|
* @return {Array}
|
||
|
*/
|
||
|
function ensureAnArray (arr) {
|
||
|
if (Object.prototype.toString.call(arr) === '[object Array]') {
|
||
|
return arr;
|
||
|
} else if (arr === null || arr === void 0) {
|
||
|
return [];
|
||
|
} else {
|
||
|
return [arr];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var Simulator = {
|
||
|
type: 'touch',
|
||
|
|
||
|
/**
|
||
|
* set type
|
||
|
* @param type
|
||
|
*/
|
||
|
setType: function(type) {
|
||
|
if(!Simulator.events[type]) {
|
||
|
throw new Error(type + " is not a valid event type.");
|
||
|
}
|
||
|
return this.type = type;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
// simple easing methods
|
||
|
// found at the source of velocity.js
|
||
|
Simulator.easings = {
|
||
|
linear: function(p) { return p; },
|
||
|
swing: function(p) { return 0.5 - Math.cos(p * Math.PI) / 2; },
|
||
|
quad: function(p) { return Math.pow(p, 2); },
|
||
|
cubic: function(p) { return Math.pow(p, 3); },
|
||
|
quart: function(p) { return Math.pow(p, 4); },
|
||
|
quint: function(p) { return Math.pow(p, 5); },
|
||
|
expo: function(p) { return Math.pow(p, 6); }
|
||
|
};
|
||
|
|
||
|
Simulator.events = {
|
||
|
/**
|
||
|
* pointer events
|
||
|
*/
|
||
|
pointer: {
|
||
|
fakeSupport: function() {
|
||
|
if(!("PointerEvent" in window)) {
|
||
|
navigator.maxTouchPoints = 10;
|
||
|
window.PointerEvent = function () {};
|
||
|
}
|
||
|
},
|
||
|
|
||
|
typeMap: {
|
||
|
start: 'pointerdown',
|
||
|
move: 'pointermove',
|
||
|
end: 'pointerup',
|
||
|
cancel: 'pointercancel'
|
||
|
},
|
||
|
|
||
|
trigger: function(touches, element, type) {
|
||
|
touches.forEach(function (touch, i) {
|
||
|
var x = Math.round(touch.x),
|
||
|
y = Math.round(touch.y);
|
||
|
|
||
|
var eventType = this.typeMap[type];
|
||
|
// ie10 style events
|
||
|
var msEventType = window.MSPointerEvent && eventType.replace(/pointer([a-z])/, function(_, a) {
|
||
|
return 'MSPointer'+ a.toUpperCase();
|
||
|
});
|
||
|
|
||
|
var event = document.createEvent('Event');
|
||
|
event.initEvent(msEventType || eventType, true, true);
|
||
|
|
||
|
event.getCurrentPoint = function() { return touch; };
|
||
|
event.setPointerCapture = event.releasePointerCapture = function() { };
|
||
|
|
||
|
event.pointerId = i;
|
||
|
event.buttons = 1;
|
||
|
event.pageX = x;
|
||
|
event.pageY = y;
|
||
|
event.clientX = x;
|
||
|
event.clientY = y;
|
||
|
event.screenX = x;
|
||
|
event.screenY = y;
|
||
|
event.target = element;
|
||
|
event.pointerType = 'touch';
|
||
|
event.identifier = i;
|
||
|
|
||
|
element.dispatchEvent(event);
|
||
|
}, this);
|
||
|
|
||
|
renderTouches(touches, element);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* touch events
|
||
|
*/
|
||
|
touch: {
|
||
|
fakeSupport: function() {
|
||
|
if(!("ontouchstart" in window)) {
|
||
|
window.ontouchstart = function () {};
|
||
|
}
|
||
|
},
|
||
|
|
||
|
emptyTouchList: function() {
|
||
|
var touchList = [];
|
||
|
touchList.identifiedTouch = touchList.item = function(index) {
|
||
|
return this[index] || {};
|
||
|
};
|
||
|
return touchList;
|
||
|
},
|
||
|
|
||
|
trigger: function (touches, element, type) {
|
||
|
var touchList = this.emptyTouchList();
|
||
|
touches.forEach(function (touch, i) {
|
||
|
var x = Math.round(touch.x),
|
||
|
y = Math.round(touch.y);
|
||
|
|
||
|
touchList.push({
|
||
|
pageX: x,
|
||
|
pageY: y,
|
||
|
clientX: x,
|
||
|
clientY: y,
|
||
|
screenX: x,
|
||
|
screenY: y,
|
||
|
target: touch.target,
|
||
|
identifier: i
|
||
|
});
|
||
|
});
|
||
|
|
||
|
var event = document.createEvent('Event');
|
||
|
event.initEvent('touch' + type, true, true);
|
||
|
|
||
|
if (type !== 'end') {
|
||
|
var targetTouches = touchList.filter(function(touch){
|
||
|
return touch.target === element;
|
||
|
})
|
||
|
|
||
|
event.changedTouches = targetTouches;
|
||
|
} else {
|
||
|
// assume that last touch is released touch. Pop it out from list of touches
|
||
|
event.changedTouches = [touchList.pop()];
|
||
|
|
||
|
var targetTouches = touchList.filter(function(touch){
|
||
|
return touch.target === element;
|
||
|
})
|
||
|
}
|
||
|
|
||
|
event.touches = touchList;
|
||
|
event.targetTouches = targetTouches;
|
||
|
element.dispatchEvent(event);
|
||
|
|
||
|
renderTouches(touches, element);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* merge objects
|
||
|
* @param dest
|
||
|
* @param src
|
||
|
* @returns dest
|
||
|
*/
|
||
|
function merge(dest, src) {
|
||
|
dest = dest || {};
|
||
|
src = src || {};
|
||
|
for (var key in src) {
|
||
|
if (src.hasOwnProperty(key) && dest[key] === undefined) {
|
||
|
dest[key] = src[key];
|
||
|
}
|
||
|
}
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* generate a list of x/y around the center
|
||
|
* @param center
|
||
|
* @param countTouches
|
||
|
* @param [radius=100]
|
||
|
* @param [rotation=0]
|
||
|
*/
|
||
|
function getTouches(center, elements, countTouches, radius, rotation) {
|
||
|
var cx = center[0],
|
||
|
cy = center[1],
|
||
|
touches = [],
|
||
|
slice, i, angle;
|
||
|
|
||
|
elements = ensureAnArray(elements);
|
||
|
|
||
|
// just one touch, at the center
|
||
|
if (countTouches === 1) {
|
||
|
if (elements.length) {
|
||
|
return [{ x: cx, y: cy, target: elements[0] }];
|
||
|
} else {
|
||
|
return [{ x: cx, y: cy }];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
radius = radius || 100;
|
||
|
rotation = (rotation * Math.PI / 180) || 0;
|
||
|
slice = 2 * Math.PI / countTouches;
|
||
|
|
||
|
for (i = 0; i < countTouches; i++) {
|
||
|
angle = (slice * i) + rotation;
|
||
|
touches.push({
|
||
|
x: (cx + radius * Math.cos(angle)),
|
||
|
y: (cy + radius * Math.sin(angle)),
|
||
|
target: elements[i % elements.length]
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return touches;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* render the touches
|
||
|
* @param touches
|
||
|
* @param element
|
||
|
* @param type
|
||
|
*/
|
||
|
function renderTouches(touches, element) {
|
||
|
touches.forEach(function(touch) {
|
||
|
var el = document.createElement('div');
|
||
|
el.style.width = '20px';
|
||
|
el.style.height = '20px';
|
||
|
el.style.background = 'red';
|
||
|
el.style.position = 'fixed';
|
||
|
el.style.top = touch.y +'px';
|
||
|
el.style.left = touch.x +'px';
|
||
|
el.style.borderRadius = '100%';
|
||
|
el.style.border = 'solid 2px #000';
|
||
|
el.style.zIndex = 6000;
|
||
|
|
||
|
element.appendChild(el);
|
||
|
setTimeout(function() {
|
||
|
el && el.parentNode && el.parentNode.removeChild(el);
|
||
|
el = null;
|
||
|
}, 100);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* trigger the touch events
|
||
|
* @param touches
|
||
|
* @param element
|
||
|
* @param type
|
||
|
* @returns {*}
|
||
|
*/
|
||
|
function trigger(touches, element, type) {
|
||
|
return Simulator.events[Simulator.type].trigger(touches, element, type);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* trigger a gesture
|
||
|
* @param elements
|
||
|
* @param startTouches
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
function triggerGesture(elements, startTouches, options, done) {
|
||
|
var interval = 10,
|
||
|
loops = Math.ceil(options.duration / interval),
|
||
|
loop = 1;
|
||
|
|
||
|
elements = ensureAnArray(elements);
|
||
|
|
||
|
options = merge(options, {
|
||
|
pos: [10, 10],
|
||
|
duration: 250,
|
||
|
touches: 1,
|
||
|
deltaX: 0,
|
||
|
deltaY: 0,
|
||
|
radius: 100,
|
||
|
scale: 1,
|
||
|
rotation: 0,
|
||
|
easing: 'swing'
|
||
|
});
|
||
|
|
||
|
function gestureLoop() {
|
||
|
// calculate the radius
|
||
|
// this is for scaling and multiple touches
|
||
|
var radius = options.radius;
|
||
|
if (options.scale !== 1) {
|
||
|
radius = options.radius - (options.radius * (1 - options.scale) * (1 / loops * loop));
|
||
|
}
|
||
|
|
||
|
// calculate new position/rotation
|
||
|
var easing = Simulator.easings[options.easing](1 / loops * loop),
|
||
|
posX = options.pos[0] + (options.deltaX / loops * loop) * easing,
|
||
|
posY = options.pos[1] + (options.deltaY / loops * loop) * easing,
|
||
|
rotation = options.rotation / loops * loop,
|
||
|
touches = getTouches([posX, posY], elements, startTouches.length, radius, rotation),
|
||
|
isFirst = (loop == 1),
|
||
|
isLast = (loop == loops);
|
||
|
|
||
|
for (var t = touches.length - 1; t >= 0; t--) {
|
||
|
if (isFirst) {
|
||
|
trigger(touches, touches[t].target, 'start');
|
||
|
} else if (isLast) {
|
||
|
trigger(touches, touches[t].target, 'end');
|
||
|
|
||
|
// Remove processed touch
|
||
|
touches.pop()
|
||
|
|
||
|
if (touches.length === 0) {
|
||
|
return done();
|
||
|
}
|
||
|
} else {
|
||
|
trigger(touches, touches[t].target, 'move');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setTimeout(gestureLoop, interval);
|
||
|
loop++;
|
||
|
}
|
||
|
gestureLoop();
|
||
|
}
|
||
|
|
||
|
Simulator.gestures = {
|
||
|
/**
|
||
|
* press
|
||
|
* @param element
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
press: function(element, options, done) {
|
||
|
options = merge(options, {
|
||
|
pos: [10, 10],
|
||
|
duration: 500,
|
||
|
touches: 1
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, element, 1);
|
||
|
|
||
|
trigger(touches, element, 'start');
|
||
|
setTimeout(function() {
|
||
|
trigger(touches, element, 'end');
|
||
|
done && setTimeout(done, 25);
|
||
|
}, options.duration);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* tap
|
||
|
* @param element
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
tap: function(element, options, done) {
|
||
|
options = merge(options, {
|
||
|
pos: [10, 10],
|
||
|
duration: 100,
|
||
|
touches: 1
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, element, 1);
|
||
|
trigger(touches, element, 'start');
|
||
|
setTimeout(function() {
|
||
|
trigger(touches, element, 'end');
|
||
|
done && setTimeout(done, 25);
|
||
|
}, options.duration);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* double tap
|
||
|
* @param element
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
doubleTap: function(element, options, done) {
|
||
|
options = merge(options, {
|
||
|
pos: [10, 10],
|
||
|
pos2: [11, 11],
|
||
|
duration: 100,
|
||
|
interval: 200,
|
||
|
touches: 1
|
||
|
});
|
||
|
|
||
|
Simulator.gestures.tap(element, options, function() {
|
||
|
setTimeout(function() {
|
||
|
options.pos = options.pos2;
|
||
|
Simulator.gestures.tap(element, options, done);
|
||
|
}, options.interval);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* pan
|
||
|
* @param element
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
pan: function(element, options, done) {
|
||
|
options = merge(options, {
|
||
|
pos: [10, 10],
|
||
|
deltaX: 300,
|
||
|
deltaY: 150,
|
||
|
duration: 250,
|
||
|
touches: 1
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, element, options.touches);
|
||
|
triggerGesture(element, touches, options, function() {
|
||
|
done && setTimeout(done, 25);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* swipe
|
||
|
* @param element
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
swipe: function(element, options, done) {
|
||
|
options = merge(options, {
|
||
|
pos: [10, 10],
|
||
|
deltaX: 300,
|
||
|
deltaY: 150,
|
||
|
duration: 250,
|
||
|
touches: 1,
|
||
|
easing: 'cubic'
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, element, options.touches);
|
||
|
triggerGesture(element, touches, options, function() {
|
||
|
done && setTimeout(done, 25);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* pinch
|
||
|
* @param {HTMLElement|Array} elements
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
pinch: function(elements, options, done) {
|
||
|
elements = ensureAnArray(elements);
|
||
|
options = merge(options, {
|
||
|
pos: [300, 300],
|
||
|
scale: 2,
|
||
|
duration: 250,
|
||
|
radius: 100,
|
||
|
touches: 2
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, elements, options.touches);
|
||
|
triggerGesture(elements, touches, options, function() {
|
||
|
done && setTimeout(done, 25);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* rotate
|
||
|
* @param {HTMLElement|Array} elements
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
rotate: function(elements, options, done) {
|
||
|
elements = ensureAnArray(elements);
|
||
|
options = merge(options, {
|
||
|
pos: [300, 300],
|
||
|
rotation: 180,
|
||
|
duration: 250,
|
||
|
touches: 2
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, elements, options.touches);
|
||
|
triggerGesture(elements, touches, options, function() {
|
||
|
done && setTimeout(done, 25);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* combination of pinch and rotate
|
||
|
* @param {HTMLElement|Array} elements
|
||
|
* @param options
|
||
|
* @param done
|
||
|
*/
|
||
|
pinchRotate: function(elements, options, done) {
|
||
|
elements = ensureAnArray(elements);
|
||
|
options = merge(options, {
|
||
|
pos: [300, 300],
|
||
|
rotation: 180,
|
||
|
radius: 100,
|
||
|
scale: .5,
|
||
|
duration: 250,
|
||
|
touches: 2
|
||
|
});
|
||
|
|
||
|
var touches = getTouches(options.pos, elements, options.touches);
|
||
|
triggerGesture(elements, touches, options, function() {
|
||
|
done && setTimeout(done, 25);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// initial
|
||
|
if(window.PointerEvent || window.MSPointerEvent) {
|
||
|
Simulator.setType('pointer');
|
||
|
} else {
|
||
|
Simulator.setType('touch');
|
||
|
Simulator.events.touch.fakeSupport();
|
||
|
}
|
||
|
|
||
|
window.Simulator = Simulator;
|
||
|
})();
|