Hendrik Langer
4 years ago
4 changed files with 572 additions and 31 deletions
@ -0,0 +1,476 @@ |
|||
var VirtualJoystick = function(opts) |
|||
{ |
|||
opts = opts || {}; |
|||
this._container = opts.container || document.body; |
|||
this._strokeStyle = opts.strokeStyle || 'cyan'; |
|||
this._stickEl = opts.stickElement || this._buildJoystickStick(); |
|||
this._baseEl = opts.baseElement || this._buildJoystickBase(); |
|||
this._mouseSupport = opts.mouseSupport !== undefined ? opts.mouseSupport : false; |
|||
this._stationaryBase = opts.stationaryBase || false; |
|||
this._baseX = this._stickX = opts.baseX || 0; |
|||
this._baseY = this._stickY = opts.baseY || 0; |
|||
this._limitStickTravel = opts.limitStickTravel || false; |
|||
this._stickRadius = opts.stickRadius !== undefined ? opts.stickRadius : 100; |
|||
this._useCssTransform = opts.useCssTransform !== undefined ? opts.useCssTransform : false; |
|||
this._show = opts.show || false; |
|||
this._releaseX = opts.releaseX !== undefined ? opts.releaseX : null; |
|||
this._releaseY = opts.releaseY !== undefined ? opts.releaseY : null; |
|||
|
|||
this._container.style.position = "relative"; |
|||
this._container.appendChild(this._baseEl); |
|||
this._baseEl.style.position = "absolute"; |
|||
this._baseEl.style.display = "none"; |
|||
this._container.appendChild(this._stickEl); |
|||
this._stickEl.style.position = "absolute"; |
|||
this._stickEl.style.display = "none"; |
|||
|
|||
this._pressed = false; |
|||
this._touchIdx = null; |
|||
|
|||
if(this._stationaryBase === true){ |
|||
this._baseEl.style.display = ""; |
|||
this._baseEl.style.left = (this._baseX - this._baseEl.width /2)+"px"; |
|||
this._baseEl.style.top = (this._baseY - this._baseEl.height/2)+"px"; |
|||
} |
|||
|
|||
this._transform = this._useCssTransform ? this._getTransformProperty() : false; |
|||
this._has3d = this._check3D(); |
|||
|
|||
this._updatePositionOfContainer(); |
|||
window.onresize = (function (joystick) { |
|||
return function () { |
|||
return joystick._updatePositionOfContainer(); |
|||
} |
|||
})(this); |
|||
|
|||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; |
|||
this._$onTouchStart = __bind(this._onTouchStart , this); |
|||
this._$onTouchEnd = __bind(this._onTouchEnd , this); |
|||
this._$onTouchMove = __bind(this._onTouchMove , this); |
|||
this._container.addEventListener( 'touchstart' , this._$onTouchStart , false ); |
|||
this._container.addEventListener( 'touchend' , this._$onTouchEnd , false ); |
|||
this._container.addEventListener( 'touchmove' , this._$onTouchMove , false ); |
|||
if( this._mouseSupport ){ |
|||
this._$onMouseDown = __bind(this._onMouseDown , this); |
|||
this._$onMouseUp = __bind(this._onMouseUp , this); |
|||
this._$onMouseMove = __bind(this._onMouseMove , this); |
|||
this._container.addEventListener( 'mousedown' , this._$onMouseDown , false ); |
|||
this._container.addEventListener( 'mouseup' , this._$onMouseUp , false ); |
|||
this._container.addEventListener( 'mousemove' , this._$onMouseMove , false ); |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// //
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
VirtualJoystick.prototype.destroy = function() |
|||
{ |
|||
this._container.removeChild(this._baseEl); |
|||
this._container.removeChild(this._stickEl); |
|||
|
|||
this._container.removeEventListener( 'touchstart' , this._$onTouchStart , false ); |
|||
this._container.removeEventListener( 'touchend' , this._$onTouchEnd , false ); |
|||
this._container.removeEventListener( 'touchmove' , this._$onTouchMove , false ); |
|||
if( this._mouseSupport ){ |
|||
this._container.removeEventListener( 'mouseup' , this._$onMouseUp , false ); |
|||
this._container.removeEventListener( 'mousedown' , this._$onMouseDown , false ); |
|||
this._container.removeEventListener( 'mousemove' , this._$onMouseMove , false ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @returns {Boolean} true if touchscreen is currently available, false otherwise |
|||
*/ |
|||
VirtualJoystick.touchScreenAvailable = function() |
|||
{ |
|||
return 'createTouch' in document ? true : false; |
|||
} |
|||
|
|||
/** |
|||
* microevents.js - https://github.com/jeromeetienne/microevent.js
|
|||
*/ |
|||
;(function(destObj){ |
|||
destObj.addEventListener = function(event, fct){ |
|||
if(this._events === undefined) this._events = {}; |
|||
this._events[event] = this._events[event] || []; |
|||
this._events[event].push(fct); |
|||
return fct; |
|||
}; |
|||
destObj.removeEventListener = function(event, fct){ |
|||
if(this._events === undefined) this._events = {}; |
|||
if( event in this._events === false ) return; |
|||
this._events[event].splice(this._events[event].indexOf(fct), 1); |
|||
}; |
|||
destObj.dispatchEvent = function(event /* , args... */){ |
|||
if(this._events === undefined) this._events = {}; |
|||
if( this._events[event] === undefined ) return; |
|||
var tmpArray = this._events[event].slice(); |
|||
for(var i = 0; i < tmpArray.length; i++){ |
|||
var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1)) |
|||
if( result !== undefined ) return result; |
|||
} |
|||
return undefined |
|||
}; |
|||
})(VirtualJoystick.prototype); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// //
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
VirtualJoystick.prototype.deltaX = function(){ return this._stickX - this._baseX; } |
|||
VirtualJoystick.prototype.deltaY = function(){ return this._stickY - this._baseY; } |
|||
|
|||
VirtualJoystick.prototype.up = function(){ |
|||
if( this._pressed === false ) return false; |
|||
var deltaX = this.deltaX(); |
|||
var deltaY = this.deltaY(); |
|||
if( deltaY >= 0 ) return false; |
|||
if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false; |
|||
return true; |
|||
} |
|||
VirtualJoystick.prototype.down = function(){ |
|||
if( this._pressed === false ) return false; |
|||
var deltaX = this.deltaX(); |
|||
var deltaY = this.deltaY(); |
|||
if( deltaY <= 0 ) return false; |
|||
if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false; |
|||
return true; |
|||
} |
|||
VirtualJoystick.prototype.right = function(){ |
|||
if( this._pressed === false ) return false; |
|||
var deltaX = this.deltaX(); |
|||
var deltaY = this.deltaY(); |
|||
if( deltaX <= 0 ) return false; |
|||
if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false; |
|||
return true; |
|||
} |
|||
VirtualJoystick.prototype.left = function(){ |
|||
if( this._pressed === false ) return false; |
|||
var deltaX = this.deltaX(); |
|||
var deltaY = this.deltaY(); |
|||
if( deltaX >= 0 ) return false; |
|||
if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false; |
|||
return true; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// Dealing with touches/mouse movents
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
VirtualJoystick.prototype._UpdateBase = function(){ |
|||
this._move(this._baseEl.style, (this._baseX - this._baseEl.width /2), (this._baseY - this._baseEl.height/2)); |
|||
} |
|||
VirtualJoystick.prototype._UpdateStick = function(){ |
|||
this._move(this._stickEl.style, (this._stickX - this._stickEl.width /2), (this._stickY - this._stickEl.height/2)); |
|||
} |
|||
|
|||
|
|||
VirtualJoystick.prototype._onUp = function() |
|||
{ |
|||
this._pressed = false; |
|||
this._show ? this._stickEl.style.display = "" : this._stickEl.style.display = "none"; |
|||
if(!(this._stationaryBase || this._show)) { this._baseEl.style.display = "none"; } |
|||
|
|||
if(this._releaseX !=null || this._releaseY !=null){ |
|||
if (this._releaseX !=null){ |
|||
this._stickX= this._baseX + this._releaseX; |
|||
} |
|||
if (this._releaseY !=null){ |
|||
this._stickY= this._baseY + this._releaseY; |
|||
} |
|||
} |
|||
else { |
|||
this._stickX = this._baseX; |
|||
this._stickY = this._baseY; |
|||
} |
|||
this._UpdateStick() |
|||
} |
|||
|
|||
|
|||
VirtualJoystick.prototype._onDown = function(x, y) |
|||
{ |
|||
this._pressed = true; |
|||
if(this._stationaryBase == false){ |
|||
this._baseX = x; |
|||
this._baseY = y; |
|||
this._baseEl.style.display = ""; |
|||
this._UpdateBase() |
|||
} |
|||
|
|||
this._stickX = x; |
|||
this._stickY = y; |
|||
|
|||
if(this._limitStickTravel === true){ |
|||
var deltaX = this.deltaX(); |
|||
var deltaY = this.deltaY(); |
|||
var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) ); |
|||
if(stickDistance > this._stickRadius){ |
|||
var stickNormalizedX = deltaX / stickDistance; |
|||
var stickNormalizedY = deltaY / stickDistance; |
|||
|
|||
this._stickX = stickNormalizedX * this._stickRadius + this._baseX; |
|||
this._stickY = stickNormalizedY * this._stickRadius + this._baseY; |
|||
} |
|||
} |
|||
|
|||
this._stickEl.style.display = ""; |
|||
this._UpdateStick() |
|||
} |
|||
|
|||
VirtualJoystick.prototype._onMove = function(x, y) |
|||
{ |
|||
if( this._pressed === true ){ |
|||
this._stickX = x; |
|||
this._stickY = y; |
|||
|
|||
if(this._limitStickTravel === true){ |
|||
var deltaX = this.deltaX(); |
|||
var deltaY = this.deltaY(); |
|||
var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) ); |
|||
if(stickDistance > this._stickRadius){ |
|||
var stickNormalizedX = deltaX / stickDistance; |
|||
var stickNormalizedY = deltaY / stickDistance; |
|||
|
|||
this._stickX = stickNormalizedX * this._stickRadius + this._baseX; |
|||
this._stickY = stickNormalizedY * this._stickRadius + this._baseY; |
|||
} |
|||
} |
|||
this._UpdateStick() |
|||
} |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// bind touch events (and mouse events for debug) //
|
|||
// mouse events
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// container offset.
|
|||
VirtualJoystick.prototype._updatePositionOfContainer = function () { |
|||
var x = 0, y = 0; |
|||
var element = this._container; |
|||
if(this._stationaryBase === true){ |
|||
this._UpdateBase() |
|||
while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { |
|||
x += element.offsetLeft; |
|||
y += element.offsetTop; |
|||
element = element.offsetParent; |
|||
} |
|||
} |
|||
else { |
|||
while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { |
|||
x += element.offsetLeft - element.scrollLeft; |
|||
y += element.offsetTop - element.scrollTop; |
|||
element = element.offsetParent; |
|||
} |
|||
} |
|||
this._containerX = x; |
|||
this._containerY = y; |
|||
} |
|||
|
|||
VirtualJoystick.prototype._onMouseUp = function(event) |
|||
{ |
|||
return this._onUp(); |
|||
} |
|||
|
|||
VirtualJoystick.prototype._onMouseDown = function(event) |
|||
{ |
|||
// notify event for validation
|
|||
var isValid = this.dispatchEvent('mouseStartValidation', event); |
|||
if( isValid === false ) return; |
|||
|
|||
event.preventDefault(); |
|||
var x = event.clientX - this._containerX; |
|||
var y = event.clientY - this._containerY; |
|||
return this._onDown(x, y); |
|||
} |
|||
|
|||
VirtualJoystick.prototype._onMouseMove = function(event) |
|||
{ |
|||
var x = event.clientX - this._containerX; |
|||
var y = event.clientY - this._containerY; |
|||
return this._onMove(x, y); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// touch events //
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
VirtualJoystick.prototype._onTouchStart = function(event) |
|||
{ |
|||
// if there is already a touch inprogress do nothing
|
|||
if( this._touchIdx !== null ) return; |
|||
|
|||
// notify event for validation
|
|||
var isValid = this.dispatchEvent('touchStartValidation', event); |
|||
if( isValid === false ) return; |
|||
|
|||
// dispatch touchStart
|
|||
this.dispatchEvent('touchStart', event); |
|||
|
|||
event.preventDefault(); |
|||
// get the first who changed
|
|||
var touch = event.changedTouches[0]; |
|||
// set the touchIdx of this joystick
|
|||
this._touchIdx = touch.identifier; |
|||
|
|||
// forward the action
|
|||
var x = touch.pageX - this._containerX; |
|||
var y = touch.pageY - this._containerY; |
|||
return this._onDown(x, y) |
|||
} |
|||
|
|||
VirtualJoystick.prototype._onTouchEnd = function(event) |
|||
{ |
|||
// if there is no touch in progress, do nothing
|
|||
if( this._touchIdx === null ) return; |
|||
|
|||
// dispatch touchEnd
|
|||
this.dispatchEvent('touchEnd', event); |
|||
|
|||
// try to find our touch event
|
|||
var touchList = event.changedTouches; |
|||
for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++); |
|||
// if touch event isnt found,
|
|||
if( i === touchList.length) return; |
|||
|
|||
// reset touchIdx - mark it as no-touch-in-progress
|
|||
this._touchIdx = null; |
|||
|
|||
//??????
|
|||
// no preventDefault to get click event on ios
|
|||
event.preventDefault(); |
|||
|
|||
return this._onUp() |
|||
} |
|||
|
|||
VirtualJoystick.prototype._onTouchMove = function(event) |
|||
{ |
|||
// if there is no touch in progress, do nothing
|
|||
if( this._touchIdx === null ) return; |
|||
|
|||
// try to find our touch event
|
|||
var touchList = event.changedTouches; |
|||
for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++ ); |
|||
// if touch event with the proper identifier isnt found, do nothing
|
|||
if( i === touchList.length) return; |
|||
var touch = touchList[i]; |
|||
|
|||
event.preventDefault(); |
|||
|
|||
var x = touch.pageX - this._containerX; |
|||
var y = touch.pageY - this._containerY; |
|||
return this._onMove(x, y) |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// build default stickEl and baseEl //
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
/** |
|||
* build the canvas for joystick base |
|||
*/ |
|||
VirtualJoystick.prototype._buildJoystickBase = function() |
|||
{ |
|||
var canvas = document.createElement( 'canvas' ); |
|||
canvas.width = 126; |
|||
canvas.height = 126; |
|||
|
|||
var ctx = canvas.getContext('2d'); |
|||
ctx.beginPath(); |
|||
ctx.strokeStyle = this._strokeStyle; |
|||
ctx.lineWidth = 6; |
|||
ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true); |
|||
ctx.stroke(); |
|||
|
|||
ctx.beginPath(); |
|||
ctx.strokeStyle = this._strokeStyle; |
|||
ctx.lineWidth = 2; |
|||
ctx.arc( canvas.width/2, canvas.width/2, 60, 0, Math.PI*2, true); |
|||
ctx.stroke(); |
|||
|
|||
return canvas; |
|||
} |
|||
|
|||
/** |
|||
* build the canvas for joystick stick |
|||
*/ |
|||
VirtualJoystick.prototype._buildJoystickStick = function() |
|||
{ |
|||
var canvas = document.createElement( 'canvas' ); |
|||
canvas.width = 86; |
|||
canvas.height = 86; |
|||
var ctx = canvas.getContext('2d'); |
|||
ctx.beginPath(); |
|||
ctx.strokeStyle = this._strokeStyle; |
|||
ctx.lineWidth = 6; |
|||
ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true); |
|||
ctx.stroke(); |
|||
return canvas; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
// move using translate3d method with fallback to translate > 'top' and 'left'
|
|||
// modified from https://github.com/component/translate and dependents
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
VirtualJoystick.prototype._move = function(style, x, y) // moves the top left of cavas to (x,y).
|
|||
{ |
|||
if (this._transform) { |
|||
if (this._has3d) { |
|||
style[this._transform] = 'translate3d(' + x + 'px,' + y + 'px, 0)'; |
|||
} else { |
|||
style[this._transform] = 'translate(' + x + 'px,' + y + 'px)'; |
|||
} |
|||
} else { |
|||
style.left = x + 'px'; |
|||
style.top = y + 'px'; |
|||
} |
|||
} |
|||
|
|||
VirtualJoystick.prototype._getTransformProperty = function() |
|||
{ |
|||
var styles = [ |
|||
'webkitTransform', |
|||
'MozTransform', |
|||
'msTransform', |
|||
'OTransform', |
|||
'transform' |
|||
]; |
|||
|
|||
var el = document.createElement('p'); |
|||
var style; |
|||
|
|||
for (var i = 0; i < styles.length; i++) { |
|||
style = styles[i]; |
|||
if (null != el.style[style]) { |
|||
return style; |
|||
} |
|||
} |
|||
} |
|||
|
|||
VirtualJoystick.prototype._check3D = function() |
|||
{ |
|||
var prop = this._getTransformProperty(); |
|||
// IE8<= doesn't have `getComputedStyle`
|
|||
if (!prop || !window.getComputedStyle) return module.exports = false; |
|||
|
|||
var map = { |
|||
webkitTransform: '-webkit-transform', |
|||
OTransform: '-o-transform', |
|||
msTransform: '-ms-transform', |
|||
MozTransform: '-moz-transform', |
|||
transform: 'transform' |
|||
}; |
|||
|
|||
// from: https://gist.github.com/lorenzopolidori/3794226
|
|||
var el = document.createElement('div'); |
|||
el.style[prop] = 'translate3d(1px,1px,1px)'; |
|||
document.body.insertBefore(el, null); |
|||
var val = getComputedStyle(el).getPropertyValue(map[prop]); |
|||
document.body.removeChild(el); |
|||
var exports = null != val && val.length && 'none' != val; |
|||
return exports; |
|||
} |
|||
|
Loading…
Reference in new issue