mirror of
https://github.com/konvajs/konva.git
synced 2025-06-28 15:23:44 +08:00
better mulitouch
This commit is contained in:
parent
b0a45caee4
commit
1d932bf76c
191
konva.js
191
konva.js
@ -85,6 +85,30 @@
|
|||||||
},
|
},
|
||||||
enableTrace: false,
|
enableTrace: false,
|
||||||
_pointerEventsEnabled: false,
|
_pointerEventsEnabled: false,
|
||||||
|
/**
|
||||||
|
* Should we enable hit detection while dragging? For performance reasons, by default it is false.
|
||||||
|
* But on some rare cases you want to see hit graph and check intersections. Just set it to true.
|
||||||
|
* @property hitOnDragEnabled
|
||||||
|
* @default false
|
||||||
|
* @name hitOnDragEnabled
|
||||||
|
* @memberof Konva
|
||||||
|
* @example
|
||||||
|
* Konva.hitOnDragEnabled = true;
|
||||||
|
*/
|
||||||
|
hitOnDragEnabled: false,
|
||||||
|
/**
|
||||||
|
* Should we capture touch events and bind them to the touchstart target? That is how it works on DOM elements.
|
||||||
|
* The case: we touchstart on div1, then touchmove out of that element into another element div2.
|
||||||
|
* DOM will continue trigger touchmove events on div1 (not div2). Because events are "captured" into initial target.
|
||||||
|
* By default Konva do not do that and will trigger touchmove on another element, while pointer is moving.
|
||||||
|
* @property captureTouchEventsEnabled
|
||||||
|
* @default false
|
||||||
|
* @name captureTouchEventsEnabled
|
||||||
|
* @memberof Konva
|
||||||
|
* @example
|
||||||
|
* Konva.captureTouchEventsEnabled = true;
|
||||||
|
*/
|
||||||
|
captureTouchEventsEnabled: false,
|
||||||
// TODO: move that to stage?
|
// TODO: move that to stage?
|
||||||
listenClickTap: false,
|
listenClickTap: false,
|
||||||
inDblClickWindow: false,
|
inDblClickWindow: false,
|
||||||
@ -2319,6 +2343,8 @@
|
|||||||
y: 0
|
y: 0
|
||||||
},
|
},
|
||||||
node: null,
|
node: null,
|
||||||
|
_nodes: [],
|
||||||
|
_offsets: [],
|
||||||
// methods
|
// methods
|
||||||
_drag: function (evt) {
|
_drag: function (evt) {
|
||||||
var node = DD.node;
|
var node = DD.node;
|
||||||
@ -2483,6 +2509,7 @@
|
|||||||
this._filterUpToDate = false;
|
this._filterUpToDate = false;
|
||||||
this._isUnderCache = false;
|
this._isUnderCache = false;
|
||||||
this.children = emptyChildren;
|
this.children = emptyChildren;
|
||||||
|
this._dragEventId = null;
|
||||||
this.setAttrs(config);
|
this.setAttrs(config);
|
||||||
// event bindings for cache handling
|
// event bindings for cache handling
|
||||||
this.on(TRANSFORM_CHANGE_STR, function () {
|
this.on(TRANSFORM_CHANGE_STR, function () {
|
||||||
@ -3860,8 +3887,8 @@
|
|||||||
config = config || {};
|
config = config || {};
|
||||||
var box = this.getClientRect();
|
var box = this.getClientRect();
|
||||||
var stage = this.getStage(), x = config.x !== undefined ? config.x : box.x, y = config.y !== undefined ? config.y : box.y, pixelRatio = config.pixelRatio || 1, canvas = new SceneCanvas({
|
var stage = this.getStage(), x = config.x !== undefined ? config.x : box.x, y = config.y !== undefined ? config.y : box.y, pixelRatio = config.pixelRatio || 1, canvas = new SceneCanvas({
|
||||||
width: config.width || box.width || (stage ? stage.getWidth() : 0),
|
width: config.width || box.width || (stage ? stage.width() : 0),
|
||||||
height: config.height || box.height || (stage ? stage.getHeight() : 0),
|
height: config.height || box.height || (stage ? stage.height() : 0),
|
||||||
pixelRatio: pixelRatio
|
pixelRatio: pixelRatio
|
||||||
}), context = canvas.getContext();
|
}), context = canvas.getContext();
|
||||||
context.save();
|
context.save();
|
||||||
@ -4241,7 +4268,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Node.prototype._setDragPosition = function (evt) {
|
Node.prototype._setDragPosition = function (evt) {
|
||||||
var pos = this.getStage().getPointerPosition(), dbf = this.dragBoundFunc();
|
// const pointers = this.getStage().getPointersPositions();
|
||||||
|
// const pos = pointers.find(p => p.id === this._dragEventId);
|
||||||
|
var pos = this.getStage().getPointerPosition();
|
||||||
|
var dbf = this.dragBoundFunc();
|
||||||
if (!pos) {
|
if (!pos) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -5274,7 +5304,9 @@
|
|||||||
};
|
};
|
||||||
Container.prototype.shouldDrawHit = function (canvas) {
|
Container.prototype.shouldDrawHit = function (canvas) {
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
var layerUnderDrag = DD.isDragging && DD.anim.getLayers().indexOf(layer) !== -1;
|
var layerUnderDrag = DD.isDragging &&
|
||||||
|
!Konva.hitOnDragEnabled &&
|
||||||
|
DD.anim.getLayers().indexOf(layer) !== -1;
|
||||||
return ((canvas && canvas.isCache) ||
|
return ((canvas && canvas.isCache) ||
|
||||||
(layer && layer.hitGraphEnabled() && this.isVisible() && !layerUnderDrag));
|
(layer && layer.hitGraphEnabled() && this.isVisible() && !layerUnderDrag));
|
||||||
};
|
};
|
||||||
@ -5480,9 +5512,7 @@
|
|||||||
if (!shape)
|
if (!shape)
|
||||||
return;
|
return;
|
||||||
var stage = shape.getStage();
|
var stage = shape.getStage();
|
||||||
if (stage && stage.content) {
|
if (stage && stage.content) ;
|
||||||
stage.content.releasePointerCapture(pointerId);
|
|
||||||
}
|
|
||||||
Captures.delete(pointerId);
|
Captures.delete(pointerId);
|
||||||
shape._fire('lostpointercapture', createEvent(new PointerEvent('lostpointercapture')));
|
shape._fire('lostpointercapture', createEvent(new PointerEvent('lostpointercapture')));
|
||||||
}
|
}
|
||||||
@ -5642,10 +5672,14 @@
|
|||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
Stage.prototype.getPointerPosition = function () {
|
Stage.prototype.getPointerPosition = function () {
|
||||||
if (!this.pointerPos) {
|
var pos = this._pointerPositions[0];
|
||||||
|
if (!pos) {
|
||||||
Util.warn(NO_POINTERS_MESSAGE);
|
Util.warn(NO_POINTERS_MESSAGE);
|
||||||
}
|
}
|
||||||
return this.pointerPos;
|
return pos;
|
||||||
|
};
|
||||||
|
Stage.prototype.getPointersPositions = function () {
|
||||||
|
return this._pointerPositions;
|
||||||
};
|
};
|
||||||
Stage.prototype.getStage = function () {
|
Stage.prototype.getStage = function () {
|
||||||
return this;
|
return this;
|
||||||
@ -5797,6 +5831,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.pointerPos = undefined;
|
this.pointerPos = undefined;
|
||||||
|
this._pointerPositions = [];
|
||||||
this._fire(CONTENT_MOUSEOUT, { evt: evt });
|
this._fire(CONTENT_MOUSEOUT, { evt: evt });
|
||||||
};
|
};
|
||||||
Stage.prototype._mousemove = function (evt) {
|
Stage.prototype._mousemove = function (evt) {
|
||||||
@ -5961,18 +5996,28 @@
|
|||||||
this._fire(CONTENT_CONTEXTMENU, { evt: evt });
|
this._fire(CONTENT_CONTEXTMENU, { evt: evt });
|
||||||
};
|
};
|
||||||
Stage.prototype._touchstart = function (evt) {
|
Stage.prototype._touchstart = function (evt) {
|
||||||
|
var _this = this;
|
||||||
this.setPointersPositions(evt);
|
this.setPointersPositions(evt);
|
||||||
var shape = this.getIntersection(this.getPointerPosition());
|
var triggeredOnShape = false;
|
||||||
|
this._changedPointerPositions.forEach(function (pos) {
|
||||||
|
var shape = _this.getIntersection(pos);
|
||||||
Konva.listenClickTap = true;
|
Konva.listenClickTap = true;
|
||||||
if (shape && shape.isListening()) {
|
var hasShape = shape && shape.isListening();
|
||||||
this.tapStartShape = shape;
|
if (!hasShape) {
|
||||||
shape._fireAndBubble(TOUCHSTART, { evt: evt });
|
return;
|
||||||
|
}
|
||||||
|
if (Konva.captureTouchEventsEnabled) {
|
||||||
|
shape.setPointerCapture(pos.id);
|
||||||
|
}
|
||||||
|
_this.tapStartShape = shape;
|
||||||
|
shape._fireAndBubble(TOUCHSTART, { evt: evt }, _this);
|
||||||
|
triggeredOnShape = true;
|
||||||
// only call preventDefault if the shape is listening for events
|
// only call preventDefault if the shape is listening for events
|
||||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
else {
|
if (!triggeredOnShape) {
|
||||||
this._fire(TOUCHSTART, {
|
this._fire(TOUCHSTART, {
|
||||||
evt: evt,
|
evt: evt,
|
||||||
target: this,
|
target: this,
|
||||||
@ -5982,9 +6027,46 @@
|
|||||||
// content event
|
// content event
|
||||||
this._fire(CONTENT_TOUCHSTART, { evt: evt });
|
this._fire(CONTENT_TOUCHSTART, { evt: evt });
|
||||||
};
|
};
|
||||||
Stage.prototype._touchend = function (evt) {
|
Stage.prototype._touchmove = function (evt) {
|
||||||
|
var _this = this;
|
||||||
this.setPointersPositions(evt);
|
this.setPointersPositions(evt);
|
||||||
var shape = this.getIntersection(this.getPointerPosition()), clickEndShape = this.clickEndShape, fireDblClick = false;
|
if (!DD.isDragging) {
|
||||||
|
var triggeredOnShape = false;
|
||||||
|
var processedShapesIds = {};
|
||||||
|
this._changedPointerPositions.forEach(function (pos) {
|
||||||
|
var shape = getCapturedShape(pos.id) || _this.getIntersection(pos);
|
||||||
|
var hasShape = shape && shape.isListening();
|
||||||
|
if (!hasShape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (processedShapesIds[shape._id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processedShapesIds[shape._id] = true;
|
||||||
|
shape._fireAndBubble(TOUCHMOVE, { evt: evt });
|
||||||
|
triggeredOnShape = true;
|
||||||
|
// only call preventDefault if the shape is listening for events
|
||||||
|
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!triggeredOnShape) {
|
||||||
|
this._fire(TOUCHMOVE, {
|
||||||
|
evt: evt,
|
||||||
|
target: this,
|
||||||
|
currentTarget: this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._fire(CONTENT_TOUCHMOVE, { evt: evt });
|
||||||
|
}
|
||||||
|
if (DD.isDragging && DD.node.preventDefault() && evt.cancelable) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Stage.prototype._touchend = function (evt) {
|
||||||
|
var _this = this;
|
||||||
|
this.setPointersPositions(evt);
|
||||||
|
var clickEndShape = this.clickEndShape, fireDblClick = false;
|
||||||
if (Konva.inDblClickWindow) {
|
if (Konva.inDblClickWindow) {
|
||||||
fireDblClick = true;
|
fireDblClick = true;
|
||||||
clearTimeout(this.dblTimeout);
|
clearTimeout(this.dblTimeout);
|
||||||
@ -5997,13 +6079,29 @@
|
|||||||
this.dblTimeout = setTimeout(function () {
|
this.dblTimeout = setTimeout(function () {
|
||||||
Konva.inDblClickWindow = false;
|
Konva.inDblClickWindow = false;
|
||||||
}, Konva.dblClickWindow);
|
}, Konva.dblClickWindow);
|
||||||
if (shape && shape.isListening()) {
|
var triggeredOnShape = false;
|
||||||
this.clickEndShape = shape;
|
var processedShapesIds = {};
|
||||||
|
this._changedPointerPositions.forEach(function (pos) {
|
||||||
|
var shape = getCapturedShape(pos.id) ||
|
||||||
|
_this.getIntersection(pos);
|
||||||
|
if (shape) {
|
||||||
|
shape.releaseCapture(pos.id);
|
||||||
|
}
|
||||||
|
var hasShape = shape && shape.isListening();
|
||||||
|
if (!hasShape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (processedShapesIds[shape._id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processedShapesIds[shape._id] = true;
|
||||||
|
_this.clickEndShape = shape;
|
||||||
shape._fireAndBubble(TOUCHEND, { evt: evt });
|
shape._fireAndBubble(TOUCHEND, { evt: evt });
|
||||||
|
triggeredOnShape = true;
|
||||||
// detect if tap or double tap occurred
|
// detect if tap or double tap occurred
|
||||||
if (Konva.listenClickTap &&
|
if (Konva.listenClickTap &&
|
||||||
this.tapStartShape &&
|
_this.tapStartShape &&
|
||||||
shape._id === this.tapStartShape._id) {
|
shape._id === _this.tapStartShape._id) {
|
||||||
shape._fireAndBubble(TAP, { evt: evt });
|
shape._fireAndBubble(TAP, { evt: evt });
|
||||||
if (fireDblClick && clickEndShape && clickEndShape === shape) {
|
if (fireDblClick && clickEndShape && clickEndShape === shape) {
|
||||||
shape._fireAndBubble(DBL_TAP, { evt: evt });
|
shape._fireAndBubble(DBL_TAP, { evt: evt });
|
||||||
@ -6013,9 +6111,10 @@
|
|||||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
else {
|
if (!triggeredOnShape) {
|
||||||
this._fire(TOUCHEND, { evt: evt, target: this, currentTarget: this });
|
this._fire(TOUCHEND, { evt: evt, target: this, currentTarget: this });
|
||||||
|
}
|
||||||
if (Konva.listenClickTap) {
|
if (Konva.listenClickTap) {
|
||||||
this._fire(TAP, { evt: evt, target: this, currentTarget: this });
|
this._fire(TAP, { evt: evt, target: this, currentTarget: this });
|
||||||
}
|
}
|
||||||
@ -6026,7 +6125,6 @@
|
|||||||
currentTarget: this
|
currentTarget: this
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// content events
|
// content events
|
||||||
this._fire(CONTENT_TOUCHEND, { evt: evt });
|
this._fire(CONTENT_TOUCHEND, { evt: evt });
|
||||||
if (Konva.listenClickTap) {
|
if (Konva.listenClickTap) {
|
||||||
@ -6037,31 +6135,6 @@
|
|||||||
}
|
}
|
||||||
Konva.listenClickTap = false;
|
Konva.listenClickTap = false;
|
||||||
};
|
};
|
||||||
Stage.prototype._touchmove = function (evt) {
|
|
||||||
this.setPointersPositions(evt);
|
|
||||||
var shape;
|
|
||||||
if (!DD.isDragging) {
|
|
||||||
shape = this.getIntersection(this.getPointerPosition());
|
|
||||||
if (shape && shape.isListening()) {
|
|
||||||
shape._fireAndBubble(TOUCHMOVE, { evt: evt });
|
|
||||||
// only call preventDefault if the shape is listening for events
|
|
||||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._fire(TOUCHMOVE, {
|
|
||||||
evt: evt,
|
|
||||||
target: this,
|
|
||||||
currentTarget: this
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._fire(CONTENT_TOUCHMOVE, { evt: evt });
|
|
||||||
}
|
|
||||||
if (DD.isDragging && DD.node.preventDefault() && evt.cancelable) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Stage.prototype._wheel = function (evt) {
|
Stage.prototype._wheel = function (evt) {
|
||||||
this.setPointersPositions(evt);
|
this.setPointersPositions(evt);
|
||||||
var shape = this.getIntersection(this.getPointerPosition());
|
var shape = this.getIntersection(this.getPointerPosition());
|
||||||
@ -6141,10 +6214,29 @@
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
Stage.prototype.setPointersPositions = function (evt) {
|
Stage.prototype.setPointersPositions = function (evt) {
|
||||||
|
var _this = this;
|
||||||
var contentPosition = this._getContentPosition(), x = null, y = null;
|
var contentPosition = this._getContentPosition(), x = null, y = null;
|
||||||
evt = evt ? evt : window.event;
|
evt = evt ? evt : window.event;
|
||||||
// touch events
|
// touch events
|
||||||
if (evt.touches !== undefined) {
|
if (evt.touches !== undefined) {
|
||||||
|
// touchlist has not support for map method
|
||||||
|
// so we have to iterate
|
||||||
|
this._pointerPositions = [];
|
||||||
|
this._changedPointerPositions = [];
|
||||||
|
Collection.prototype.each.call(evt.touches, function (touch) {
|
||||||
|
_this._pointerPositions.push({
|
||||||
|
id: touch.identifier,
|
||||||
|
x: touch.clientX - contentPosition.left,
|
||||||
|
y: touch.clientY - contentPosition.top
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Collection.prototype.each.call(evt.changedTouches || evt.touches, function (touch) {
|
||||||
|
_this._changedPointerPositions.push({
|
||||||
|
id: touch.identifier,
|
||||||
|
x: touch.clientX - contentPosition.left,
|
||||||
|
y: touch.clientY - contentPosition.top
|
||||||
|
});
|
||||||
|
});
|
||||||
// currently, only handle one finger
|
// currently, only handle one finger
|
||||||
if (evt.touches.length > 0) {
|
if (evt.touches.length > 0) {
|
||||||
var touch = evt.touches[0];
|
var touch = evt.touches[0];
|
||||||
@ -6157,6 +6249,7 @@
|
|||||||
// mouse events
|
// mouse events
|
||||||
x = evt.clientX - contentPosition.left;
|
x = evt.clientX - contentPosition.left;
|
||||||
y = evt.clientY - contentPosition.top;
|
y = evt.clientY - contentPosition.top;
|
||||||
|
this._pointerPositions = [{ x: x, y: y }];
|
||||||
}
|
}
|
||||||
if (x !== null && y !== null) {
|
if (x !== null && y !== null) {
|
||||||
this.pointerPos = {
|
this.pointerPos = {
|
||||||
|
4
konva.min.js
vendored
4
konva.min.js
vendored
File diff suppressed because one or more lines are too long
@ -3,6 +3,7 @@ import { Factory } from './Factory';
|
|||||||
import { Node, NodeConfig } from './Node';
|
import { Node, NodeConfig } from './Node';
|
||||||
import { DD } from './DragAndDrop';
|
import { DD } from './DragAndDrop';
|
||||||
import { getNumberValidator } from './Validators';
|
import { getNumberValidator } from './Validators';
|
||||||
|
import { Konva } from './Global';
|
||||||
|
|
||||||
import { GetSet, IRect } from './types';
|
import { GetSet, IRect } from './types';
|
||||||
import { Shape } from './Shape';
|
import { Shape } from './Shape';
|
||||||
@ -444,7 +445,9 @@ export abstract class Container<ChildType extends Node> extends Node<
|
|||||||
shouldDrawHit(canvas?) {
|
shouldDrawHit(canvas?) {
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
var layerUnderDrag =
|
var layerUnderDrag =
|
||||||
DD.isDragging && DD.anim.getLayers().indexOf(layer) !== -1;
|
DD.isDragging &&
|
||||||
|
!Konva.hitOnDragEnabled &&
|
||||||
|
DD.anim.getLayers().indexOf(layer) !== -1;
|
||||||
return (
|
return (
|
||||||
(canvas && canvas.isCache) ||
|
(canvas && canvas.isCache) ||
|
||||||
(layer && layer.hitGraphEnabled() && this.isVisible() && !layerUnderDrag)
|
(layer && layer.hitGraphEnabled() && this.isVisible() && !layerUnderDrag)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Animation } from './Animation';
|
import { Animation } from './Animation';
|
||||||
import { Konva } from './Global';
|
import { Konva } from './Global';
|
||||||
|
import { Node } from './Node';
|
||||||
|
|
||||||
// TODO: make better module,
|
// TODO: make better module,
|
||||||
// make sure other modules import it without global
|
// make sure other modules import it without global
|
||||||
@ -21,6 +22,8 @@ export const DD = {
|
|||||||
y: 0
|
y: 0
|
||||||
},
|
},
|
||||||
node: null,
|
node: null,
|
||||||
|
_nodes: [],
|
||||||
|
_offsets: [],
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
_drag(evt) {
|
_drag(evt) {
|
||||||
|
@ -97,6 +97,30 @@ export const Konva = {
|
|||||||
},
|
},
|
||||||
enableTrace: false,
|
enableTrace: false,
|
||||||
_pointerEventsEnabled: false,
|
_pointerEventsEnabled: false,
|
||||||
|
/**
|
||||||
|
* Should we enable hit detection while dragging? For performance reasons, by default it is false.
|
||||||
|
* But on some rare cases you want to see hit graph and check intersections. Just set it to true.
|
||||||
|
* @property hitOnDragEnabled
|
||||||
|
* @default false
|
||||||
|
* @name hitOnDragEnabled
|
||||||
|
* @memberof Konva
|
||||||
|
* @example
|
||||||
|
* Konva.hitOnDragEnabled = true;
|
||||||
|
*/
|
||||||
|
hitOnDragEnabled: false,
|
||||||
|
/**
|
||||||
|
* Should we capture touch events and bind them to the touchstart target? That is how it works on DOM elements.
|
||||||
|
* The case: we touchstart on div1, then touchmove out of that element into another element div2.
|
||||||
|
* DOM will continue trigger touchmove events on div1 (not div2). Because events are "captured" into initial target.
|
||||||
|
* By default Konva do not do that and will trigger touchmove on another element, while pointer is moving.
|
||||||
|
* @property captureTouchEventsEnabled
|
||||||
|
* @default false
|
||||||
|
* @name captureTouchEventsEnabled
|
||||||
|
* @memberof Konva
|
||||||
|
* @example
|
||||||
|
* Konva.captureTouchEventsEnabled = true;
|
||||||
|
*/
|
||||||
|
captureTouchEventsEnabled: false,
|
||||||
|
|
||||||
// TODO: move that to stage?
|
// TODO: move that to stage?
|
||||||
listenClickTap: false,
|
listenClickTap: false,
|
||||||
|
14
src/Node.ts
14
src/Node.ts
@ -205,6 +205,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
children = emptyChildren;
|
children = emptyChildren;
|
||||||
nodeType!: string;
|
nodeType!: string;
|
||||||
className!: string;
|
className!: string;
|
||||||
|
_dragEventId: number | null = null;
|
||||||
|
|
||||||
constructor(config?: Config) {
|
constructor(config?: Config) {
|
||||||
this.setAttrs(config);
|
this.setAttrs(config);
|
||||||
@ -1575,7 +1576,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
* @name Konva.Node#getStage
|
* @name Konva.Node#getStage
|
||||||
* @returns {Konva.Stage}
|
* @returns {Konva.Stage}
|
||||||
*/
|
*/
|
||||||
getStage(): any {
|
getStage(): Stage | null {
|
||||||
return this._getCache(STAGE, this._getStage);
|
return this._getCache(STAGE, this._getStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1805,8 +1806,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
y = config.y !== undefined ? config.y : box.y,
|
y = config.y !== undefined ? config.y : box.y,
|
||||||
pixelRatio = config.pixelRatio || 1,
|
pixelRatio = config.pixelRatio || 1,
|
||||||
canvas = new SceneCanvas({
|
canvas = new SceneCanvas({
|
||||||
width: config.width || box.width || (stage ? stage.getWidth() : 0),
|
width: config.width || box.width || (stage ? stage.width() : 0),
|
||||||
height: config.height || box.height || (stage ? stage.getHeight() : 0),
|
height: config.height || box.height || (stage ? stage.height() : 0),
|
||||||
pixelRatio: pixelRatio
|
pixelRatio: pixelRatio
|
||||||
}),
|
}),
|
||||||
context = canvas.getContext();
|
context = canvas.getContext();
|
||||||
@ -2241,8 +2242,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setDragPosition(evt?) {
|
_setDragPosition(evt?) {
|
||||||
var pos = this.getStage().getPointerPosition(),
|
// const pointers = this.getStage().getPointersPositions();
|
||||||
dbf = this.dragBoundFunc();
|
// const pos = pointers.find(p => p.id === this._dragEventId);
|
||||||
|
const pos = this.getStage().getPointerPosition();
|
||||||
|
|
||||||
|
var dbf = this.dragBoundFunc();
|
||||||
if (!pos) {
|
if (!pos) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ export function releaseCapture(pointerId: number, target?: Shape | Stage) {
|
|||||||
const stage = shape.getStage();
|
const stage = shape.getStage();
|
||||||
|
|
||||||
if (stage && stage.content) {
|
if (stage && stage.content) {
|
||||||
stage.content.releasePointerCapture(pointerId);
|
// stage.content.releasePointerCapture(pointerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Captures.delete(pointerId);
|
Captures.delete(pointerId);
|
||||||
|
158
src/Stage.ts
158
src/Stage.ts
@ -127,6 +127,9 @@ function checkNoClip(attrs: any = {}) {
|
|||||||
export class Stage extends Container<BaseLayer> {
|
export class Stage extends Container<BaseLayer> {
|
||||||
content: HTMLDivElement;
|
content: HTMLDivElement;
|
||||||
pointerPos: Vector2d | null;
|
pointerPos: Vector2d | null;
|
||||||
|
_pointerPositions: (Vector2d & { id?: number })[];
|
||||||
|
_changedPointerPositions: (Vector2d & { id?: number })[];
|
||||||
|
|
||||||
bufferCanvas: SceneCanvas;
|
bufferCanvas: SceneCanvas;
|
||||||
bufferHitCanvas: HitCanvas;
|
bufferHitCanvas: HitCanvas;
|
||||||
targetShape: Shape;
|
targetShape: Shape;
|
||||||
@ -244,10 +247,14 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
getPointerPosition() {
|
getPointerPosition() {
|
||||||
if (!this.pointerPos) {
|
const pos = this._pointerPositions[0];
|
||||||
|
if (!pos) {
|
||||||
Util.warn(NO_POINTERS_MESSAGE);
|
Util.warn(NO_POINTERS_MESSAGE);
|
||||||
}
|
}
|
||||||
return this.pointerPos;
|
return pos;
|
||||||
|
}
|
||||||
|
getPointersPositions() {
|
||||||
|
return this._pointerPositions;
|
||||||
}
|
}
|
||||||
getStage() {
|
getStage() {
|
||||||
return this;
|
return this;
|
||||||
@ -437,6 +444,7 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.pointerPos = undefined;
|
this.pointerPos = undefined;
|
||||||
|
this._pointerPositions = [];
|
||||||
|
|
||||||
this._fire(CONTENT_MOUSEOUT, { evt: evt });
|
this._fire(CONTENT_MOUSEOUT, { evt: evt });
|
||||||
}
|
}
|
||||||
@ -617,32 +625,83 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
}
|
}
|
||||||
_touchstart(evt) {
|
_touchstart(evt) {
|
||||||
this.setPointersPositions(evt);
|
this.setPointersPositions(evt);
|
||||||
var shape = this.getIntersection(this.getPointerPosition());
|
var triggeredOnShape = false;
|
||||||
|
this._changedPointerPositions.forEach(pos => {
|
||||||
|
var shape = this.getIntersection(pos);
|
||||||
Konva.listenClickTap = true;
|
Konva.listenClickTap = true;
|
||||||
|
const hasShape = shape && shape.isListening();
|
||||||
|
|
||||||
|
if (!hasShape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Konva.captureTouchEventsEnabled) {
|
||||||
|
shape.setPointerCapture(pos.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (shape && shape.isListening()) {
|
|
||||||
this.tapStartShape = shape;
|
this.tapStartShape = shape;
|
||||||
shape._fireAndBubble(TOUCHSTART, { evt: evt });
|
shape._fireAndBubble(TOUCHSTART, { evt: evt }, this);
|
||||||
|
triggeredOnShape = true;
|
||||||
// only call preventDefault if the shape is listening for events
|
// only call preventDefault if the shape is listening for events
|
||||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
|
|
||||||
|
if (!triggeredOnShape) {
|
||||||
this._fire(TOUCHSTART, {
|
this._fire(TOUCHSTART, {
|
||||||
evt: evt,
|
evt: evt,
|
||||||
target: this,
|
target: this,
|
||||||
currentTarget: this
|
currentTarget: this
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// content event
|
// content event
|
||||||
this._fire(CONTENT_TOUCHSTART, { evt: evt });
|
this._fire(CONTENT_TOUCHSTART, { evt: evt });
|
||||||
}
|
}
|
||||||
|
_touchmove(evt) {
|
||||||
|
this.setPointersPositions(evt);
|
||||||
|
if (!DD.isDragging) {
|
||||||
|
var triggeredOnShape = false;
|
||||||
|
var processedShapesIds = {};
|
||||||
|
this._changedPointerPositions.forEach(pos => {
|
||||||
|
const shape =
|
||||||
|
PointerEvents.getCapturedShape(pos.id) || this.getIntersection(pos);
|
||||||
|
|
||||||
|
const hasShape = shape && shape.isListening();
|
||||||
|
if (!hasShape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (processedShapesIds[shape._id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processedShapesIds[shape._id] = true;
|
||||||
|
shape._fireAndBubble(TOUCHMOVE, { evt: evt });
|
||||||
|
triggeredOnShape = true;
|
||||||
|
// only call preventDefault if the shape is listening for events
|
||||||
|
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!triggeredOnShape) {
|
||||||
|
this._fire(TOUCHMOVE, {
|
||||||
|
evt: evt,
|
||||||
|
target: this,
|
||||||
|
currentTarget: this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fire(CONTENT_TOUCHMOVE, { evt: evt });
|
||||||
|
}
|
||||||
|
if (DD.isDragging && DD.node.preventDefault() && evt.cancelable) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
_touchend(evt) {
|
_touchend(evt) {
|
||||||
this.setPointersPositions(evt);
|
this.setPointersPositions(evt);
|
||||||
var shape = this.getIntersection(this.getPointerPosition()),
|
|
||||||
clickEndShape = this.clickEndShape,
|
var clickEndShape = this.clickEndShape,
|
||||||
fireDblClick = false;
|
fireDblClick = false;
|
||||||
|
|
||||||
if (Konva.inDblClickWindow) {
|
if (Konva.inDblClickWindow) {
|
||||||
@ -658,9 +717,29 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
Konva.inDblClickWindow = false;
|
Konva.inDblClickWindow = false;
|
||||||
}, Konva.dblClickWindow);
|
}, Konva.dblClickWindow);
|
||||||
|
|
||||||
if (shape && shape.isListening()) {
|
var triggeredOnShape = false;
|
||||||
|
var processedShapesIds = {};
|
||||||
|
this._changedPointerPositions.forEach(pos => {
|
||||||
|
var shape =
|
||||||
|
(PointerEvents.getCapturedShape(pos.id) as Shape) ||
|
||||||
|
this.getIntersection(pos);
|
||||||
|
|
||||||
|
if (shape) {
|
||||||
|
shape.releaseCapture(pos.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasShape = shape && shape.isListening();
|
||||||
|
if (!hasShape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (processedShapesIds[shape._id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processedShapesIds[shape._id] = true;
|
||||||
|
|
||||||
this.clickEndShape = shape;
|
this.clickEndShape = shape;
|
||||||
shape._fireAndBubble(TOUCHEND, { evt: evt });
|
shape._fireAndBubble(TOUCHEND, { evt: evt });
|
||||||
|
triggeredOnShape = true;
|
||||||
|
|
||||||
// detect if tap or double tap occurred
|
// detect if tap or double tap occurred
|
||||||
if (
|
if (
|
||||||
@ -674,12 +753,17 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
shape._fireAndBubble(DBL_TAP, { evt: evt });
|
shape._fireAndBubble(DBL_TAP, { evt: evt });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only call preventDefault if the shape is listening for events
|
// only call preventDefault if the shape is listening for events
|
||||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
|
|
||||||
|
if (!triggeredOnShape) {
|
||||||
this._fire(TOUCHEND, { evt: evt, target: this, currentTarget: this });
|
this._fire(TOUCHEND, { evt: evt, target: this, currentTarget: this });
|
||||||
|
}
|
||||||
|
|
||||||
if (Konva.listenClickTap) {
|
if (Konva.listenClickTap) {
|
||||||
this._fire(TAP, { evt: evt, target: this, currentTarget: this });
|
this._fire(TAP, { evt: evt, target: this, currentTarget: this });
|
||||||
}
|
}
|
||||||
@ -690,7 +774,6 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
currentTarget: this
|
currentTarget: this
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// content events
|
// content events
|
||||||
this._fire(CONTENT_TOUCHEND, { evt: evt });
|
this._fire(CONTENT_TOUCHEND, { evt: evt });
|
||||||
if (Konva.listenClickTap) {
|
if (Konva.listenClickTap) {
|
||||||
@ -702,30 +785,7 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
|
|
||||||
Konva.listenClickTap = false;
|
Konva.listenClickTap = false;
|
||||||
}
|
}
|
||||||
_touchmove(evt) {
|
|
||||||
this.setPointersPositions(evt);
|
|
||||||
var shape;
|
|
||||||
if (!DD.isDragging) {
|
|
||||||
shape = this.getIntersection(this.getPointerPosition());
|
|
||||||
if (shape && shape.isListening()) {
|
|
||||||
shape._fireAndBubble(TOUCHMOVE, { evt: evt });
|
|
||||||
// only call preventDefault if the shape is listening for events
|
|
||||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._fire(TOUCHMOVE, {
|
|
||||||
evt: evt,
|
|
||||||
target: this,
|
|
||||||
currentTarget: this
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._fire(CONTENT_TOUCHMOVE, { evt: evt });
|
|
||||||
}
|
|
||||||
if (DD.isDragging && DD.node.preventDefault() && evt.cancelable) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_wheel(evt) {
|
_wheel(evt) {
|
||||||
this.setPointersPositions(evt);
|
this.setPointersPositions(evt);
|
||||||
var shape = this.getIntersection(this.getPointerPosition());
|
var shape = this.getIntersection(this.getPointerPosition());
|
||||||
@ -830,6 +890,29 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
|
|
||||||
// touch events
|
// touch events
|
||||||
if (evt.touches !== undefined) {
|
if (evt.touches !== undefined) {
|
||||||
|
// touchlist has not support for map method
|
||||||
|
// so we have to iterate
|
||||||
|
this._pointerPositions = [];
|
||||||
|
this._changedPointerPositions = [];
|
||||||
|
Collection.prototype.each.call(evt.touches, (touch: any) => {
|
||||||
|
this._pointerPositions.push({
|
||||||
|
id: touch.identifier,
|
||||||
|
x: touch.clientX - contentPosition.left,
|
||||||
|
y: touch.clientY - contentPosition.top
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Collection.prototype.each.call(
|
||||||
|
evt.changedTouches || evt.touches,
|
||||||
|
(touch: any) => {
|
||||||
|
this._changedPointerPositions.push({
|
||||||
|
id: touch.identifier,
|
||||||
|
x: touch.clientX - contentPosition.left,
|
||||||
|
y: touch.clientY - contentPosition.top
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// currently, only handle one finger
|
// currently, only handle one finger
|
||||||
if (evt.touches.length > 0) {
|
if (evt.touches.length > 0) {
|
||||||
var touch = evt.touches[0];
|
var touch = evt.touches[0];
|
||||||
@ -841,6 +924,7 @@ export class Stage extends Container<BaseLayer> {
|
|||||||
// mouse events
|
// mouse events
|
||||||
x = evt.clientX - contentPosition.left;
|
x = evt.clientX - contentPosition.left;
|
||||||
y = evt.clientY - contentPosition.top;
|
y = evt.clientY - contentPosition.top;
|
||||||
|
this._pointerPositions = [{ x, y }];
|
||||||
}
|
}
|
||||||
if (x !== null && y !== null) {
|
if (x !== null && y !== null) {
|
||||||
this.pointerPos = {
|
this.pointerPos = {
|
||||||
|
@ -58,7 +58,13 @@ suite('TouchEvents', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
stage._touchend({
|
stage._touchend({
|
||||||
touches: []
|
touches: [],
|
||||||
|
changedTouches: [
|
||||||
|
{
|
||||||
|
clientX: 100,
|
||||||
|
clientY: 100 + top
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(circleTouchstart, 1, 1);
|
assert.equal(circleTouchstart, 1, 1);
|
||||||
@ -162,6 +168,12 @@ suite('TouchEvents', function() {
|
|||||||
// touchend circle
|
// touchend circle
|
||||||
stage._touchend({
|
stage._touchend({
|
||||||
touches: [],
|
touches: [],
|
||||||
|
changedTouches: [
|
||||||
|
{
|
||||||
|
clientX: 289,
|
||||||
|
clientY: 100 + top
|
||||||
|
}
|
||||||
|
],
|
||||||
preventDefault: function() {}
|
preventDefault: function() {}
|
||||||
});
|
});
|
||||||
// end drag is tied to document mouseup and touchend event
|
// end drag is tied to document mouseup and touchend event
|
||||||
@ -194,6 +206,12 @@ suite('TouchEvents', function() {
|
|||||||
// touchend circle to triger dbltap
|
// touchend circle to triger dbltap
|
||||||
stage._touchend({
|
stage._touchend({
|
||||||
touches: [],
|
touches: [],
|
||||||
|
changedTouches: [
|
||||||
|
{
|
||||||
|
clientX: 289,
|
||||||
|
clientY: 100 + top
|
||||||
|
}
|
||||||
|
],
|
||||||
preventDefault: function() {}
|
preventDefault: function() {}
|
||||||
});
|
});
|
||||||
// end drag is tied to document mouseup and touchend event
|
// end drag is tied to document mouseup and touchend event
|
||||||
@ -351,4 +369,227 @@ suite('TouchEvents', function() {
|
|||||||
'should NOT trigger dbltap on second circle'
|
'should NOT trigger dbltap on second circle'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('multitouch - register all touches', function() {
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
var circle1 = new Konva.Circle({
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle1',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
layer.add(circle1);
|
||||||
|
|
||||||
|
var circle2 = new Konva.Circle({
|
||||||
|
x: 100,
|
||||||
|
y: 200,
|
||||||
|
radius: 80,
|
||||||
|
fill: 'red',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle2',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
layer.add(circle2);
|
||||||
|
layer.draw();
|
||||||
|
|
||||||
|
var touchStart = 0;
|
||||||
|
var touchMove = 0;
|
||||||
|
var touchEnd = 0;
|
||||||
|
var touchEnd2 = 0;
|
||||||
|
|
||||||
|
circle1.on('touchstart', function() {
|
||||||
|
touchStart++;
|
||||||
|
});
|
||||||
|
circle1.on('touchmove', function() {
|
||||||
|
touchMove++;
|
||||||
|
});
|
||||||
|
circle1.on('touchend', function() {
|
||||||
|
touchEnd++;
|
||||||
|
});
|
||||||
|
|
||||||
|
circle2.on('touchend', function() {
|
||||||
|
touchEnd2++;
|
||||||
|
});
|
||||||
|
|
||||||
|
var stageTouchStart = 0;
|
||||||
|
var stageTouchMove = 0;
|
||||||
|
var stageTouchEnd = 0;
|
||||||
|
stage.on('touchstart', function() {
|
||||||
|
stageTouchStart++;
|
||||||
|
});
|
||||||
|
stage.on('touchmove', function() {
|
||||||
|
stageTouchMove++;
|
||||||
|
});
|
||||||
|
stage.on('touchend', function() {
|
||||||
|
stageTouchEnd++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// start with one touch
|
||||||
|
stage.simulateTouchStart(
|
||||||
|
[{ x: 100, y: 100, id: 0 }],
|
||||||
|
[{ x: 100, y: 100, id: 0 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(stageTouchStart, 1, 'trigger first touch start on stage');
|
||||||
|
assert.equal(touchStart, 1, 'trigger first touch start on circle');
|
||||||
|
|
||||||
|
// make second touch
|
||||||
|
stage.simulateTouchStart(
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 210, y: 100, id: 1 }],
|
||||||
|
[{ x: 210, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
stageTouchStart,
|
||||||
|
2,
|
||||||
|
'should trigger the second touch on stage'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
touchStart,
|
||||||
|
1,
|
||||||
|
'should not trigger the second touch start (it is outside)'
|
||||||
|
);
|
||||||
|
|
||||||
|
// now try to make two touches at the same time
|
||||||
|
// TODO: should we trigger touch end first?
|
||||||
|
stage.simulateTouchStart(
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 210, y: 100, id: 1 }],
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 210, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(stageTouchStart, 3, 'should trigger one more touch');
|
||||||
|
assert.equal(
|
||||||
|
touchStart,
|
||||||
|
2,
|
||||||
|
'should trigger the second touch start on the circle'
|
||||||
|
);
|
||||||
|
|
||||||
|
// check variables
|
||||||
|
assert.deepEqual(stage.getPointerPosition(), { x: 100, y: 100, id: 0 });
|
||||||
|
assert.deepEqual(stage.getPointersPositions(), [
|
||||||
|
{ x: 100, y: 100, id: 0 },
|
||||||
|
{ x: 210, y: 100, id: 1 }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// move one finger
|
||||||
|
stage.simulateTouchMove(
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 220, y: 100, id: 1 }],
|
||||||
|
[{ x: 220, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
assert.equal(touchMove, 0, 'should not trigger touch move on circle');
|
||||||
|
assert.equal(stageTouchMove, 1, 'should trigger touch move on stage');
|
||||||
|
|
||||||
|
// move two fingers
|
||||||
|
stage.simulateTouchMove(
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 220, y: 100, id: 1 }],
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 220, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
assert.equal(touchMove, 1, 'should trigger touch move on circle');
|
||||||
|
assert.equal(
|
||||||
|
stageTouchMove,
|
||||||
|
2,
|
||||||
|
'should trigger two more touchmoves on stage'
|
||||||
|
);
|
||||||
|
|
||||||
|
stage.simulateTouchEnd(
|
||||||
|
[],
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 220, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
assert.equal(touchEnd, 1);
|
||||||
|
assert.equal(stageTouchEnd, 1);
|
||||||
|
|
||||||
|
// try two touch ends on both shapes
|
||||||
|
stage.simulateTouchEnd(
|
||||||
|
[],
|
||||||
|
[{ x: 100, y: 100, id: 0 }, { x: 100, y: 170, id: 1 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(touchEnd, 2);
|
||||||
|
assert.equal(touchEnd2, 1);
|
||||||
|
// TODO: it should be 2, not 3
|
||||||
|
assert.equal(stageTouchEnd, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can capture touch events', function() {
|
||||||
|
Konva.captureTouchEventsEnabled = true;
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
var circle1 = new Konva.Circle({
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle1'
|
||||||
|
});
|
||||||
|
layer.add(circle1);
|
||||||
|
|
||||||
|
layer.draw();
|
||||||
|
|
||||||
|
var touchStart = 0;
|
||||||
|
var touchMove = 0;
|
||||||
|
var touchEnd = 0;
|
||||||
|
|
||||||
|
circle1.on('touchstart', function(e) {
|
||||||
|
touchStart++;
|
||||||
|
});
|
||||||
|
circle1.on('touchmove', function() {
|
||||||
|
touchMove++;
|
||||||
|
});
|
||||||
|
circle1.on('touchend', function() {
|
||||||
|
touchEnd++;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.simulateTouchStart(
|
||||||
|
[{ x: 100, y: 100, id: 0 }],
|
||||||
|
[{ x: 100, y: 100, id: 0 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
// go out of circle
|
||||||
|
stage.simulateTouchMove(
|
||||||
|
[{ x: 180, y: 100, id: 0 }],
|
||||||
|
[{ x: 180, y: 100, id: 0 }]
|
||||||
|
);
|
||||||
|
assert.equal(touchMove, 1, 'first touchmove');
|
||||||
|
|
||||||
|
// add another finger
|
||||||
|
stage.simulateTouchStart(
|
||||||
|
[{ x: 180, y: 100, id: 0 }, { x: 100, y: 100, id: 1 }],
|
||||||
|
[{ x: 100, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
// move all out
|
||||||
|
stage.simulateTouchMove(
|
||||||
|
[{ x: 185, y: 100, id: 0 }, { x: 190, y: 100, id: 1 }],
|
||||||
|
[{ x: 185, y: 100, id: 0 }, { x: 190, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
// should trigger just one more touchmove
|
||||||
|
assert.equal(touchMove, 2, 'second touchmove');
|
||||||
|
|
||||||
|
// remove fingers
|
||||||
|
stage.simulateTouchEnd(
|
||||||
|
[],
|
||||||
|
[{ x: 185, y: 100, id: 0 }, { x: 190, y: 100, id: 1 }]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(touchEnd, 1, 'first touchend');
|
||||||
|
|
||||||
|
// should release captures on touchend
|
||||||
|
assert.equal(circle1.hasPointerCapture(0), false);
|
||||||
|
assert.equal(circle1.hasPointerCapture(1), false);
|
||||||
|
|
||||||
|
Konva.captureTouchEventsEnabled = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -283,45 +283,98 @@ Konva.Stage.prototype.simulateMouseUp = function(pos) {
|
|||||||
Konva.DD._endDragAfter(evt);
|
Konva.DD._endDragAfter(evt);
|
||||||
};
|
};
|
||||||
|
|
||||||
Konva.Stage.prototype.simulateTouchStart = function(pos) {
|
Konva.Stage.prototype.simulateTouchStart = function(pos, changed) {
|
||||||
var top = this.content.getBoundingClientRect().top;
|
var top = this.content.getBoundingClientRect().top;
|
||||||
|
|
||||||
this._touchstart({
|
var touches;
|
||||||
touches: [
|
var changedTouches;
|
||||||
|
if (Array.isArray(pos)) {
|
||||||
|
touches = pos.map(touch => ({
|
||||||
|
identifier: touch.id,
|
||||||
|
clientX: touch.x,
|
||||||
|
clientY: touch.y + top
|
||||||
|
}));
|
||||||
|
changedTouches = (changed || pos).map(touch => ({
|
||||||
|
identifier: touch.id,
|
||||||
|
clientX: touch.x,
|
||||||
|
clientY: touch.y + top
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
touches = [
|
||||||
{
|
{
|
||||||
clientX: pos.x,
|
clientX: pos.x,
|
||||||
clientY: pos.y + top
|
clientY: pos.y + top
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
});
|
}
|
||||||
|
var evt = {
|
||||||
|
touches: touches,
|
||||||
|
changedTouches: changedTouches
|
||||||
|
};
|
||||||
|
|
||||||
|
this._touchstart(evt);
|
||||||
};
|
};
|
||||||
|
|
||||||
Konva.Stage.prototype.simulateTouchMove = function(pos) {
|
Konva.Stage.prototype.simulateTouchMove = function(pos, changed) {
|
||||||
var top = this.content.getBoundingClientRect().top;
|
var top = this.content.getBoundingClientRect().top;
|
||||||
|
|
||||||
var evt = {
|
var touches;
|
||||||
touches: [
|
var changedTouches;
|
||||||
|
if (Array.isArray(pos)) {
|
||||||
|
touches = pos.map(touch => ({
|
||||||
|
identifier: touch.id,
|
||||||
|
clientX: touch.x,
|
||||||
|
clientY: touch.y + top
|
||||||
|
}));
|
||||||
|
changedTouches = (changed || pos).map(touch => ({
|
||||||
|
identifier: touch.id,
|
||||||
|
clientX: touch.x,
|
||||||
|
clientY: touch.y + top
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
touches = [
|
||||||
{
|
{
|
||||||
clientX: pos.x,
|
clientX: pos.x,
|
||||||
clientY: pos.y + top
|
clientY: pos.y + top
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
}
|
||||||
|
var evt = {
|
||||||
|
touches: touches,
|
||||||
|
changedTouches: changedTouches
|
||||||
};
|
};
|
||||||
|
|
||||||
this._touchmove(evt);
|
this._touchmove(evt);
|
||||||
Konva.DD._drag(evt);
|
Konva.DD._drag(evt);
|
||||||
};
|
};
|
||||||
|
|
||||||
Konva.Stage.prototype.simulateTouchEnd = function(pos) {
|
Konva.Stage.prototype.simulateTouchEnd = function(pos, changed) {
|
||||||
var top = this.content.getBoundingClientRect().top;
|
var top = this.content.getBoundingClientRect().top;
|
||||||
|
|
||||||
var evt = {
|
var touches;
|
||||||
touches: [
|
var changedTouches;
|
||||||
|
if (Array.isArray(pos)) {
|
||||||
|
touches = pos.map(touch => ({
|
||||||
|
identifier: touch.id,
|
||||||
|
clientX: touch.x,
|
||||||
|
clientY: touch.y + top
|
||||||
|
}));
|
||||||
|
changedTouches = (changed || pos).map(touch => ({
|
||||||
|
identifier: touch.id,
|
||||||
|
clientX: touch.x,
|
||||||
|
clientY: touch.y + top
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
touches = [
|
||||||
{
|
{
|
||||||
clientX: pos.x,
|
clientX: pos.x,
|
||||||
clientY: pos.y + top
|
clientY: pos.y + top
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
}
|
||||||
|
var evt = {
|
||||||
|
touches: touches,
|
||||||
|
changedTouches: changedTouches
|
||||||
};
|
};
|
||||||
|
|
||||||
Konva.DD._endDragBefore(evt);
|
Konva.DD._endDragBefore(evt);
|
||||||
|
@ -535,6 +535,174 @@ suite('DragAndDrop', function() {
|
|||||||
assert.equal(circle.y(), 100);
|
assert.equal(circle.y(), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('drag with multi-touch (second finger on empty space)', function() {
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
|
||||||
|
var circle = new Konva.Circle({
|
||||||
|
x: 70,
|
||||||
|
y: 70,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
layer.add(circle);
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
circle.on('dragstart', function() {
|
||||||
|
assert.equal(circle.x(), 70);
|
||||||
|
assert.equal(circle.y(), 70);
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.simulateTouchStart([
|
||||||
|
{
|
||||||
|
x: 70,
|
||||||
|
y: 70,
|
||||||
|
id: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 270,
|
||||||
|
y: 270,
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
stage.simulateTouchMove([
|
||||||
|
{
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
id: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 270,
|
||||||
|
y: 270,
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
stage.simulateTouchEnd([
|
||||||
|
{
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
id: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 270,
|
||||||
|
y: 270,
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
assert.equal(circle.x(), 100);
|
||||||
|
assert.equal(circle.y(), 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.only('drag with multi-touch (two shapes)', function() {
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
var circle1 = new Konva.Circle({
|
||||||
|
x: 70,
|
||||||
|
y: 70,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
layer.add(circle1);
|
||||||
|
|
||||||
|
var circle2 = new Konva.Circle({
|
||||||
|
x: 270,
|
||||||
|
y: 70,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
layer.add(circle2);
|
||||||
|
layer.draw();
|
||||||
|
|
||||||
|
var dragstart1 = 0;
|
||||||
|
var dragmove1 = 0;
|
||||||
|
circle1.on('dragstart', function() {
|
||||||
|
dragstart1 += 1;
|
||||||
|
});
|
||||||
|
circle1.on('dragmove', function() {
|
||||||
|
dragmove1 += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
var dragstart2 = 0;
|
||||||
|
var dragmove2 = 0;
|
||||||
|
circle2.on('dragstart', function() {
|
||||||
|
dragstart2 += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
circle2.on('dragmove', function() {
|
||||||
|
dragmove2 += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.simulateTouchStart([
|
||||||
|
{
|
||||||
|
x: 70,
|
||||||
|
y: 70,
|
||||||
|
id: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 270,
|
||||||
|
y: 70,
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// move one finger
|
||||||
|
stage.simulateTouchMove([
|
||||||
|
{
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
id: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 270,
|
||||||
|
y: 270,
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(dragstart1, 1);
|
||||||
|
assert.equal(circle1.isDragging(), true);
|
||||||
|
assert.equal(dragmove1, 1);
|
||||||
|
assert.equal(circle1.x(), 100);
|
||||||
|
assert.equal(circle1.y(), 100);
|
||||||
|
|
||||||
|
// move second finger
|
||||||
|
stage.simulateTouchEnd([
|
||||||
|
{
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
id: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 290,
|
||||||
|
y: 270,
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(dragstart2, 1);
|
||||||
|
assert.equal(circle2.isDragging(), true);
|
||||||
|
assert.equal(dragmove2, 1);
|
||||||
|
assert.equal(circle2.x(), 290);
|
||||||
|
assert.equal(circle2.y(), 270);
|
||||||
|
});
|
||||||
|
|
||||||
test('can stop drag on dragstart without changing position later', function() {
|
test('can stop drag on dragstart without changing position later', function() {
|
||||||
var stage = addStage();
|
var stage = addStage();
|
||||||
var layer = new Konva.Layer();
|
var layer = new Konva.Layer();
|
||||||
|
Loading…
Reference in New Issue
Block a user