First commit
This commit is contained in:
450
js/threex.domevents.js
Executable file
450
js/threex.domevents.js
Executable file
@@ -0,0 +1,450 @@
|
||||
// This THREEx helper makes it easy to handle the mouse events in your 3D scene
|
||||
//
|
||||
// * CHANGES NEEDED
|
||||
// * handle drag/drop
|
||||
// * notify events not object3D - like DOM
|
||||
// * so single object with property
|
||||
// * DONE bubling implement bubling/capturing
|
||||
// * DONE implement event.stopPropagation()
|
||||
// * DONE implement event.type = "click" and co
|
||||
// * DONE implement event.target
|
||||
//
|
||||
// # Lets get started
|
||||
//
|
||||
// First you include it in your page
|
||||
//
|
||||
// ```<script src='threex.domevent.js'>< /script>```
|
||||
//
|
||||
// # use the object oriented api
|
||||
//
|
||||
// You bind an event like this
|
||||
//
|
||||
// ```mesh.on('click', function(object3d){ ... })```
|
||||
//
|
||||
// To unbind an event, just do
|
||||
//
|
||||
// ```mesh.off('click', function(object3d){ ... })```
|
||||
//
|
||||
// As an alternative, there is another naming closer DOM events.
|
||||
// Pick the one you like, they are doing the same thing
|
||||
//
|
||||
// ```mesh.addEventListener('click', function(object3d){ ... })```
|
||||
// ```mesh.removeEventListener('click', function(object3d){ ... })```
|
||||
//
|
||||
// # Supported Events
|
||||
//
|
||||
// Always in a effort to stay close to usual pratices, the events name are the same as in DOM.
|
||||
// The semantic is the same too.
|
||||
// Currently, the available events are
|
||||
// [click, dblclick, mouseup, mousedown](http://www.quirksmode.org/dom/events/click.html),
|
||||
// [mouseover and mouse out](http://www.quirksmode.org/dom/events/mouseover.html).
|
||||
//
|
||||
// # use the standalone api
|
||||
//
|
||||
// The object-oriented api modifies THREE.Object3D class.
|
||||
// It is a global class, so it may be legitimatly considered unclean by some people.
|
||||
// If this bother you, simply do ```THREEx.DomEvents.noConflict()``` and use the
|
||||
// standalone API. In fact, the object oriented API is just a thin wrapper
|
||||
// on top of the standalone API.
|
||||
//
|
||||
// First, you instanciate the object
|
||||
//
|
||||
// ```var domEvent = new THREEx.DomEvent();```
|
||||
//
|
||||
// Then you bind an event like this
|
||||
//
|
||||
// ```domEvent.bind(mesh, 'click', function(object3d){ object3d.scale.x *= 2; });```
|
||||
//
|
||||
// To unbind an event, just do
|
||||
//
|
||||
// ```domEvent.unbind(mesh, 'click', callback);```
|
||||
//
|
||||
//
|
||||
// # Code
|
||||
|
||||
//
|
||||
|
||||
/** @namespace */
|
||||
var THREEx = THREEx || {};
|
||||
|
||||
// # Constructor
|
||||
THREEx.DomEvents = function(camera, domElement)
|
||||
{
|
||||
this._camera = camera || null;
|
||||
this._domElement= domElement || document;
|
||||
this._raycaster = new THREE.Raycaster();
|
||||
this._selected = null;
|
||||
this._boundObjs = {};
|
||||
// Bind dom event for mouse and touch
|
||||
var _this = this;
|
||||
|
||||
this._$onClick = function(){ _this._onClick.apply(_this, arguments); };
|
||||
this._$onDblClick = function(){ _this._onDblClick.apply(_this, arguments); };
|
||||
this._$onMouseMove = function(){ _this._onMouseMove.apply(_this, arguments); };
|
||||
this._$onMouseDown = function(){ _this._onMouseDown.apply(_this, arguments); };
|
||||
this._$onMouseUp = function(){ _this._onMouseUp.apply(_this, arguments); };
|
||||
this._$onTouchMove = function(){ _this._onTouchMove.apply(_this, arguments); };
|
||||
this._$onTouchStart = function(){ _this._onTouchStart.apply(_this, arguments); };
|
||||
this._$onTouchEnd = function(){ _this._onTouchEnd.apply(_this, arguments); };
|
||||
this._$onContextmenu = function(){ _this._onContextmenu.apply(_this, arguments); };
|
||||
this._domElement.addEventListener( 'click' , this._$onClick , false );
|
||||
this._domElement.addEventListener( 'dblclick' , this._$onDblClick , false );
|
||||
this._domElement.addEventListener( 'mousemove' , this._$onMouseMove , false );
|
||||
this._domElement.addEventListener( 'mousedown' , this._$onMouseDown , false );
|
||||
this._domElement.addEventListener( 'mouseup' , this._$onMouseUp , false );
|
||||
this._domElement.addEventListener( 'touchmove' , this._$onTouchMove , false );
|
||||
this._domElement.addEventListener( 'touchstart' , this._$onTouchStart , false );
|
||||
this._domElement.addEventListener( 'touchend' , this._$onTouchEnd , false );
|
||||
this._domElement.addEventListener( 'contextmenu', this._$onContextmenu , false );
|
||||
|
||||
}
|
||||
|
||||
// # Destructor
|
||||
THREEx.DomEvents.prototype.destroy = function()
|
||||
{
|
||||
// unBind dom event for mouse and touch
|
||||
this._domElement.removeEventListener( 'click' , this._$onClick , false );
|
||||
this._domElement.removeEventListener( 'dblclick' , this._$onDblClick , false );
|
||||
this._domElement.removeEventListener( 'mousemove' , this._$onMouseMove , false );
|
||||
this._domElement.removeEventListener( 'mousedown' , this._$onMouseDown , false );
|
||||
this._domElement.removeEventListener( 'mouseup' , this._$onMouseUp , false );
|
||||
this._domElement.removeEventListener( 'touchmove' , this._$onTouchMove , false );
|
||||
this._domElement.removeEventListener( 'touchstart' , this._$onTouchStart , false );
|
||||
this._domElement.removeEventListener( 'touchend' , this._$onTouchEnd , false );
|
||||
this._domElement.removeEventListener( 'contextmenu' , this._$onContextmenu , false );
|
||||
}
|
||||
|
||||
THREEx.DomEvents.eventNames = [
|
||||
"click",
|
||||
"dblclick",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"mousemove",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"contextmenu",
|
||||
"touchstart",
|
||||
"touchend"
|
||||
];
|
||||
|
||||
THREEx.DomEvents.prototype._getRelativeMouseXY = function(domEvent){
|
||||
var element = domEvent.target || domEvent.srcElement;
|
||||
if (element.nodeType === 3) {
|
||||
element = element.parentNode; // Safari fix -- see http://www.quirksmode.org/js/events_properties.html
|
||||
}
|
||||
|
||||
//get the real position of an element relative to the page starting point (0, 0)
|
||||
//credits go to brainjam on answering http://stackoverflow.com/questions/5755312/getting-mouse-position-relative-to-content-area-of-an-element
|
||||
var elPosition = { x : 0 , y : 0};
|
||||
var tmpElement = element;
|
||||
//store padding
|
||||
var style = getComputedStyle(tmpElement, null);
|
||||
elPosition.y += parseInt(style.getPropertyValue("padding-top"), 10);
|
||||
elPosition.x += parseInt(style.getPropertyValue("padding-left"), 10);
|
||||
//add positions
|
||||
do {
|
||||
elPosition.x += tmpElement.offsetLeft;
|
||||
elPosition.y += tmpElement.offsetTop;
|
||||
style = getComputedStyle(tmpElement, null);
|
||||
|
||||
elPosition.x += parseInt(style.getPropertyValue("border-left-width"), 10);
|
||||
elPosition.y += parseInt(style.getPropertyValue("border-top-width"), 10);
|
||||
} while(tmpElement = tmpElement.offsetParent);
|
||||
|
||||
var elDimension = {
|
||||
width : (element === window) ? window.innerWidth : element.offsetWidth,
|
||||
height : (element === window) ? window.innerHeight : element.offsetHeight
|
||||
};
|
||||
|
||||
return {
|
||||
x : +((domEvent.pageX - elPosition.x) / elDimension.width ) * 2 - 1,
|
||||
y : -((domEvent.pageY - elPosition.y) / elDimension.height) * 2 + 1
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
/* domevent context */
|
||||
/********************************************************************************/
|
||||
|
||||
// handle domevent context in object3d instance
|
||||
|
||||
THREEx.DomEvents.prototype._objectCtxInit = function(object3d){
|
||||
object3d._3xDomEvent = {};
|
||||
}
|
||||
THREEx.DomEvents.prototype._objectCtxDeinit = function(object3d){
|
||||
delete object3d._3xDomEvent;
|
||||
}
|
||||
THREEx.DomEvents.prototype._objectCtxIsInit = function(object3d){
|
||||
return object3d._3xDomEvent ? true : false;
|
||||
}
|
||||
THREEx.DomEvents.prototype._objectCtxGet = function(object3d){
|
||||
return object3d._3xDomEvent;
|
||||
}
|
||||
|
||||
/********************************************************************************/
|
||||
/* */
|
||||
/********************************************************************************/
|
||||
|
||||
/**
|
||||
* Getter/Setter for camera
|
||||
*/
|
||||
THREEx.DomEvents.prototype.camera = function(value)
|
||||
{
|
||||
if( value ) this._camera = value;
|
||||
return this._camera;
|
||||
}
|
||||
|
||||
THREEx.DomEvents.prototype.bind = function(object3d, eventName, callback, useCapture)
|
||||
{
|
||||
console.assert( THREEx.DomEvents.eventNames.indexOf(eventName) !== -1, "not available events:"+eventName );
|
||||
|
||||
if( !this._objectCtxIsInit(object3d) ) this._objectCtxInit(object3d);
|
||||
var objectCtx = this._objectCtxGet(object3d);
|
||||
if( !objectCtx[eventName+'Handlers'] ) objectCtx[eventName+'Handlers'] = [];
|
||||
|
||||
objectCtx[eventName+'Handlers'].push({
|
||||
callback : callback,
|
||||
useCapture : useCapture
|
||||
});
|
||||
|
||||
// add this object in this._boundObjs
|
||||
if( this._boundObjs[eventName] === undefined ){
|
||||
this._boundObjs[eventName] = [];
|
||||
}
|
||||
this._boundObjs[eventName].push(object3d);
|
||||
}
|
||||
THREEx.DomEvents.prototype.addEventListener = THREEx.DomEvents.prototype.bind
|
||||
|
||||
THREEx.DomEvents.prototype.unbind = function(object3d, eventName, callback, useCapture)
|
||||
{
|
||||
console.assert( THREEx.DomEvents.eventNames.indexOf(eventName) !== -1, "not available events:"+eventName );
|
||||
|
||||
if( !this._objectCtxIsInit(object3d) ) this._objectCtxInit(object3d);
|
||||
|
||||
var objectCtx = this._objectCtxGet(object3d);
|
||||
if( !objectCtx[eventName+'Handlers'] ) objectCtx[eventName+'Handlers'] = [];
|
||||
|
||||
var handlers = objectCtx[eventName+'Handlers'];
|
||||
for(var i = 0; i < handlers.length; i++){
|
||||
var handler = handlers[i];
|
||||
if( callback != handler.callback ) continue;
|
||||
if( useCapture != handler.useCapture ) continue;
|
||||
handlers.splice(i, 1)
|
||||
break;
|
||||
}
|
||||
// from this object from this._boundObjs
|
||||
var index = this._boundObjs[eventName].indexOf(object3d);
|
||||
console.assert( index !== -1 );
|
||||
this._boundObjs[eventName].splice(index, 1);
|
||||
}
|
||||
THREEx.DomEvents.prototype.removeEventListener = THREEx.DomEvents.prototype.unbind
|
||||
|
||||
THREEx.DomEvents.prototype._bound = function(eventName, object3d)
|
||||
{
|
||||
var objectCtx = this._objectCtxGet(object3d);
|
||||
if( !objectCtx ) return false;
|
||||
return objectCtx[eventName+'Handlers'] ? true : false;
|
||||
}
|
||||
|
||||
/********************************************************************************/
|
||||
/* onMove */
|
||||
/********************************************************************************/
|
||||
|
||||
// # handle mousemove kind of events
|
||||
|
||||
THREEx.DomEvents.prototype._onMove = function(eventName, mouseX, mouseY, origDomEvent)
|
||||
{
|
||||
//console.log('eventName', eventName, 'boundObjs', this._boundObjs[eventName])
|
||||
// get objects bound to this event
|
||||
var boundObjs = this._boundObjs[eventName];
|
||||
if( boundObjs === undefined || boundObjs.length === 0 ) return;
|
||||
// compute the intersection
|
||||
var vector = new THREE.Vector2();
|
||||
|
||||
// update the picking ray with the camera and mouse position
|
||||
vector.set( mouseX, mouseY );
|
||||
this._raycaster.setFromCamera( vector, this._camera );
|
||||
|
||||
var intersects = this._raycaster.intersectObjects( boundObjs );
|
||||
|
||||
var oldSelected = this._selected;
|
||||
|
||||
if( intersects.length > 0 ){
|
||||
var notifyOver, notifyOut, notifyMove;
|
||||
var intersect = intersects[ 0 ];
|
||||
var newSelected = intersect.object;
|
||||
this._selected = newSelected;
|
||||
// if newSelected bound mousemove, notify it
|
||||
notifyMove = this._bound('mousemove', newSelected);
|
||||
|
||||
if( oldSelected != newSelected ){
|
||||
// if newSelected bound mouseenter, notify it
|
||||
notifyOver = this._bound('mouseover', newSelected);
|
||||
// if there is a oldSelect and oldSelected bound mouseleave, notify it
|
||||
notifyOut = oldSelected && this._bound('mouseout', oldSelected);
|
||||
}
|
||||
}else{
|
||||
// if there is a oldSelect and oldSelected bound mouseleave, notify it
|
||||
notifyOut = oldSelected && this._bound('mouseout', oldSelected);
|
||||
this._selected = null;
|
||||
}
|
||||
|
||||
|
||||
// notify mouseMove - done at the end with a copy of the list to allow callback to remove handlers
|
||||
notifyMove && this._notify('mousemove', newSelected, origDomEvent, intersect);
|
||||
// notify mouseEnter - done at the end with a copy of the list to allow callback to remove handlers
|
||||
notifyOver && this._notify('mouseover', newSelected, origDomEvent, intersect);
|
||||
// notify mouseLeave - done at the end with a copy of the list to allow callback to remove handlers
|
||||
notifyOut && this._notify('mouseout' , oldSelected, origDomEvent, intersect);
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
/* onEvent */
|
||||
/********************************************************************************/
|
||||
|
||||
// # handle click kind of events
|
||||
|
||||
THREEx.DomEvents.prototype._onEvent = function(eventName, mouseX, mouseY, origDomEvent)
|
||||
{
|
||||
//console.log('eventName', eventName, 'boundObjs', this._boundObjs[eventName])
|
||||
// get objects bound to this event
|
||||
var boundObjs = this._boundObjs[eventName];
|
||||
if( boundObjs === undefined || boundObjs.length === 0 ) return;
|
||||
// compute the intersection
|
||||
var vector = new THREE.Vector2();
|
||||
|
||||
// update the picking ray with the camera and mouse position
|
||||
vector.set( mouseX, mouseY );
|
||||
this._raycaster.setFromCamera( vector, this._camera );
|
||||
|
||||
var intersects = this._raycaster.intersectObjects( boundObjs, true);
|
||||
// if there are no intersections, return now
|
||||
if( intersects.length === 0 ) return;
|
||||
|
||||
// init some variables
|
||||
var intersect = intersects[0];
|
||||
var object3d = intersect.object;
|
||||
var objectCtx = this._objectCtxGet(object3d);
|
||||
var objectParent = object3d.parent;
|
||||
|
||||
while ( typeof(objectCtx) == 'undefined' && objectParent )
|
||||
{
|
||||
objectCtx = this._objectCtxGet(objectParent);
|
||||
objectParent = objectParent.parent;
|
||||
}
|
||||
if( !objectCtx ) return;
|
||||
|
||||
// notify handlers
|
||||
this._notify(eventName, object3d, origDomEvent, intersect);
|
||||
}
|
||||
|
||||
THREEx.DomEvents.prototype._notify = function(eventName, object3d, origDomEvent, intersect)
|
||||
{
|
||||
var objectCtx = this._objectCtxGet(object3d);
|
||||
var handlers = objectCtx ? objectCtx[eventName+'Handlers'] : null;
|
||||
|
||||
// parameter check
|
||||
console.assert(arguments.length === 4)
|
||||
|
||||
// do bubbling
|
||||
if( !objectCtx || !handlers || handlers.length === 0 ){
|
||||
object3d.parent && this._notify(eventName, object3d.parent, origDomEvent, intersect);
|
||||
return;
|
||||
}
|
||||
|
||||
// notify all handlers
|
||||
var handlers = objectCtx[eventName+'Handlers'];
|
||||
for(var i = 0; i < handlers.length; i++){
|
||||
var handler = handlers[i];
|
||||
var toPropagate = true;
|
||||
handler.callback({
|
||||
type : eventName,
|
||||
target : object3d,
|
||||
origDomEvent : origDomEvent,
|
||||
intersect : intersect,
|
||||
stopPropagation : function(){
|
||||
toPropagate = false;
|
||||
}
|
||||
});
|
||||
if( !toPropagate ) continue;
|
||||
// do bubbling
|
||||
if( handler.useCapture === false ){
|
||||
object3d.parent && this._notify(eventName, object3d.parent, origDomEvent, intersect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************************/
|
||||
/* handle mouse events */
|
||||
/********************************************************************************/
|
||||
// # handle mouse events
|
||||
|
||||
THREEx.DomEvents.prototype._onMouseDown = function(event){ return this._onMouseEvent('mousedown', event); }
|
||||
THREEx.DomEvents.prototype._onMouseUp = function(event){ return this._onMouseEvent('mouseup' , event); }
|
||||
|
||||
|
||||
THREEx.DomEvents.prototype._onMouseEvent = function(eventName, domEvent)
|
||||
{
|
||||
var mouseCoords = this._getRelativeMouseXY(domEvent);
|
||||
this._onEvent(eventName, mouseCoords.x, mouseCoords.y, domEvent);
|
||||
}
|
||||
|
||||
THREEx.DomEvents.prototype._onMouseMove = function(domEvent)
|
||||
{
|
||||
var mouseCoords = this._getRelativeMouseXY(domEvent);
|
||||
this._onMove('mousemove', mouseCoords.x, mouseCoords.y, domEvent);
|
||||
this._onMove('mouseover', mouseCoords.x, mouseCoords.y, domEvent);
|
||||
this._onMove('mouseout' , mouseCoords.x, mouseCoords.y, domEvent);
|
||||
}
|
||||
|
||||
THREEx.DomEvents.prototype._onClick = function(event)
|
||||
{
|
||||
// TODO handle touch ?
|
||||
this._onMouseEvent('click' , event);
|
||||
}
|
||||
THREEx.DomEvents.prototype._onDblClick = function(event)
|
||||
{
|
||||
// TODO handle touch ?
|
||||
this._onMouseEvent('dblclick' , event);
|
||||
}
|
||||
|
||||
THREEx.DomEvents.prototype._onContextmenu = function(event)
|
||||
{
|
||||
//TODO don't have a clue about how this should work with touch..
|
||||
this._onMouseEvent('contextmenu' , event);
|
||||
}
|
||||
|
||||
/********************************************************************************/
|
||||
/* handle touch events */
|
||||
/********************************************************************************/
|
||||
// # handle touch events
|
||||
|
||||
|
||||
THREEx.DomEvents.prototype._onTouchStart = function(event){ return this._onTouchEvent('touchstart', event); }
|
||||
THREEx.DomEvents.prototype._onTouchEnd = function(event){ return this._onTouchEvent('touchend' , event); }
|
||||
|
||||
THREEx.DomEvents.prototype._onTouchMove = function(domEvent)
|
||||
{
|
||||
if( domEvent.touches.length != 1 ) return undefined;
|
||||
|
||||
domEvent.preventDefault();
|
||||
|
||||
var mouseX = +(domEvent.touches[ 0 ].pageX / window.innerWidth ) * 2 - 1;
|
||||
var mouseY = -(domEvent.touches[ 0 ].pageY / window.innerHeight) * 2 + 1;
|
||||
this._onMove('mousemove', mouseX, mouseY, domEvent);
|
||||
this._onMove('mouseover', mouseX, mouseY, domEvent);
|
||||
this._onMove('mouseout' , mouseX, mouseY, domEvent);
|
||||
}
|
||||
|
||||
THREEx.DomEvents.prototype._onTouchEvent = function(eventName, domEvent)
|
||||
{
|
||||
if( domEvent.touches.length != 1 ) return undefined;
|
||||
|
||||
domEvent.preventDefault();
|
||||
|
||||
var mouseX = +(domEvent.touches[ 0 ].pageX / window.innerWidth ) * 2 - 1;
|
||||
var mouseY = -(domEvent.touches[ 0 ].pageY / window.innerHeight) * 2 + 1;
|
||||
this._onEvent(eventName, mouseX, mouseY, domEvent);
|
||||
}
|
||||
Reference in New Issue
Block a user