better mulitouch

This commit is contained in:
Anton Lavrenov
2019-08-04 09:41:57 +07:00
parent b0a45caee4
commit 1d932bf76c
11 changed files with 802 additions and 129 deletions

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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 = {