improved mouseover and mouseout event handling by ignoring parent handlers if mouse moves from one child to another

This commit is contained in:
Eric Rowell 2012-03-18 11:24:57 -07:00
parent 9a61e149df
commit 84e7e71461
5 changed files with 237 additions and 83 deletions

94
dist/kinetic-core.js vendored
View File

@ -3,7 +3,7 @@
* http://www.kineticjs.com/ * http://www.kineticjs.com/
* Copyright 2012, Eric Rowell * Copyright 2012, Eric Rowell
* Licensed under the MIT or GPL Version 2 licenses. * Licensed under the MIT or GPL Version 2 licenses.
* Date: Mar 17 2012 * Date: Mar 18 2012
* *
* Copyright (C) 2011 - 2012 by Eric Rowell * Copyright (C) 2011 - 2012 by Eric Rowell
* *
@ -733,23 +733,42 @@ Kinetic.Node.prototype = {
* @param {Event} evt * @param {Event} evt
*/ */
_handleEvents: function(eventType, evt) { _handleEvents: function(eventType, evt) {
// generic events handler var stage = this.getStage();
function handle(obj) { this._handleEvent(this, stage.mouseoverShape, stage.mouseoutShape, eventType, evt);
var el = obj.eventListeners; },
if(el[eventType]) { /**
* handle node event
*/
_handleEvent: function(node, mouseoverNode, mouseoutNode, eventType, evt) {
var el = node.eventListeners;
var okayToRun = true;
/*
* determine if event handler should be skipped by comparing
* parent nodes
*/
if(eventType === 'onmouseover' && mouseoutNode && mouseoutNode.id === node.id) {
okayToRun = false;
}
else if(eventType === 'onmouseout' && mouseoverNode && mouseoverNode.id === node.id) {
okayToRun = false;
}
if(el[eventType] && okayToRun) {
var events = el[eventType]; var events = el[eventType];
for(var i = 0; i < events.length; i++) { for(var i = 0; i < events.length; i++) {
events[i].handler.apply(obj, [evt]); events[i].handler.apply(node, [evt]);
} }
} }
var mouseoverParent = mouseoverNode ? mouseoverNode.parent : undefined;
var mouseoutParent = mouseoutNode ? mouseoutNode.parent : undefined;
// simulate event bubbling // simulate event bubbling
if(!evt.cancelBubble && obj.parent.className !== 'Stage') { if(!evt.cancelBubble && node.parent.className !== 'Stage') {
handle(obj.parent); this._handleEvent(node.parent, mouseoverParent, mouseoutParent, eventType, evt);
} }
} }
handle(this);
}
}; };
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
@ -882,9 +901,11 @@ Kinetic.Stage = function(cont, width, height) {
y: 1 y: 1
}; };
this.dblClickWindow = 400; this.dblClickWindow = 400;
this.targetShape = undefined;
this.clickStart = false; this.clickStart = false;
this.targetShape = undefined;
this.targetFound = false; this.targetFound = false;
this.mouseoverShape = undefined;
this.mouseoutShape = undefined;
// desktop flags // desktop flags
this.mousePos = undefined; this.mousePos = undefined;
@ -1202,9 +1223,22 @@ Kinetic.Stage.prototype = {
* NOTE: these event handlers require target shape * NOTE: these event handlers require target shape
* handling * handling
*/ */
else if(!isDragging && this._isNewTarget(shape, evt)) {
// handle onmouseover // handle onmouseover
else if(!isDragging && this._isNewTarget(shape, evt)) {
/*
* check to see if there are stored mouseout events first.
* if there are, run those before running the onmouseover
* events
*/
if(this.mouseoutShape) {
this.mouseoverShape = shape;
this.mouseoutShape._handleEvents('onmouseout', evt);
this.mouseoverShape = undefined;
}
shape._handleEvents('onmouseover', evt); shape._handleEvents('onmouseover', evt);
this._setTarget(shape);
return true; return true;
} }
@ -1217,13 +1251,24 @@ Kinetic.Stage.prototype = {
} }
// handle mouseout condition // handle mouseout condition
else if(!isDragging && this.targetShape && this.targetShape.id === shape.id) { else if(!isDragging && this.targetShape && this.targetShape.id === shape.id) {
this.targetShape = undefined; this._setTarget(undefined);
shape._handleEvents('onmouseout', evt); this.mouseoutShape = shape;
//shape._handleEvents('onmouseout', evt);
return true; return true;
} }
return false; return false;
}, },
/**
* set new target
*/
_setTarget: function(shape) {
this.targetShape = shape;
this.targetFound = true;
},
/**
* check if shape should be a new target
*/
_isNewTarget: function(shape, evt) { _isNewTarget: function(shape, evt) {
if(!this.targetShape || (!this.targetFound && shape.id !== this.targetShape.id)) { if(!this.targetShape || (!this.targetFound && shape.id !== this.targetShape.id)) {
/* /*
@ -1232,14 +1277,10 @@ Kinetic.Stage.prototype = {
if(this.targetShape) { if(this.targetShape) {
var oldEl = this.targetShape.eventListeners; var oldEl = this.targetShape.eventListeners;
if(oldEl) { if(oldEl) {
this.targetShape._handleEvents('onmouseout', evt); this.mouseoutShape = this.targetShape;
//this.targetShape._handleEvents('onmouseout', evt);
} }
} }
// set new target shape
this.targetShape = shape;
this.targetFound = true;
return true; return true;
} }
else { else {
@ -1293,15 +1334,26 @@ Kinetic.Stage.prototype = {
* three nested loops * three nested loops
*/ */
this.targetFound = false; this.targetFound = false;
var shapeDetected = false;
for(var n = this.children.length - 1; n >= 0; n--) { for(var n = this.children.length - 1; n >= 0; n--) {
var layer = this.children[n]; var layer = this.children[n];
if(layer.visible && n >= 0 && layer.isListening) { if(layer.visible && n >= 0 && layer.isListening) {
if(this._traverseChildren(layer, evt)) { if(this._traverseChildren(layer, evt)) {
n = -1; n = -1;
shapeDetected = true;
} }
} }
} }
/*
* if no shape was detected and a mouseout shape has been stored,
* then run the onmouseout event handlers
*/
if(!shapeDetected && this.mouseoutShape) {
this.mouseoutShape._handleEvents('onmouseout', evt);
this.mouseoutShape = undefined;
}
}, },
/** /**
* begin listening for events by adding event handlers * begin listening for events by adding event handlers

File diff suppressed because one or more lines are too long

View File

@ -531,21 +531,40 @@ Kinetic.Node.prototype = {
* @param {Event} evt * @param {Event} evt
*/ */
_handleEvents: function(eventType, evt) { _handleEvents: function(eventType, evt) {
// generic events handler var stage = this.getStage();
function handle(obj) { this._handleEvent(this, stage.mouseoverShape, stage.mouseoutShape, eventType, evt);
var el = obj.eventListeners; },
if(el[eventType]) { /**
* handle node event
*/
_handleEvent: function(node, mouseoverNode, mouseoutNode, eventType, evt) {
var el = node.eventListeners;
var okayToRun = true;
/*
* determine if event handler should be skipped by comparing
* parent nodes
*/
if(eventType === 'onmouseover' && mouseoutNode && mouseoutNode.id === node.id) {
okayToRun = false;
}
else if(eventType === 'onmouseout' && mouseoverNode && mouseoverNode.id === node.id) {
okayToRun = false;
}
if(el[eventType] && okayToRun) {
var events = el[eventType]; var events = el[eventType];
for(var i = 0; i < events.length; i++) { for(var i = 0; i < events.length; i++) {
events[i].handler.apply(obj, [evt]); events[i].handler.apply(node, [evt]);
} }
} }
var mouseoverParent = mouseoverNode ? mouseoverNode.parent : undefined;
var mouseoutParent = mouseoutNode ? mouseoutNode.parent : undefined;
// simulate event bubbling // simulate event bubbling
if(!evt.cancelBubble && obj.parent.className !== 'Stage') { if(!evt.cancelBubble && node.parent.className !== 'Stage') {
handle(obj.parent); this._handleEvent(node.parent, mouseoverParent, mouseoutParent, eventType, evt);
} }
} }
handle(this);
}
}; };

