2021-05-05 22:54:03 +08:00
import { Util } from './Util' ;
import { Factory } from './Factory' ;
import { Container , ContainerConfig } from './Container' ;
import { Konva } from './Global' ;
import { SceneCanvas , HitCanvas } from './Canvas' ;
2019-01-02 04:59:27 +08:00
import { GetSet , Vector2d } from './types' ;
2021-05-05 22:54:03 +08:00
import { Shape } from './Shape' ;
import { Layer } from './Layer' ;
import { DD } from './DragAndDrop' ;
import { _registerNode } from './Global' ;
import * as PointerEvents from './PointerEvents' ;
2019-01-02 04:59:27 +08:00
2019-03-10 23:31:13 +08:00
export interface StageConfig extends ContainerConfig {
container : HTMLDivElement | string ;
}
2019-01-02 04:59:27 +08:00
// CONSTANTS
var STAGE = 'Stage' ,
STRING = 'string' ,
PX = 'px' ,
MOUSEOUT = 'mouseout' ,
MOUSELEAVE = 'mouseleave' ,
MOUSEOVER = 'mouseover' ,
MOUSEENTER = 'mouseenter' ,
MOUSEMOVE = 'mousemove' ,
MOUSEDOWN = 'mousedown' ,
MOUSEUP = 'mouseup' ,
2019-03-27 04:30:00 +08:00
POINTERMOVE = 'pointermove' ,
POINTERDOWN = 'pointerdown' ,
POINTERUP = 'pointerup' ,
POINTERCANCEL = 'pointercancel' ,
LOSTPOINTERCAPTURE = 'lostpointercapture' ,
2021-05-09 09:22:20 +08:00
POINTEROUT = 'pointerout' ,
POINTERLEAVE = 'pointerleave' ,
POINTEROVER = 'pointerover' ,
POINTERENTER = 'pointerenter' ,
2019-01-02 04:59:27 +08:00
CONTEXTMENU = 'contextmenu' ,
TOUCHSTART = 'touchstart' ,
TOUCHEND = 'touchend' ,
TOUCHMOVE = 'touchmove' ,
2021-05-07 20:48:13 +08:00
TOUCHCANCEL = 'touchcancel' ,
2019-01-02 04:59:27 +08:00
WHEEL = 'wheel' ,
2019-01-22 21:43:43 +08:00
MAX_LAYERS_NUMBER = 5 ,
2019-01-02 04:59:27 +08:00
EVENTS = [
2021-05-07 20:48:13 +08:00
[ MOUSEENTER , '_pointerenter' ] ,
[ MOUSEDOWN , '_pointerdown' ] ,
[ MOUSEMOVE , '_pointermove' ] ,
[ MOUSEUP , '_pointerup' ] ,
[ MOUSELEAVE , '_pointerleave' ] ,
[ TOUCHSTART , '_pointerdown' ] ,
[ TOUCHMOVE , '_pointermove' ] ,
[ TOUCHEND , '_pointerup' ] ,
[ TOUCHCANCEL , '_pointercancel' ] ,
[ MOUSEOVER , '_pointerover' ] ,
[ WHEEL , '_wheel' ] ,
[ CONTEXTMENU , '_contextmenu' ] ,
[ POINTERDOWN , '_pointerdown' ] ,
[ POINTERMOVE , '_pointermove' ] ,
[ POINTERUP , '_pointerup' ] ,
[ POINTERCANCEL , '_pointercancel' ] ,
[ LOSTPOINTERCAPTURE , '_lostpointercapture' ] ,
] ;
2019-01-22 21:43:43 +08:00
2021-05-07 20:48:13 +08:00
const EVENTS_MAP = {
mouse : {
2021-05-09 09:22:20 +08:00
[ POINTEROUT ] : MOUSEOUT ,
[ POINTERLEAVE ] : MOUSELEAVE ,
[ POINTEROVER ] : MOUSEOVER ,
[ POINTERENTER ] : MOUSEENTER ,
[ POINTERMOVE ] : MOUSEMOVE ,
[ POINTERDOWN ] : MOUSEDOWN ,
[ POINTERUP ] : MOUSEUP ,
[ POINTERCANCEL ] : 'mousecancel' ,
2021-05-07 20:48:13 +08:00
pointerclick : 'click' ,
pointerdblclick : 'dblclick' ,
} ,
touch : {
2021-05-09 09:22:20 +08:00
[ POINTEROUT ] : 'touchout' ,
[ POINTERLEAVE ] : 'touchleave' ,
[ POINTEROVER ] : 'touchover' ,
[ POINTERENTER ] : 'touchenter' ,
[ POINTERMOVE ] : TOUCHMOVE ,
[ POINTERDOWN ] : TOUCHSTART ,
[ POINTERUP ] : TOUCHEND ,
[ POINTERCANCEL ] : TOUCHCANCEL ,
2021-05-07 20:48:13 +08:00
pointerclick : 'tap' ,
pointerdblclick : 'dbltap' ,
} ,
pointer : {
2021-05-09 09:22:20 +08:00
[ POINTEROUT ] : POINTEROUT ,
[ POINTERLEAVE ] : POINTERLEAVE ,
[ POINTEROVER ] : POINTEROVER ,
[ POINTERENTER ] : POINTERENTER ,
[ POINTERMOVE ] : POINTERMOVE ,
[ POINTERDOWN ] : POINTERDOWN ,
[ POINTERUP ] : POINTERUP ,
[ POINTERCANCEL ] : POINTERCANCEL ,
2021-05-07 20:48:13 +08:00
pointerclick : 'pointerclick' ,
pointerdblclick : 'pointerdblclick' ,
} ,
} ;
const getEventType = ( type ) = > {
if ( type . indexOf ( 'pointer' ) >= 0 ) {
return 'pointer' ;
}
if ( type . indexOf ( 'touch' ) >= 0 ) {
return 'touch' ;
}
return 'mouse' ;
} ;
const getEventsMap = ( eventType : string ) = > {
const type = getEventType ( eventType ) ;
if ( type === 'pointer' ) {
return Konva . pointerEventsEnabled && EVENTS_MAP . pointer ;
}
if ( type === 'touch' ) {
return EVENTS_MAP . touch ;
}
if ( type === 'mouse' ) {
return EVENTS_MAP . mouse ;
}
} ;
2019-02-23 09:48:00 +08:00
function checkNoClip ( attrs : any = { } ) {
if ( attrs . clipFunc || attrs . clipWidth || attrs . clipHeight ) {
Util . warn (
'Stage does not support clipping. Please use clip for Layers or Groups.'
) ;
}
return attrs ;
}
2021-05-09 09:22:20 +08:00
const NO_POINTERS_MESSAGE = ` Pointer position is missing and not registered by the stage. Looks like it is outside of the stage container. You can set it manually from event: stage.setPointersPositions(event); ` ;
export const stages : Stage [ ] = [ ] ;
2019-01-02 04:59:27 +08:00
/ * *
* Stage constructor . A stage is used to contain multiple layers
* @constructor
* @memberof Konva
* @augments Konva . Container
* @param { Object } config
* @param { String | Element } config . container Container selector or DOM element
* @ @nodeParams
* @example
* var stage = new Konva . Stage ( {
* width : 500 ,
* height : 800 ,
* container : 'containerId' // or "#containerId" or ".containerClass"
* } ) ;
* /
2020-05-15 00:13:47 +08:00
export class Stage extends Container < Layer > {
2019-01-02 04:59:27 +08:00
content : HTMLDivElement ;
pointerPos : Vector2d | null ;
2019-08-04 15:38:57 +08:00
_pointerPositions : ( Vector2d & { id? : number } ) [ ] = [ ] ;
2021-05-07 20:48:13 +08:00
_changedPointerPositions : ( Vector2d & { id : number } ) [ ] = [ ] ;
2019-08-04 10:41:57 +08:00
2019-01-02 04:59:27 +08:00
bufferCanvas : SceneCanvas ;
bufferHitCanvas : HitCanvas ;
2021-05-07 20:48:13 +08:00
_mouseTargetShape : Shape ;
_touchTargetShape : Shape ;
_pointerTargetShape : Shape ;
_mouseClickStartShape : Shape ;
_touchClickStartShape : Shape ;
_pointerClickStartShape : Shape ;
_mouseClickEndShape : Shape ;
_touchClickEndShape : Shape ;
_pointerClickEndShape : Shape ;
_mouseDblTimeout : any ;
_touchDblTimeout : any ;
_pointerDblTimeout : any ;
2019-01-02 04:59:27 +08:00
2019-03-10 23:31:13 +08:00
constructor ( config : StageConfig ) {
2019-02-23 09:48:00 +08:00
super ( checkNoClip ( config ) ) ;
2019-01-02 04:59:27 +08:00
this . _buildDOM ( ) ;
this . _bindContentEvents ( ) ;
stages . push ( this ) ;
2019-02-20 09:17:49 +08:00
this . on ( 'widthChange.konva heightChange.konva' , this . _resizeDOM ) ;
this . on ( 'visibleChange.konva' , this . _checkVisibility ) ;
2019-02-23 09:48:00 +08:00
this . on (
'clipWidthChange.konva clipHeightChange.konva clipFuncChange.konva' ,
( ) = > {
checkNoClip ( this . attrs ) ;
}
) ;
2019-02-20 09:17:49 +08:00
this . _checkVisibility ( ) ;
2019-01-02 04:59:27 +08:00
}
_validateAdd ( child ) {
2019-02-27 21:06:04 +08:00
const isLayer = child . getType ( ) === 'Layer' ;
const isFastLayer = child . getType ( ) === 'FastLayer' ;
const valid = isLayer || isFastLayer ;
if ( ! valid ) {
2019-01-02 04:59:27 +08:00
Util . throw ( 'You may only add layers to the stage.' ) ;
}
}
2019-02-20 09:17:49 +08:00
_checkVisibility() {
2020-03-15 10:07:37 +08:00
if ( ! this . content ) {
return ;
}
2019-02-20 09:17:49 +08:00
const style = this . visible ( ) ? '' : 'none' ;
this . content . style . display = style ;
}
2019-01-02 04:59:27 +08:00
/ * *
* set container dom element which contains the stage wrapper div element
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # setContainer
2019-01-02 04:59:27 +08:00
* @param { DomElement } container can pass in a dom element or id string
* /
setContainer ( container ) {
if ( typeof container === STRING ) {
if ( container . charAt ( 0 ) === '.' ) {
var className = container . slice ( 1 ) ;
container = document . getElementsByClassName ( className ) [ 0 ] ;
} else {
var id ;
if ( container . charAt ( 0 ) !== '#' ) {
id = container ;
} else {
id = container . slice ( 1 ) ;
}
container = document . getElementById ( id ) ;
}
if ( ! container ) {
throw 'Can not find container in document with id ' + id ;
}
}
2021-05-09 09:22:20 +08:00
this . _setAttr ( 'container' , container ) ;
2019-02-20 09:43:06 +08:00
if ( this . content ) {
2019-06-09 13:13:29 +08:00
if ( this . content . parentElement ) {
this . content . parentElement . removeChild ( this . content ) ;
}
2019-02-20 09:43:06 +08:00
container . appendChild ( this . content ) ;
}
2019-01-02 04:59:27 +08:00
return this ;
}
shouldDrawHit() {
return true ;
}
/ * *
* clear all layers
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # clear
2019-01-02 04:59:27 +08:00
* /
clear() {
var layers = this . children ,
len = layers . length ,
n ;
for ( n = 0 ; n < len ; n ++ ) {
layers [ n ] . clear ( ) ;
}
return this ;
}
2021-04-30 22:24:27 +08:00
clone ( obj ? ) {
2019-01-02 04:59:27 +08:00
if ( ! obj ) {
obj = { } ;
}
2021-04-30 22:24:27 +08:00
obj . container =
typeof document !== 'undefined' && document . createElement ( 'div' ) ;
2019-01-02 04:59:27 +08:00
return Container . prototype . clone . call ( this , obj ) ;
}
2019-01-06 16:01:20 +08:00
2019-01-02 04:59:27 +08:00
destroy() {
2019-01-06 16:01:20 +08:00
super . destroy ( ) ;
2019-01-02 04:59:27 +08:00
2019-01-06 16:01:20 +08:00
var content = this . content ;
2019-01-02 04:59:27 +08:00
if ( content && Util . _isInDocument ( content ) ) {
this . container ( ) . removeChild ( content ) ;
}
var index = stages . indexOf ( this ) ;
if ( index > - 1 ) {
stages . splice ( index , 1 ) ;
}
return this ;
}
/ * *
2021-05-09 10:43:51 +08:00
* returns ABSOLUTE pointer position which can be a touch position or mouse position
2019-11-09 00:37:10 +08:00
* pointer position doesn ' t include any transforms ( such as scale ) of the stage
2021-05-09 10:43:51 +08:00
* it is just a plain position of pointer relative to top - left corner of the canvas
2019-01-02 04:59:27 +08:00
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # getPointerPosition
2019-08-22 22:40:05 +08:00
* @returns { Vector2d | null }
2019-01-02 04:59:27 +08:00
* /
2019-08-22 22:40:05 +08:00
getPointerPosition ( ) : Vector2d | null {
2019-09-07 02:39:26 +08:00
const pos = this . _pointerPositions [ 0 ] || this . _changedPointerPositions [ 0 ] ;
2019-08-04 10:41:57 +08:00
if ( ! pos ) {
2019-02-20 09:17:49 +08:00
Util . warn ( NO_POINTERS_MESSAGE ) ;
2019-08-22 22:40:05 +08:00
return null ;
2019-02-20 09:17:49 +08:00
}
2019-08-04 15:38:57 +08:00
return {
x : pos.x ,
2020-05-15 00:13:47 +08:00
y : pos.y ,
2019-08-04 15:38:57 +08:00
} ;
}
_getPointerById ( id? : number ) {
2020-05-15 00:13:47 +08:00
return this . _pointerPositions . find ( ( p ) = > p . id === id ) ;
2019-08-04 10:41:57 +08:00
}
getPointersPositions() {
return this . _pointerPositions ;
2019-01-02 04:59:27 +08:00
}
getStage() {
return this ;
}
getContent() {
return this . content ;
}
_toKonvaCanvas ( config ) {
config = config || { } ;
2020-11-11 01:50:29 +08:00
config . x = config . x || 0 ;
config . y = config . y || 0 ;
config . width = config . width || this . width ( ) ;
config . height = config . height || this . height ( ) ;
2019-01-02 04:59:27 +08:00
2020-11-11 01:50:29 +08:00
var canvas = new SceneCanvas ( {
width : config.width ,
height : config.height ,
pixelRatio : config.pixelRatio || 1 ,
} ) ;
var _context = canvas . getContext ( ) . _context ;
var layers = this . children ;
if ( config . x || config . y ) {
_context . translate ( - 1 * config . x , - 1 * config . y ) ;
2019-01-02 04:59:27 +08:00
}
2021-04-30 22:24:27 +08:00
layers . forEach ( function ( layer ) {
2019-01-02 04:59:27 +08:00
if ( ! layer . isVisible ( ) ) {
return ;
}
var layerCanvas = layer . _toKonvaCanvas ( config ) ;
_context . drawImage (
layerCanvas . _canvas ,
2020-11-11 01:50:29 +08:00
config . x ,
config . y ,
2019-01-02 04:59:27 +08:00
layerCanvas . getWidth ( ) / layerCanvas . getPixelRatio ( ) ,
layerCanvas . getHeight ( ) / layerCanvas . getPixelRatio ( )
) ;
} ) ;
return canvas ;
}
/ * *
* get visible intersection shape . This is the preferred
* method for determining if a point intersects a shape or not
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # getIntersection
2019-01-02 04:59:27 +08:00
* @param { Object } pos
* @param { Number } pos . x
* @param { Number } pos . y
* @returns { Konva . Node }
* @example
* var shape = stage . getIntersection ( { x : 50 , y : 50 } ) ;
* /
2021-05-07 20:48:13 +08:00
getIntersection ( pos : Vector2d ) {
2019-08-22 22:40:05 +08:00
if ( ! pos ) {
return null ;
}
2019-01-02 04:59:27 +08:00
var layers = this . children ,
len = layers . length ,
end = len - 1 ,
2021-04-30 22:24:27 +08:00
n ;
2019-01-02 04:59:27 +08:00
for ( n = end ; n >= 0 ; n -- ) {
2021-05-07 20:48:13 +08:00
const shape = layers [ n ] . getIntersection ( pos ) ;
2019-01-02 04:59:27 +08:00
if ( shape ) {
return shape ;
}
}
return null ;
}
_resizeDOM() {
2020-03-15 10:07:37 +08:00
var width = this . width ( ) ;
var height = this . height ( ) ;
2019-01-02 04:59:27 +08:00
if ( this . content ) {
// set content dimensions
this . content . style . width = width + PX ;
this . content . style . height = height + PX ;
2020-03-15 10:07:37 +08:00
}
2019-01-02 04:59:27 +08:00
2020-03-15 10:07:37 +08:00
this . bufferCanvas . setSize ( width , height ) ;
this . bufferHitCanvas . setSize ( width , height ) ;
2019-01-02 04:59:27 +08:00
2020-03-15 10:07:37 +08:00
// set layer dimensions
2021-04-30 22:24:27 +08:00
this . children . forEach ( ( layer ) = > {
2020-03-15 10:07:37 +08:00
layer . setSize ( { width , height } ) ;
layer . draw ( ) ;
} ) ;
2019-01-02 04:59:27 +08:00
}
2021-04-30 22:24:27 +08:00
add ( layer : Layer , . . . rest ) {
2019-01-02 04:59:27 +08:00
if ( arguments . length > 1 ) {
for ( var i = 0 ; i < arguments . length ; i ++ ) {
this . add ( arguments [ i ] ) ;
}
return this ;
}
2019-01-06 16:01:20 +08:00
super . add ( layer ) ;
2019-01-22 21:43:43 +08:00
var length = this . children . length ;
if ( length > MAX_LAYERS_NUMBER ) {
Util . warn (
'The stage has ' +
length +
2019-11-17 09:35:58 +08:00
' layers. Recommended maximum number of layers is 3-5. Adding more layers into the stage may drop the performance. Rethink your tree structure, you can use Konva.Group.'
2019-01-22 21:43:43 +08:00
) ;
}
2020-04-08 01:47:22 +08:00
layer . setSize ( { width : this.width ( ) , height : this.height ( ) } ) ;
2019-01-02 04:59:27 +08:00
// draw layer and append canvas to container
layer . draw ( ) ;
2019-03-07 11:19:32 +08:00
if ( Konva . isBrowser ) {
2019-01-02 04:59:27 +08:00
this . content . appendChild ( layer . canvas . _canvas ) ;
}
// chainable
return this ;
}
getParent() {
return null ;
}
getLayer() {
return null ;
}
2019-05-31 05:11:31 +08:00
hasPointerCapture ( pointerId : number ) : boolean {
return PointerEvents . hasPointerCapture ( pointerId , this ) ;
}
setPointerCapture ( pointerId : number ) {
PointerEvents . setPointerCapture ( pointerId , this ) ;
}
releaseCapture ( pointerId : number ) {
PointerEvents . releaseCapture ( pointerId , this ) ;
}
2019-01-02 04:59:27 +08:00
/ * *
2021-04-30 22:24:27 +08:00
* returns an array of layers
2019-01-02 04:59:27 +08:00
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # getLayers
2019-01-02 04:59:27 +08:00
* /
getLayers() {
2021-04-30 22:24:27 +08:00
return this . children ;
2019-01-02 04:59:27 +08:00
}
_bindContentEvents() {
2019-03-07 11:19:32 +08:00
if ( ! Konva . isBrowser ) {
2019-01-02 04:59:27 +08:00
return ;
}
2021-05-07 20:48:13 +08:00
EVENTS . forEach ( ( [ event , methodName ] ) = > {
this . content . addEventListener ( event , ( evt ) = > {
this [ methodName ] ( evt ) ;
} ) ;
} ) ;
2019-01-02 04:59:27 +08:00
}
2021-05-07 20:48:13 +08:00
_pointerenter ( evt ) {
2019-04-04 09:28:48 +08:00
this . setPointersPositions ( evt ) ;
2021-05-07 20:48:13 +08:00
const events = getEventsMap ( evt . type ) ;
this . _fire ( events . pointerenter , {
evt : evt ,
target : this ,
currentTarget : this ,
} ) ;
2019-04-04 09:28:48 +08:00
}
2021-05-07 20:48:13 +08:00
_pointerover ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2021-05-07 20:48:13 +08:00
const events = getEventsMap ( evt . type ) ;
this . _fire ( events . pointerover , {
evt : evt ,
target : this ,
currentTarget : this ,
} ) ;
}
_getTargetShape ( evenType ) {
let shape : Shape | null = this [ evenType + 'targetShape' ] ;
if ( shape && ! shape . getStage ( ) ) {
shape = null ;
}
return shape ;
2019-01-02 04:59:27 +08:00
}
2021-05-07 20:48:13 +08:00
_pointerleave ( evt ) {
const events = getEventsMap ( evt . type ) ;
const eventType = getEventType ( evt . type ) ;
if ( ! events ) {
return ;
}
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
var targetShape = this . _getTargetShape ( eventType ) ;
2019-08-08 17:24:55 +08:00
var eventsEnabled = ! DD . isDragging || Konva . hitOnDragEnabled ;
if ( targetShape && eventsEnabled ) {
2021-05-07 20:48:13 +08:00
targetShape . _fireAndBubble ( events . pointerout , { evt : evt } ) ;
targetShape . _fireAndBubble ( events . pointerleave , { evt : evt } ) ;
this . _fire ( events . pointerleave , {
2019-04-04 09:28:48 +08:00
evt : evt ,
target : this ,
2020-05-15 00:13:47 +08:00
currentTarget : this ,
2019-04-04 09:28:48 +08:00
} ) ;
2021-05-07 20:48:13 +08:00
this [ eventType + 'targetShape' ] = null ;
} else if ( eventsEnabled ) {
this . _fire ( events . pointerleave , {
2019-04-04 09:28:48 +08:00
evt : evt ,
target : this ,
2020-05-15 00:13:47 +08:00
currentTarget : this ,
2019-04-04 09:28:48 +08:00
} ) ;
2021-05-07 20:48:13 +08:00
this . _fire ( events . pointerout , {
2019-01-02 04:59:27 +08:00
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
2019-01-02 04:59:27 +08:00
} ) ;
}
2021-05-07 20:48:13 +08:00
this . pointerPos = undefined ;
this . _pointerPositions = [ ] ;
2019-01-02 04:59:27 +08:00
}
2021-05-07 20:48:13 +08:00
_pointerdown ( evt : TouchEvent | MouseEvent | PointerEvent ) {
const events = getEventsMap ( evt . type ) ;
const eventType = getEventType ( evt . type ) ;
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
if ( ! events ) {
return ;
2019-01-02 04:59:27 +08:00
}
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
2019-08-04 10:41:57 +08:00
var triggeredOnShape = false ;
2020-05-15 00:13:47 +08:00
this . _changedPointerPositions . forEach ( ( pos ) = > {
2021-05-07 20:48:13 +08:00
var shape = this . getIntersection ( pos ) ;
2019-11-13 04:10:36 +08:00
DD . justDragged = false ;
2021-05-07 20:48:13 +08:00
// probably we are staring a click
2021-05-09 09:22:20 +08:00
Konva [ '_' + eventType + 'ListenClick' ] = true ;
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
// no shape detected? do nothing
const hasShape = shape && shape . isListening ( ) ;
2019-08-04 10:41:57 +08:00
if ( ! hasShape ) {
return ;
}
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
if ( Konva . capturePointerEventsEnabled ) {
2019-08-04 10:41:57 +08:00
shape . setPointerCapture ( pos . id ) ;
}
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
// save where we started the click
this [ eventType + 'ClickStartShape' ] = shape ;
shape . _fireAndBubble ( events . pointerdown , {
evt : evt ,
pointerId : pos.id ,
} ) ;
2019-08-04 10:41:57 +08:00
triggeredOnShape = true ;
2021-05-07 20:48:13 +08:00
// TODO: test in iframe
2019-01-02 04:59:27 +08:00
// only call preventDefault if the shape is listening for events
2021-05-07 20:48:13 +08:00
const isTouch = evt . type . indexOf ( 'touch' ) >= 0 ;
if ( shape . preventDefault ( ) && evt . cancelable && isTouch ) {
2019-01-02 04:59:27 +08:00
evt . preventDefault ( ) ;
}
2019-08-04 10:41:57 +08:00
} ) ;
2021-05-07 20:48:13 +08:00
// trigger down on stage if not already
2019-08-04 10:41:57 +08:00
if ( ! triggeredOnShape ) {
2021-05-07 20:48:13 +08:00
this . _fire ( events . pointerdown , {
2019-01-02 04:59:27 +08:00
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
2021-05-07 20:48:13 +08:00
pointerId : this._pointerPositions [ 0 ] . id ,
2019-01-02 04:59:27 +08:00
} ) ;
}
}
2021-05-07 20:48:13 +08:00
_pointermove ( evt : TouchEvent | MouseEvent | PointerEvent ) {
const events = getEventsMap ( evt . type ) ;
const eventType = getEventType ( evt . type ) ;
if ( ! events ) {
return ;
}
if ( DD . isDragging && DD . node . preventDefault ( ) && evt . cancelable ) {
evt . preventDefault ( ) ;
}
2019-08-04 10:41:57 +08:00
this . setPointersPositions ( evt ) ;
2021-05-07 20:48:13 +08:00
2019-08-08 17:24:55 +08:00
var eventsEnabled = ! DD . isDragging || Konva . hitOnDragEnabled ;
2021-05-07 20:48:13 +08:00
if ( ! eventsEnabled ) {
return ;
}
var processedShapesIds = { } ;
let triggeredOnShape = false ;
var targetShape = this . _getTargetShape ( eventType ) ;
this . _changedPointerPositions . forEach ( ( pos ) = > {
const shape = ( PointerEvents . getCapturedShape ( pos . id ) ||
this . getIntersection ( pos ) ) as Shape ;
const pointerId = pos . id ;
const event = { evt : evt , pointerId } ;
var differentTarget = targetShape !== shape ;
if ( differentTarget && targetShape ) {
2021-09-16 04:33:27 +08:00
targetShape . _fireAndBubble ( events . pointerout , { . . . event } , shape ) ;
targetShape . _fireAndBubble ( events . pointerleave , { . . . event } , shape ) ;
2021-05-07 20:48:13 +08:00
}
if ( shape ) {
2019-08-04 10:41:57 +08:00
if ( processedShapesIds [ shape . _id ] ) {
return ;
}
processedShapesIds [ shape . _id ] = true ;
2021-05-07 20:48:13 +08:00
}
if ( shape && shape . isListening ( ) ) {
2019-08-04 10:41:57 +08:00
triggeredOnShape = true ;
2021-05-07 20:48:13 +08:00
if ( differentTarget ) {
2021-09-16 04:33:27 +08:00
shape . _fireAndBubble ( events . pointerover , { . . . event } , targetShape ) ;
shape . _fireAndBubble ( events . pointerenter , { . . . event } , targetShape ) ;
2021-05-07 20:48:13 +08:00
this [ eventType + 'targetShape' ] = shape ;
}
2021-09-16 04:33:27 +08:00
shape . _fireAndBubble ( events . pointermove , { . . . event } ) ;
2021-05-07 20:48:13 +08:00
} else {
if ( targetShape ) {
this . _fire ( events . pointerover , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId ,
} ) ;
this [ eventType + 'targetShape' ] = null ;
2019-08-04 10:41:57 +08:00
}
}
2021-05-07 20:48:13 +08:00
} ) ;
2019-08-04 10:41:57 +08:00
2021-05-07 20:48:13 +08:00
if ( ! triggeredOnShape ) {
this . _fire ( events . pointermove , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId : this._changedPointerPositions [ 0 ] . id ,
} ) ;
2019-08-04 10:41:57 +08:00
}
2021-05-07 20:48:13 +08:00
}
_pointerup ( evt ) {
const events = getEventsMap ( evt . type ) ;
const eventType = getEventType ( evt . type ) ;
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
if ( ! events ) {
return ;
2019-01-02 04:59:27 +08:00
}
2021-05-07 20:48:13 +08:00
this . setPointersPositions ( evt ) ;
const clickStartShape = this [ eventType + 'ClickStartShape' ] ;
const clickEndShape = this [ eventType + 'ClickEndShape' ] ;
2019-08-04 10:41:57 +08:00
var processedShapesIds = { } ;
2021-05-07 20:48:13 +08:00
let triggeredOnShape = false ;
2020-05-15 00:13:47 +08:00
this . _changedPointerPositions . forEach ( ( pos ) = > {
2021-05-07 20:48:13 +08:00
const shape = ( PointerEvents . getCapturedShape ( pos . id ) ||
this . getIntersection ( pos ) ) as Shape ;
2019-08-04 10:41:57 +08:00
if ( shape ) {
shape . releaseCapture ( pos . id ) ;
2021-05-07 20:48:13 +08:00
if ( processedShapesIds [ shape . _id ] ) {
return ;
}
processedShapesIds [ shape . _id ] = true ;
2019-08-04 10:41:57 +08:00
}
2021-05-07 20:48:13 +08:00
const pointerId = pos . id ;
const event = { evt : evt , pointerId } ;
let fireDblClick = false ;
2021-12-09 21:57:01 +08:00
if ( Konva [ '_' + eventType + 'InDblClickWindow' ] ) {
2021-05-07 20:48:13 +08:00
fireDblClick = true ;
clearTimeout ( this [ eventType + 'DblTimeout' ] ) ;
} else if ( ! DD . justDragged ) {
// don't set inDblClickWindow after dragging
2021-05-09 09:22:20 +08:00
Konva [ '_' + eventType + 'InDblClickWindow' ] = true ;
2021-05-07 20:48:13 +08:00
clearTimeout ( this [ eventType + 'DblTimeout' ] ) ;
2019-08-04 10:41:57 +08:00
}
2021-05-07 20:48:13 +08:00
this [ eventType + 'DblTimeout' ] = setTimeout ( function ( ) {
2021-05-09 09:22:20 +08:00
Konva [ '_' + eventType + 'InDblClickWindow' ] = false ;
2021-05-07 20:48:13 +08:00
} , Konva . dblClickWindow ) ;
2019-01-02 04:59:27 +08:00
2021-05-07 20:48:13 +08:00
if ( shape && shape . isListening ( ) ) {
triggeredOnShape = true ;
this [ eventType + 'ClickEndShape' ] = shape ;
2021-09-16 04:33:27 +08:00
shape . _fireAndBubble ( events . pointerup , { . . . event } ) ;
2021-05-07 20:48:13 +08:00
// detect if click or double click occurred
if (
2021-05-09 09:22:20 +08:00
Konva [ '_' + eventType + 'ListenClick' ] &&
2021-05-07 20:48:13 +08:00
clickStartShape &&
clickStartShape === shape
) {
2021-09-16 04:33:27 +08:00
shape . _fireAndBubble ( events . pointerclick , { . . . event } ) ;
2021-05-07 20:48:13 +08:00
if ( fireDblClick && clickEndShape && clickEndShape === shape ) {
2021-09-16 04:33:27 +08:00
shape . _fireAndBubble ( events . pointerdblclick , { . . . event } ) ;
2021-05-07 20:48:13 +08:00
}
}
} else {
this [ eventType + 'ClickEndShape' ] = null ;
2019-01-02 04:59:27 +08:00
2021-05-09 09:22:20 +08:00
if ( Konva [ '_' + eventType + 'ListenClick' ] ) {
2021-05-07 20:48:13 +08:00
this . _fire ( events . pointerclick , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId ,
} ) ;
2019-01-02 04:59:27 +08:00
}
2019-08-04 10:41:57 +08:00
2021-05-07 20:48:13 +08:00
if ( fireDblClick ) {
this . _fire ( events . pointerdblclick , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId ,
} ) ;
}
2019-01-02 04:59:27 +08:00
}
2019-08-04 10:41:57 +08:00
} ) ;
if ( ! triggeredOnShape ) {
2021-05-07 20:48:13 +08:00
this . _fire ( events . pointerup , {
2019-08-04 15:38:57 +08:00
evt : evt ,
target : this ,
currentTarget : this ,
2020-05-15 00:13:47 +08:00
pointerId : this._changedPointerPositions [ 0 ] . id ,
2019-08-04 15:38:57 +08:00
} ) ;
2019-08-04 10:41:57 +08:00
}
2021-05-09 09:22:20 +08:00
Konva [ '_' + eventType + 'ListenClick' ] = false ;
2021-05-07 20:48:13 +08:00
// always call preventDefault for desktop events because some browsers
// try to drag and drop the canvas element
2022-08-05 23:33:04 +08:00
if ( evt . cancelable && eventType !== "touch" ) {
2021-05-07 20:48:13 +08:00
evt . preventDefault ( ) ;
2019-08-04 10:41:57 +08:00
}
2021-05-07 20:48:13 +08:00
}
_contextmenu ( evt ) {
this . setPointersPositions ( evt ) ;
var shape = this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape && shape . isListening ( ) ) {
shape . _fireAndBubble ( CONTEXTMENU , { evt : evt } ) ;
} else {
this . _fire ( CONTEXTMENU , {
2019-08-04 10:41:57 +08:00
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
2019-08-04 10:41:57 +08:00
} ) ;
2019-01-02 04:59:27 +08:00
}
}
2019-08-04 10:41:57 +08:00
2019-01-02 04:59:27 +08:00
_wheel ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
var shape = this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape && shape . isListening ( ) ) {
shape . _fireAndBubble ( WHEEL , { evt : evt } ) ;
} else {
this . _fire ( WHEEL , {
evt : evt ,
target : this ,
2020-05-15 00:13:47 +08:00
currentTarget : this ,
2019-01-02 04:59:27 +08:00
} ) ;
}
2019-03-27 04:30:00 +08:00
}
_pointercancel ( evt : PointerEvent ) {
this . setPointersPositions ( evt ) ;
const shape =
PointerEvents . getCapturedShape ( evt . pointerId ) ||
this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape ) {
shape . _fireAndBubble ( POINTERUP , PointerEvents . createEvent ( evt ) ) ;
}
PointerEvents . releaseCapture ( evt . pointerId ) ;
}
_lostpointercapture ( evt : PointerEvent ) {
PointerEvents . releaseCapture ( evt . pointerId ) ;
}
2019-02-20 09:17:49 +08:00
/ * *
* manually register pointers positions ( mouse / touch ) in the stage .
* So you can use stage . getPointerPosition ( ) . Usually you don ' t need to use that method
* because all internal events are automatically registered . It may be useful if event
* is triggered outside of the stage , but you still want to use Konva methods to get pointers position .
* @method
* @name Konva . Stage # setPointersPositions
* @param { Object } event Event object
* @example
*
* window . addEventListener ( 'mousemove' , ( e ) = > {
* stage . setPointersPositions ( e ) ;
* } ) ;
* /
setPointersPositions ( evt ) {
2019-01-02 04:59:27 +08:00
var contentPosition = this . _getContentPosition ( ) ,
x = null ,
y = null ;
evt = evt ? evt : window.event ;
// touch events
if ( evt . touches !== undefined ) {
2019-08-04 10:41:57 +08:00
// touchlist has not support for map method
// so we have to iterate
this . _pointerPositions = [ ] ;
this . _changedPointerPositions = [ ] ;
2021-04-30 22:24:27 +08:00
Array . prototype . forEach . call ( evt . touches , ( touch : any ) = > {
2019-08-04 10:41:57 +08:00
this . _pointerPositions . push ( {
id : touch.identifier ,
2019-11-29 01:22:44 +08:00
x : ( touch . clientX - contentPosition . left ) / contentPosition . scaleX ,
2020-05-15 00:13:47 +08:00
y : ( touch . clientY - contentPosition . top ) / contentPosition . scaleY ,
2019-08-04 10:41:57 +08:00
} ) ;
} ) ;
2021-04-30 22:24:27 +08:00
Array . prototype . forEach . call (
2019-08-04 10:41:57 +08:00
evt . changedTouches || evt . touches ,
( touch : any ) = > {
this . _changedPointerPositions . push ( {
id : touch.identifier ,
2019-11-29 01:22:44 +08:00
x : ( touch . clientX - contentPosition . left ) / contentPosition . scaleX ,
2020-05-15 00:13:47 +08:00
y : ( touch . clientY - contentPosition . top ) / contentPosition . scaleY ,
2019-08-04 10:41:57 +08:00
} ) ;
}
) ;
2019-01-02 04:59:27 +08:00
} else {
// mouse events
2019-11-29 01:22:44 +08:00
x = ( evt . clientX - contentPosition . left ) / contentPosition . scaleX ;
y = ( evt . clientY - contentPosition . top ) / contentPosition . scaleY ;
2019-01-02 04:59:27 +08:00
this . pointerPos = {
x : x ,
2020-05-15 00:13:47 +08:00
y : y ,
2019-01-02 04:59:27 +08:00
} ;
2019-08-04 15:38:57 +08:00
this . _pointerPositions = [ { x , y , id : Util._getFirstPointerId ( evt ) } ] ;
this . _changedPointerPositions = [
2020-05-15 00:13:47 +08:00
{ x , y , id : Util._getFirstPointerId ( evt ) } ,
2019-08-04 15:38:57 +08:00
] ;
2019-01-02 04:59:27 +08:00
}
}
2019-02-20 09:17:49 +08:00
_setPointerPosition ( evt ) {
Util . warn (
'Method _setPointerPosition is deprecated. Use "stage.setPointersPositions(event)" instead.'
) ;
this . setPointersPositions ( evt ) ;
}
2019-01-02 04:59:27 +08:00
_getContentPosition() {
2020-03-15 10:07:37 +08:00
if ( ! this . content || ! this . content . getBoundingClientRect ) {
return {
top : 0 ,
left : 0 ,
scaleX : 1 ,
2020-05-15 00:13:47 +08:00
scaleY : 1 ,
2020-03-15 10:07:37 +08:00
} ;
}
var rect = this . content . getBoundingClientRect ( ) ;
2019-11-29 01:22:44 +08:00
2019-01-02 04:59:27 +08:00
return {
top : rect.top ,
2019-11-29 01:22:44 +08:00
left : rect.left ,
2020-01-08 21:15:12 +08:00
// sometimes clientWidth can be equals to 0
// i saw it in react-konva test, looks like it is because of hidden testing element
scaleX : rect.width / this . content . clientWidth || 1 ,
2020-05-15 00:13:47 +08:00
scaleY : rect.height / this . content . clientHeight || 1 ,
2019-01-02 04:59:27 +08:00
} ;
}
_buildDOM() {
2020-03-15 10:07:37 +08:00
this . bufferCanvas = new SceneCanvas ( {
width : this.width ( ) ,
2020-05-15 00:13:47 +08:00
height : this.height ( ) ,
2020-03-15 10:07:37 +08:00
} ) ;
this . bufferHitCanvas = new HitCanvas ( {
pixelRatio : 1 ,
width : this.width ( ) ,
2020-05-15 00:13:47 +08:00
height : this.height ( ) ,
2020-03-15 10:07:37 +08:00
} ) ;
2019-01-02 04:59:27 +08:00
2019-03-07 11:19:32 +08:00
if ( ! Konva . isBrowser ) {
2019-01-02 04:59:27 +08:00
return ;
}
var container = this . container ( ) ;
if ( ! container ) {
throw 'Stage has no container. A container is required.' ;
}
// clear content inside container
2021-05-09 09:22:20 +08:00
container . innerHTML = '' ;
2019-01-02 04:59:27 +08:00
// content
2019-03-07 11:19:32 +08:00
this . content = document . createElement ( 'div' ) ;
2021-05-09 09:22:20 +08:00
this . content . style . position = 'relative' ;
2019-01-02 04:59:27 +08:00
this . content . style . userSelect = 'none' ;
2021-05-09 09:22:20 +08:00
this . content . className = 'konvajs-content' ;
2019-01-02 04:59:27 +08:00
this . content . setAttribute ( 'role' , 'presentation' ) ;
container . appendChild ( this . content ) ;
this . _resizeDOM ( ) ;
}
// currently cache function is now working for stage, because stage has no its own canvas element
cache() {
Util . warn (
'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.'
) ;
return this ;
}
clearCache() {
return this ;
}
2019-01-25 13:20:15 +08:00
/ * *
* batch draw
* @method
2020-11-10 21:59:20 +08:00
* @name Konva . Stage # batchDraw
2019-01-25 13:20:15 +08:00
* @return { Konva . Stage } this
* /
batchDraw() {
2021-05-04 21:57:03 +08:00
this . getChildren ( ) . forEach ( function ( layer ) {
2019-01-25 13:20:15 +08:00
layer . batchDraw ( ) ;
} ) ;
return this ;
}
2019-01-02 04:59:27 +08:00
container : GetSet < HTMLDivElement , this > ;
}
2019-01-28 04:43:50 +08:00
Stage . prototype . nodeType = STAGE ;
2019-02-27 21:06:04 +08:00
_registerNode ( Stage ) ;
2019-01-28 04:43:50 +08:00
2019-01-02 04:59:27 +08:00
/ * *
2019-01-06 16:01:20 +08:00
* get / set container DOM element
2019-01-02 04:59:27 +08:00
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # container
2019-01-02 04:59:27 +08:00
* @returns { DomElement } container
* @example
* // get container
* var container = stage . container ( ) ;
* // set container
* var container = document . createElement ( 'div' ) ;
* body . appendChild ( container ) ;
* stage . container ( container ) ;
* /
2019-02-19 01:12:03 +08:00
Factory . addGetterSetter ( Stage , 'container' ) ;