mirror of
https://github.com/konvajs/konva.git
synced 2025-10-15 12:34:52 +08:00
better mulitouch
This commit is contained in:
@@ -3,6 +3,7 @@ import { Factory } from './Factory';
|
||||
import { Node, NodeConfig } from './Node';
|
||||
import { DD } from './DragAndDrop';
|
||||
import { getNumberValidator } from './Validators';
|
||||
import { Konva } from './Global';
|
||||
|
||||
import { GetSet, IRect } from './types';
|
||||
import { Shape } from './Shape';
|
||||
@@ -444,7 +445,9 @@ export abstract class Container<ChildType extends Node> extends Node<
|
||||
shouldDrawHit(canvas?) {
|
||||
var layer = this.getLayer();
|
||||
var layerUnderDrag =
|
||||
DD.isDragging && DD.anim.getLayers().indexOf(layer) !== -1;
|
||||
DD.isDragging &&
|
||||
!Konva.hitOnDragEnabled &&
|
||||
DD.anim.getLayers().indexOf(layer) !== -1;
|
||||
return (
|
||||
(canvas && canvas.isCache) ||
|
||||
(layer && layer.hitGraphEnabled() && this.isVisible() && !layerUnderDrag)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Animation } from './Animation';
|
||||
import { Konva } from './Global';
|
||||
import { Node } from './Node';
|
||||
|
||||
// TODO: make better module,
|
||||
// make sure other modules import it without global
|
||||
@@ -21,6 +22,8 @@ export const DD = {
|
||||
y: 0
|
||||
},
|
||||
node: null,
|
||||
_nodes: [],
|
||||
_offsets: [],
|
||||
|
||||
// methods
|
||||
_drag(evt) {
|
||||
|
@@ -97,6 +97,30 @@ export const Konva = {
|
||||
},
|
||||
enableTrace: 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?
|
||||
listenClickTap: false,
|
||||
|
16
src/Node.ts
16
src/Node.ts
@@ -205,6 +205,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
||||
children = emptyChildren;
|
||||
nodeType!: string;
|
||||
className!: string;
|
||||
_dragEventId: number | null = null;
|
||||
|
||||
constructor(config?: Config) {
|
||||
this.setAttrs(config);
|
||||
@@ -1575,7 +1576,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
||||
* @name Konva.Node#getStage
|
||||
* @returns {Konva.Stage}
|
||||
*/
|
||||
getStage(): any {
|
||||
getStage(): Stage | null {
|
||||
return this._getCache(STAGE, this._getStage);
|
||||
}
|
||||
|
||||
@@ -1636,7 +1637,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
||||
return this._getAbsoluteTransform(top);
|
||||
} else {
|
||||
// if no argument, we can cache the result
|
||||
return this._getCache(
|
||||
return this._getCache(
|
||||
ABSOLUTE_TRANSFORM,
|
||||
this._getAbsoluteTransform
|
||||
) as Transform;
|
||||
@@ -1805,8 +1806,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
||||
y = config.y !== undefined ? config.y : box.y,
|
||||
pixelRatio = config.pixelRatio || 1,
|
||||
canvas = new SceneCanvas({
|
||||
width: config.width || box.width || (stage ? stage.getWidth() : 0),
|
||||
height: config.height || box.height || (stage ? stage.getHeight() : 0),
|
||||
width: config.width || box.width || (stage ? stage.width() : 0),
|
||||
height: config.height || box.height || (stage ? stage.height() : 0),
|
||||
pixelRatio: pixelRatio
|
||||
}),
|
||||
context = canvas.getContext();
|
||||
@@ -2241,8 +2242,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
||||
}
|
||||
|
||||
_setDragPosition(evt?) {
|
||||
var pos = this.getStage().getPointerPosition(),
|
||||
dbf = this.dragBoundFunc();
|
||||
// const pointers = this.getStage().getPointersPositions();
|
||||
// const pos = pointers.find(p => p.id === this._dragEventId);
|
||||
const pos = this.getStage().getPointerPosition();
|
||||
|
||||
var dbf = this.dragBoundFunc();
|
||||
if (!pos) {
|
||||
return;
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ export function releaseCapture(pointerId: number, target?: Shape | Stage) {
|
||||
const stage = shape.getStage();
|
||||
|
||||
if (stage && stage.content) {
|
||||
stage.content.releasePointerCapture(pointerId);
|
||||
// stage.content.releasePointerCapture(pointerId);
|
||||
}
|
||||
|
||||
Captures.delete(pointerId);
|
||||
|
176
src/Stage.ts
176
src/Stage.ts
@@ -127,6 +127,9 @@ function checkNoClip(attrs: any = {}) {
|
||||
export class Stage extends Container<BaseLayer> {
|
||||
content: HTMLDivElement;
|
||||
pointerPos: Vector2d | null;
|
||||
_pointerPositions: (Vector2d & { id?: number })[];
|
||||
_changedPointerPositions: (Vector2d & { id?: number })[];
|
||||
|
||||
bufferCanvas: SceneCanvas;
|
||||
bufferHitCanvas: HitCanvas;
|
||||
targetShape: Shape;
|
||||
@@ -244,10 +247,14 @@ export class Stage extends Container<BaseLayer> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
getPointerPosition() {
|
||||
if (!this.pointerPos) {
|
||||
const pos = this._pointerPositions[0];
|
||||
if (!pos) {
|
||||
Util.warn(NO_POINTERS_MESSAGE);
|
||||
}
|
||||
return this.pointerPos;
|
||||
return pos;
|
||||
}
|
||||
getPointersPositions() {
|
||||
return this._pointerPositions;
|
||||
}
|
||||
getStage() {
|
||||
return this;
|
||||
@@ -437,6 +444,7 @@ export class Stage extends Container<BaseLayer> {
|
||||
});
|
||||
}
|
||||
this.pointerPos = undefined;
|
||||
this._pointerPositions = [];
|
||||
|
||||
this._fire(CONTENT_MOUSEOUT, { evt: evt });
|
||||
}
|
||||
@@ -617,32 +625,83 @@ export class Stage extends Container<BaseLayer> {
|
||||
}
|
||||
_touchstart(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;
|
||||
const hasShape = shape && shape.isListening();
|
||||
|
||||
Konva.listenClickTap = true;
|
||||
if (!hasShape) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Konva.captureTouchEventsEnabled) {
|
||||
shape.setPointerCapture(pos.id);
|
||||
}
|
||||
|
||||
if (shape && shape.isListening()) {
|
||||
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
|
||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
} else {
|
||||
});
|
||||
|
||||
if (!triggeredOnShape) {
|
||||
this._fire(TOUCHSTART, {
|
||||
evt: evt,
|
||||
target: this,
|
||||
currentTarget: this
|
||||
});
|
||||
}
|
||||
|
||||
// content event
|
||||
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) {
|
||||
this.setPointersPositions(evt);
|
||||
var shape = this.getIntersection(this.getPointerPosition()),
|
||||
clickEndShape = this.clickEndShape,
|
||||
|
||||
var clickEndShape = this.clickEndShape,
|
||||
fireDblClick = false;
|
||||
|
||||
if (Konva.inDblClickWindow) {
|
||||
@@ -658,9 +717,29 @@ export class Stage extends Container<BaseLayer> {
|
||||
Konva.inDblClickWindow = false;
|
||||
}, 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;
|
||||
shape._fireAndBubble(TOUCHEND, { evt: evt });
|
||||
triggeredOnShape = true;
|
||||
|
||||
// detect if tap or double tap occurred
|
||||
if (
|
||||
@@ -674,22 +753,26 @@ export class Stage extends Container<BaseLayer> {
|
||||
shape._fireAndBubble(DBL_TAP, { evt: evt });
|
||||
}
|
||||
}
|
||||
|
||||
// only call preventDefault if the shape is listening for events
|
||||
if (shape.isListening() && shape.preventDefault() && evt.cancelable) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
} else {
|
||||
});
|
||||
|
||||
if (!triggeredOnShape) {
|
||||
this._fire(TOUCHEND, { evt: evt, target: this, currentTarget: this });
|
||||
if (Konva.listenClickTap) {
|
||||
this._fire(TAP, { evt: evt, target: this, currentTarget: this });
|
||||
}
|
||||
if (fireDblClick) {
|
||||
this._fire(DBL_TAP, {
|
||||
evt: evt,
|
||||
target: this,
|
||||
currentTarget: this
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Konva.listenClickTap) {
|
||||
this._fire(TAP, { evt: evt, target: this, currentTarget: this });
|
||||
}
|
||||
if (fireDblClick) {
|
||||
this._fire(DBL_TAP, {
|
||||
evt: evt,
|
||||
target: this,
|
||||
currentTarget: this
|
||||
});
|
||||
}
|
||||
// content events
|
||||
this._fire(CONTENT_TOUCHEND, { evt: evt });
|
||||
@@ -702,30 +785,7 @@ export class Stage extends Container<BaseLayer> {
|
||||
|
||||
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) {
|
||||
this.setPointersPositions(evt);
|
||||
var shape = this.getIntersection(this.getPointerPosition());
|
||||
@@ -830,6 +890,29 @@ export class Stage extends Container<BaseLayer> {
|
||||
|
||||
// touch events
|
||||
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
|
||||
if (evt.touches.length > 0) {
|
||||
var touch = evt.touches[0];
|
||||
@@ -841,6 +924,7 @@ export class Stage extends Container<BaseLayer> {
|
||||
// mouse events
|
||||
x = evt.clientX - contentPosition.left;
|
||||
y = evt.clientY - contentPosition.top;
|
||||
this._pointerPositions = [{ x, y }];
|
||||
}
|
||||
if (x !== null && y !== null) {
|
||||
this.pointerPos = {
|
||||
|
Reference in New Issue
Block a user