View File

@ -21,9 +21,11 @@ Kinetic.Stage = function(cont, width, height) {
y: 1 y: 1
}; };
this.dblClickWindow = 400; this.dblClickWindow = 400;
this.targetShape = undefined;
this.clickStart = false; this.clickStart = false;
this.targetShape = undefined;
this.targetFound = false; this.targetFound = false;
this.mouseoverShape = undefined;
this.mouseoutShape = undefined;
// desktop flags // desktop flags
this.mousePos = undefined; this.mousePos = undefined;
@ -341,9 +343,22 @@ Kinetic.Stage.prototype = {
* NOTE: these event handlers require target shape * NOTE: these event handlers require target shape
* handling * handling
*/ */
else if(!isDragging && this._isNewTarget(shape, evt)) {
// handle onmouseover // handle onmouseover
else if(!isDragging && this._isNewTarget(shape, evt)) {
/*
* check to see if there are stored mouseout events first.
* if there are, run those before running the onmouseover
* events
*/
if(this.mouseoutShape) {
this.mouseoverShape = shape;
this.mouseoutShape._handleEvents('onmouseout', evt);
this.mouseoverShape = undefined;
}
shape._handleEvents('onmouseover', evt); shape._handleEvents('onmouseover', evt);
this._setTarget(shape);
return true; return true;
} }
@ -356,13 +371,24 @@ Kinetic.Stage.prototype = {
} }
// handle mouseout condition // handle mouseout condition
else if(!isDragging && this.targetShape && this.targetShape.id === shape.id) { else if(!isDragging && this.targetShape && this.targetShape.id === shape.id) {
this.targetShape = undefined; this._setTarget(undefined);
shape._handleEvents('onmouseout', evt); this.mouseoutShape = shape;
//shape._handleEvents('onmouseout', evt);
return true; return true;
} }
return false; return false;
}, },
/**
* set new target
*/
_setTarget: function(shape) {
this.targetShape = shape;
this.targetFound = true;
},
/**
* check if shape should be a new target
*/
_isNewTarget: function(shape, evt) { _isNewTarget: function(shape, evt) {
if(!this.targetShape || (!this.targetFound && shape.id !== this.targetShape.id)) { if(!this.targetShape || (!this.targetFound && shape.id !== this.targetShape.id)) {
/* /*
@ -371,14 +397,10 @@ Kinetic.Stage.prototype = {
if(this.targetShape) { if(this.targetShape) {
var oldEl = this.targetShape.eventListeners; var oldEl = this.targetShape.eventListeners;
if(oldEl) { if(oldEl) {
this.targetShape._handleEvents('onmouseout', evt); this.mouseoutShape = this.targetShape;
//this.targetShape._handleEvents('onmouseout', evt);
} }
} }
// set new target shape
this.targetShape = shape;
this.targetFound = true;
return true; return true;
} }
else { else {
@ -432,15 +454,26 @@ Kinetic.Stage.prototype = {
* three nested loops * three nested loops
*/ */
this.targetFound = false; this.targetFound = false;
var shapeDetected = false;
for(var n = this.children.length - 1; n >= 0; n--) { for(var n = this.children.length - 1; n >= 0; n--) {
var layer = this.children[n]; var layer = this.children[n];
if(layer.visible && n >= 0 && layer.isListening) { if(layer.visible && n >= 0 && layer.isListening) {
if(this._traverseChildren(layer, evt)) { if(this._traverseChildren(layer, evt)) {
n = -1; n = -1;
shapeDetected = true;
} }
} }
} }
/*
* if no shape was detected and a mouseout shape has been stored,
* then run the onmouseout event handlers
*/
if(!shapeDetected && this.mouseoutShape) {
this.mouseoutShape._handleEvents('onmouseout', evt);
this.mouseoutShape = undefined;
}
}, },
/** /**
* begin listening for events by adding event handlers * begin listening for events by adding event handlers

View File

@ -458,28 +458,54 @@ Test.prototype.tests = {
stage.add(layer); stage.add(layer);
}, },
'EVENTS - event bubbling': function(containerId) { 'EVENTS - group click events': function(containerId) {
var stage = new Kinetic.Stage(containerId, 578, 200); var stage = new Kinetic.Stage(containerId, 578, 200);
var layer = new Kinetic.Layer(); var layer = new Kinetic.Layer();
var group = new Kinetic.Group(); var group = new Kinetic.Group();
layer.on('mouseover', function() {
log('mouseover layer');
//console.log(this); group.on('click', function() {
}); log('click group');
layer.on('mouseout', function() {
log('mouseout layer');
//console.log(this); //console.log(this);
}); });
group.on('mouseover', function() { var redCircle = new Kinetic.Circle({
log('mouseover group'); x: stage.width / 2,
//console.log(this); y: stage.height / 2,
}); radius: 80,
group.on('mouseout', function() { strokeWidth: 4,
log('mouseout group'); fill: 'red',
stroke: 'black',
name: 'red'
});
var greenCircle = new Kinetic.Circle({
x: stage.width / 2,
y: stage.height / 2,
radius: 40,
strokeWidth: 4,
fill: 'green',
stroke: 'black',
name: 'green'
});
group.add(redCircle);
group.add(greenCircle);
layer.add(group);
stage.add(layer);
},
'EVENTS - group mousemove events': function(containerId) {
var stage = new Kinetic.Stage(containerId, 578, 200);
var layer = new Kinetic.Layer();
var group = new Kinetic.Group();
group.on('mousemove', function() {
log('mousemove group');
//console.log(this); //console.log(this);
}); });
var redCircle = new Kinetic.Circle({ var redCircle = new Kinetic.Circle({
x: stage.width / 2, x: stage.width / 2,
y: stage.height / 2, y: stage.height / 2,
@ -489,14 +515,6 @@ Test.prototype.tests = {
stroke: 'black' stroke: 'black'
}); });
redCircle.on('mouseover', function() {
log('mouseover red circle');
//console.log(this);
});
redCircle.on('mouseout', function() {
log('mouseout red circle');
//console.log(this);
});
var greenCircle = new Kinetic.Circle({ var greenCircle = new Kinetic.Circle({
x: stage.width / 2, x: stage.width / 2,
y: stage.height / 2, y: stage.height / 2,
@ -506,13 +524,45 @@ Test.prototype.tests = {
stroke: 'black' stroke: 'black'
}); });
greenCircle.on('mouseover', function() { group.add(redCircle);
log('mouseover green circle'); group.add(greenCircle);
//console.log(this);
layer.add(group);
stage.add(layer);
},
'EVENTS - group mouseover events': function(containerId) {
var stage = new Kinetic.Stage(containerId, 578, 200);
var layer = new Kinetic.Layer();
var group = new Kinetic.Group({
name: 'group'
}); });
greenCircle.on('mouseout', function() {
log('mouseout green circle'); group.on('mouseover', function() {
//console.log(this); log('mouseover group');
});
group.on('mouseout', function() {
log('mouseout group');
});
var redCircle = new Kinetic.Circle({
x: stage.width / 2,
y: stage.height / 2,
radius: 80,
strokeWidth: 4,
fill: 'red',
stroke: 'black',
name: 'red'
});
var greenCircle = new Kinetic.Circle({
x: stage.width / 2,
y: stage.height / 2,
radius: 40,
strokeWidth: 4,
fill: 'green',
stroke: 'black',
name: 'green'
}); });
group.add(redCircle); group.add(redCircle);