mirror of
https://github.com/konvajs/konva.git
synced 2025-04-05 20:48:28 +08:00
2644 lines
71 KiB
JavaScript
2644 lines
71 KiB
JavaScript
(function(Konva) {
|
|
'use strict';
|
|
// CONSTANTS
|
|
var ABSOLUTE_OPACITY = 'absoluteOpacity',
|
|
ABSOLUTE_TRANSFORM = 'absoluteTransform',
|
|
ABSOLUTE_SCALE = 'absoluteScale',
|
|
CHANGE = 'Change',
|
|
CHILDREN = 'children',
|
|
DOT = '.',
|
|
EMPTY_STRING = '',
|
|
GET = 'get',
|
|
ID = 'id',
|
|
KONVA = 'konva',
|
|
LISTENING = 'listening',
|
|
MOUSEENTER = 'mouseenter',
|
|
MOUSELEAVE = 'mouseleave',
|
|
NAME = 'name',
|
|
SET = 'set',
|
|
SHAPE = 'Shape',
|
|
SPACE = ' ',
|
|
STAGE = 'stage',
|
|
TRANSFORM = 'transform',
|
|
UPPER_STAGE = 'Stage',
|
|
VISIBLE = 'visible',
|
|
CLONE_BLACK_LIST = ['id'],
|
|
TRANSFORM_CHANGE_STR = [
|
|
'xChange.konva',
|
|
'yChange.konva',
|
|
'scaleXChange.konva',
|
|
'scaleYChange.konva',
|
|
'skewXChange.konva',
|
|
'skewYChange.konva',
|
|
'rotationChange.konva',
|
|
'offsetXChange.konva',
|
|
'offsetYChange.konva',
|
|
'transformsEnabledChange.konva'
|
|
].join(SPACE),
|
|
SCALE_CHANGE_STR = ['scaleXChange.konva', 'scaleYChange.konva'].join(SPACE);
|
|
|
|
/**
|
|
* Node constructor. Nodes are entities that can be transformed, layered,
|
|
* and have bound events. The stage, layers, groups, and shapes all extend Node.
|
|
* @constructor
|
|
* @memberof Konva
|
|
* @abstract
|
|
* @param {Object} config
|
|
* @@nodeParams
|
|
*/
|
|
Konva.Node = function(config) {
|
|
this._init(config);
|
|
};
|
|
|
|
Konva.Util.addMethods(Konva.Node, {
|
|
_init: function(config) {
|
|
this._id = Konva.idCounter++;
|
|
this.eventListeners = {};
|
|
this.attrs = {};
|
|
this._cache = {};
|
|
this._filterUpToDate = false;
|
|
this._isUnderCache = false;
|
|
this.setAttrs(config);
|
|
|
|
// event bindings for cache handling
|
|
this.on(TRANSFORM_CHANGE_STR, function() {
|
|
this._clearCache(TRANSFORM);
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM);
|
|
});
|
|
|
|
this.on(SCALE_CHANGE_STR, function() {
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_SCALE);
|
|
});
|
|
|
|
this.on('visibleChange.konva', function() {
|
|
this._clearSelfAndDescendantCache(VISIBLE);
|
|
});
|
|
this.on('listeningChange.konva', function() {
|
|
this._clearSelfAndDescendantCache(LISTENING);
|
|
});
|
|
this.on('opacityChange.konva', function() {
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY);
|
|
});
|
|
},
|
|
_clearCache: function(attr) {
|
|
if (attr) {
|
|
delete this._cache[attr];
|
|
} else {
|
|
this._cache = {};
|
|
}
|
|
},
|
|
_getCache: function(attr, privateGetter) {
|
|
var cache = this._cache[attr];
|
|
|
|
// if not cached, we need to set it using the private getter method.
|
|
if (cache === undefined) {
|
|
this._cache[attr] = privateGetter.call(this);
|
|
}
|
|
|
|
return this._cache[attr];
|
|
},
|
|
/*
|
|
* when the logic for a cached result depends on ancestor propagation, use this
|
|
* method to clear self and children cache
|
|
*/
|
|
_clearSelfAndDescendantCache: function(attr) {
|
|
this._clearCache(attr);
|
|
|
|
if (this.children) {
|
|
this.getChildren().each(function(node) {
|
|
node._clearSelfAndDescendantCache(attr);
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* clear cached canvas
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* node.clearCache();
|
|
*/
|
|
clearCache: function() {
|
|
delete this._cache.canvas;
|
|
this._filterUpToDate = false;
|
|
return this;
|
|
},
|
|
/**
|
|
* cache node to improve drawing performance, apply filters, or create more accurate
|
|
* hit regions. For all basic shapes size of cache canvas will be automatically detected.
|
|
* If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box
|
|
* properties. Look at [https://konvajs.github.io/docs/performance/Shape_Caching.html](link to demo page) for more information.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} [config]
|
|
* @param {Number} [config.x]
|
|
* @param {Number} [config.y]
|
|
* @param {Number} [config.width]
|
|
* @param {Number} [config.height]
|
|
* @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions.
|
|
* @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached
|
|
* region for debugging purposes
|
|
* @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache.
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // cache a shape with the x,y position of the bounding box at the center and
|
|
* // the width and height of the bounding box equal to the width and height of
|
|
* // the shape obtained from shape.width() and shape.height()
|
|
* image.cache();
|
|
*
|
|
* // cache a node and define the bounding box position and size
|
|
* node.cache({
|
|
* x: -30,
|
|
* y: -30,
|
|
* width: 100,
|
|
* height: 200
|
|
* });
|
|
*
|
|
* // cache a node and draw a red border around the bounding box
|
|
* // for debugging purposes
|
|
* node.cache({
|
|
* x: -30,
|
|
* y: -30,
|
|
* width: 100,
|
|
* height: 200,
|
|
* offset : 10,
|
|
* drawBorder: true
|
|
* });
|
|
*/
|
|
cache: function(config) {
|
|
var conf = config || {},
|
|
rect = this.getClientRect({
|
|
skipTransform: true,
|
|
relativeTo: this.getParent()
|
|
}),
|
|
width = conf.width || rect.width,
|
|
height = conf.height || rect.height,
|
|
pixelRatio = conf.pixelRatio,
|
|
x = conf.x || rect.x,
|
|
y = conf.y || rect.y,
|
|
offset = conf.offset || 0,
|
|
drawBorder = conf.drawBorder || false;
|
|
|
|
if (!width || !height) {
|
|
Konva.Util.error(
|
|
'Can not cache the node. Width or height of the node equals 0. Caching is skipped.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
width += offset * 2;
|
|
height += offset * 2;
|
|
|
|
x -= offset;
|
|
y -= offset;
|
|
|
|
var cachedSceneCanvas = new Konva.SceneCanvas({
|
|
pixelRatio: pixelRatio,
|
|
width: width,
|
|
height: height
|
|
}),
|
|
cachedFilterCanvas = new Konva.SceneCanvas({
|
|
pixelRatio: pixelRatio,
|
|
width: width,
|
|
height: height
|
|
}),
|
|
cachedHitCanvas = new Konva.HitCanvas({
|
|
pixelRatio: 1,
|
|
width: width,
|
|
height: height
|
|
}),
|
|
sceneContext = cachedSceneCanvas.getContext(),
|
|
hitContext = cachedHitCanvas.getContext();
|
|
|
|
cachedHitCanvas.isCache = true;
|
|
|
|
this.clearCache();
|
|
|
|
sceneContext.save();
|
|
hitContext.save();
|
|
|
|
sceneContext.translate(-x, -y);
|
|
hitContext.translate(-x, -y);
|
|
|
|
// extra flag to skip on getAbsolute opacity calc
|
|
this._isUnderCache = true;
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY);
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_SCALE);
|
|
|
|
this.drawScene(cachedSceneCanvas, this, true);
|
|
this.drawHit(cachedHitCanvas, this, true);
|
|
this._isUnderCache = false;
|
|
|
|
sceneContext.restore();
|
|
hitContext.restore();
|
|
|
|
// this will draw a red border around the cached box for
|
|
// debugging purposes
|
|
if (drawBorder) {
|
|
sceneContext.save();
|
|
sceneContext.beginPath();
|
|
sceneContext.rect(0, 0, width, height);
|
|
sceneContext.closePath();
|
|
sceneContext.setAttr('strokeStyle', 'red');
|
|
sceneContext.setAttr('lineWidth', 5);
|
|
sceneContext.stroke();
|
|
sceneContext.restore();
|
|
}
|
|
|
|
this._cache.canvas = {
|
|
scene: cachedSceneCanvas,
|
|
filter: cachedFilterCanvas,
|
|
hit: cachedHitCanvas,
|
|
x: x,
|
|
y: y
|
|
};
|
|
|
|
return this;
|
|
},
|
|
/**
|
|
* Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc).
|
|
* The rectangle position is relative to parent container.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} config
|
|
* @param {Boolean} [config.skipTransform] should we apply transform to node for calculating rect?
|
|
* @param {Object} [config.relativeTo] calculate client rect relative to one of the parents
|
|
* @returns {Object} rect with {x, y, width, height} properties
|
|
* @example
|
|
* var rect = new Konva.Rect({
|
|
* width : 100,
|
|
* height : 100,
|
|
* x : 50,
|
|
* y : 50,
|
|
* strokeWidth : 4,
|
|
* stroke : 'black',
|
|
* offsetX : 50,
|
|
* scaleY : 2
|
|
* });
|
|
*
|
|
* // get client rect without think off transformations (position, rotation, scale, offset, etc)
|
|
* rect.getClientRect({ skipTransform: true});
|
|
* // returns {
|
|
* // x : -2, // two pixels for stroke / 2
|
|
* // y : -2,
|
|
* // width : 104, // increased by 4 for stroke
|
|
* // height : 104
|
|
* //}
|
|
*
|
|
* // get client rect with transformation applied
|
|
* rect.getClientRect();
|
|
* // returns Object {x: -2, y: 46, width: 104, height: 208}
|
|
*/
|
|
getClientRect: function() {
|
|
// abstract method
|
|
// redefine in Container and Shape
|
|
throw new Error('abstract "getClientRect" method call');
|
|
},
|
|
_transformedRect: function(rect, top) {
|
|
var points = [
|
|
{ x: rect.x, y: rect.y },
|
|
{ x: rect.x + rect.width, y: rect.y },
|
|
{ x: rect.x + rect.width, y: rect.y + rect.height },
|
|
{ x: rect.x, y: rect.y + rect.height }
|
|
];
|
|
var minX, minY, maxX, maxY;
|
|
var trans = this.getAbsoluteTransform(top);
|
|
points.forEach(function(point) {
|
|
var transformed = trans.point(point);
|
|
if (minX === undefined) {
|
|
minX = maxX = transformed.x;
|
|
minY = maxY = transformed.y;
|
|
}
|
|
minX = Math.min(minX, transformed.x);
|
|
minY = Math.min(minY, transformed.y);
|
|
maxX = Math.max(maxX, transformed.x);
|
|
maxY = Math.max(maxY, transformed.y);
|
|
});
|
|
return {
|
|
x: minX,
|
|
y: minY,
|
|
width: maxX - minX,
|
|
height: maxY - minY
|
|
};
|
|
},
|
|
_drawCachedSceneCanvas: function(context) {
|
|
context.save();
|
|
context._applyOpacity(this);
|
|
context._applyGlobalCompositeOperation(this);
|
|
context.translate(this._cache.canvas.x, this._cache.canvas.y);
|
|
|
|
var cacheCanvas = this._getCachedSceneCanvas();
|
|
var ratio = cacheCanvas.pixelRatio;
|
|
|
|
context.drawImage(
|
|
cacheCanvas._canvas,
|
|
0,
|
|
0,
|
|
cacheCanvas.width / ratio,
|
|
cacheCanvas.height / ratio
|
|
);
|
|
context.restore();
|
|
},
|
|
_drawCachedHitCanvas: function(context) {
|
|
var cachedCanvas = this._cache.canvas,
|
|
hitCanvas = cachedCanvas.hit;
|
|
context.save();
|
|
context.translate(this._cache.canvas.x, this._cache.canvas.y);
|
|
context.drawImage(hitCanvas._canvas, 0, 0);
|
|
context.restore();
|
|
},
|
|
_getCachedSceneCanvas: function() {
|
|
var filters = this.filters(),
|
|
cachedCanvas = this._cache.canvas,
|
|
sceneCanvas = cachedCanvas.scene,
|
|
filterCanvas = cachedCanvas.filter,
|
|
filterContext = filterCanvas.getContext(),
|
|
len,
|
|
imageData,
|
|
n,
|
|
filter;
|
|
|
|
if (filters) {
|
|
if (!this._filterUpToDate) {
|
|
var ratio = sceneCanvas.pixelRatio;
|
|
|
|
try {
|
|
len = filters.length;
|
|
filterContext.clear();
|
|
|
|
// copy cached canvas onto filter context
|
|
filterContext.drawImage(
|
|
sceneCanvas._canvas,
|
|
0,
|
|
0,
|
|
sceneCanvas.getWidth() / ratio,
|
|
sceneCanvas.getHeight() / ratio
|
|
);
|
|
imageData = filterContext.getImageData(
|
|
0,
|
|
0,
|
|
filterCanvas.getWidth(),
|
|
filterCanvas.getHeight()
|
|
);
|
|
|
|
// apply filters to filter context
|
|
for (n = 0; n < len; n++) {
|
|
filter = filters[n];
|
|
if (typeof filter !== 'function') {
|
|
Konva.Util.error(
|
|
'Filter should be type of function, but got ' +
|
|
typeof filter +
|
|
' insted. Please check correct filters'
|
|
);
|
|
continue;
|
|
}
|
|
filter.call(this, imageData);
|
|
filterContext.putImageData(imageData, 0, 0);
|
|
}
|
|
} catch (e) {
|
|
Konva.Util.error('Unable to apply filter. ' + e.message);
|
|
}
|
|
|
|
this._filterUpToDate = true;
|
|
}
|
|
|
|
return filterCanvas;
|
|
}
|
|
return sceneCanvas;
|
|
},
|
|
/**
|
|
* bind events to the node. KonvaJS supports mouseover, mousemove,
|
|
* mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, contextmenu, click, dblclick, touchstart, touchmove,
|
|
* touchend, tap, dbltap, dragstart, dragmove, and dragend events. The Konva Stage supports
|
|
* contentMouseover, contentMousemove, contentMouseout, contentMousedown, contentMouseup, contentWheel, contentContextmenu
|
|
* contentClick, contentDblclick, contentTouchstart, contentTouchmove, contentTouchend, contentTap,
|
|
* and contentDblTap. Pass in a string of events delimited by a space to bind multiple events at once
|
|
* such as 'mousedown mouseup mousemove'. Include a namespace to bind an
|
|
* event by name such as 'click.foobar'.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo touchstart.foo'
|
|
* @param {Function} handler The handler function is passed an event object
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // add click listener
|
|
* node.on('click', function() {
|
|
* console.log('you clicked me!');
|
|
* });
|
|
*
|
|
* // get the target node
|
|
* node.on('click', function(evt) {
|
|
* console.log(evt.target);
|
|
* });
|
|
*
|
|
* // stop event propagation
|
|
* node.on('click', function(evt) {
|
|
* evt.cancelBubble = true;
|
|
* });
|
|
*
|
|
* // bind multiple listeners
|
|
* node.on('click touchstart', function() {
|
|
* console.log('you clicked/touched me!');
|
|
* });
|
|
*
|
|
* // namespace listener
|
|
* node.on('click.foo', function() {
|
|
* console.log('you clicked/touched me!');
|
|
* });
|
|
*
|
|
* // get the event type
|
|
* node.on('click tap', function(evt) {
|
|
* var eventType = evt.type;
|
|
* });
|
|
*
|
|
* // get native event object
|
|
* node.on('click tap', function(evt) {
|
|
* var nativeEvent = evt.evt;
|
|
* });
|
|
*
|
|
* // for change events, get the old and new val
|
|
* node.on('xChange', function(evt) {
|
|
* var oldVal = evt.oldVal;
|
|
* var newVal = evt.newVal;
|
|
* });
|
|
*
|
|
* // get event targets
|
|
* // with event delegations
|
|
* layer.on('click', 'Group', function(evt) {
|
|
* var shape = evt.target;
|
|
* var group = evtn.currentTarger;
|
|
* });
|
|
*/
|
|
on: function(evtStr, handler) {
|
|
if (arguments.length === 3) {
|
|
return this._delegate.apply(this, arguments);
|
|
}
|
|
var events = evtStr.split(SPACE),
|
|
len = events.length,
|
|
n,
|
|
event,
|
|
parts,
|
|
baseEvent,
|
|
name;
|
|
|
|
/*
|
|
* loop through types and attach event listeners to
|
|
* each one. eg. 'click mouseover.namespace mouseout'
|
|
* will create three event bindings
|
|
*/
|
|
for (n = 0; n < len; n++) {
|
|
event = events[n];
|
|
parts = event.split(DOT);
|
|
baseEvent = parts[0];
|
|
name = parts[1] || EMPTY_STRING;
|
|
|
|
// create events array if it doesn't exist
|
|
if (!this.eventListeners[baseEvent]) {
|
|
this.eventListeners[baseEvent] = [];
|
|
}
|
|
|
|
this.eventListeners[baseEvent].push({
|
|
name: name,
|
|
handler: handler
|
|
});
|
|
}
|
|
|
|
return this;
|
|
},
|
|
/**
|
|
* remove event bindings from the node. Pass in a string of
|
|
* event types delimmited by a space to remove multiple event
|
|
* bindings at once such as 'mousedown mouseup mousemove'.
|
|
* include a namespace to remove an event binding by name
|
|
* such as 'click.foobar'. If you only give a name like '.foobar',
|
|
* all events in that namespace will be removed.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar'
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // remove listener
|
|
* node.off('click');
|
|
*
|
|
* // remove multiple listeners
|
|
* node.off('click touchstart');
|
|
*
|
|
* // remove listener by name
|
|
* node.off('click.foo');
|
|
*/
|
|
off: function(evtStr, callback) {
|
|
var events = (evtStr || '').split(SPACE),
|
|
len = events.length,
|
|
n,
|
|
t,
|
|
event,
|
|
parts,
|
|
baseEvent,
|
|
name;
|
|
|
|
if (!evtStr) {
|
|
// remove all events
|
|
for (t in this.eventListeners) {
|
|
this._off(t);
|
|
}
|
|
}
|
|
for (n = 0; n < len; n++) {
|
|
event = events[n];
|
|
parts = event.split(DOT);
|
|
baseEvent = parts[0];
|
|
name = parts[1];
|
|
|
|
if (baseEvent) {
|
|
if (this.eventListeners[baseEvent]) {
|
|
this._off(baseEvent, name, callback);
|
|
}
|
|
} else {
|
|
for (t in this.eventListeners) {
|
|
this._off(t, name, callback);
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
// some event aliases for third party integration like HammerJS
|
|
dispatchEvent: function(evt) {
|
|
var e = {
|
|
target: this,
|
|
type: evt.type,
|
|
evt: evt
|
|
};
|
|
this.fire(evt.type, e);
|
|
return this;
|
|
},
|
|
addEventListener: function(type, handler) {
|
|
// we have to pass native event to handler
|
|
this.on(type, function(evt) {
|
|
handler.call(this, evt.evt);
|
|
});
|
|
return this;
|
|
},
|
|
removeEventListener: function(type) {
|
|
this.off(type);
|
|
return this;
|
|
},
|
|
// like node.on
|
|
_delegate: function(event, selector, handler) {
|
|
var stopNode = this;
|
|
this.on(event, function(evt) {
|
|
var targets = evt.target.findAncestors(selector, true, stopNode);
|
|
for (var i = 0; i < targets.length; i++) {
|
|
evt = Konva.Util.cloneObject(evt);
|
|
evt.currentTarget = targets[i];
|
|
handler.call(targets[i], evt);
|
|
}
|
|
});
|
|
},
|
|
/**
|
|
* remove self from parent, but don't destroy. You can reuse node later.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* node.remove();
|
|
*/
|
|
remove: function() {
|
|
var parent = this.getParent();
|
|
|
|
if (parent && parent.children) {
|
|
parent.children.splice(this.index, 1);
|
|
parent._setChildrenIndices();
|
|
delete this.parent;
|
|
}
|
|
|
|
// every cached attr that is calculated via node tree
|
|
// traversal must be cleared when removing a node
|
|
this._clearSelfAndDescendantCache(STAGE);
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM);
|
|
this._clearSelfAndDescendantCache(VISIBLE);
|
|
this._clearSelfAndDescendantCache(LISTENING);
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY);
|
|
|
|
return this;
|
|
},
|
|
/**
|
|
* remove and destroy a node. Kill it forever! You should not reuse node after destroy().
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @example
|
|
* node.destroy();
|
|
*/
|
|
destroy: function() {
|
|
// remove from ids and names hashes
|
|
Konva._removeId(this.getId());
|
|
|
|
// remove all names
|
|
var names = (this.getName() || '').split(/\s/g);
|
|
for (var i = 0; i < names.length; i++) {
|
|
var subname = names[i];
|
|
Konva._removeName(subname, this._id);
|
|
}
|
|
|
|
this.remove();
|
|
return this;
|
|
},
|
|
/**
|
|
* get attr
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} attr
|
|
* @returns {Integer|String|Object|Array}
|
|
* @example
|
|
* var x = node.getAttr('x');
|
|
*/
|
|
getAttr: function(attr) {
|
|
var method = GET + Konva.Util._capitalize(attr);
|
|
if (Konva.Util._isFunction(this[method])) {
|
|
return this[method]();
|
|
}
|
|
// otherwise get directly
|
|
return this.attrs[attr];
|
|
},
|
|
/**
|
|
* get ancestors
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Collection}
|
|
* @example
|
|
* shape.getAncestors().each(function(node) {
|
|
* console.log(node.getId());
|
|
* })
|
|
*/
|
|
getAncestors: function() {
|
|
var parent = this.getParent(),
|
|
ancestors = new Konva.Collection();
|
|
|
|
while (parent) {
|
|
ancestors.push(parent);
|
|
parent = parent.getParent();
|
|
}
|
|
|
|
return ancestors;
|
|
},
|
|
/**
|
|
* get attrs object literal
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Object}
|
|
*/
|
|
getAttrs: function() {
|
|
return this.attrs || {};
|
|
},
|
|
/**
|
|
* set multiple attrs at once using an object literal
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} config object containing key value pairs
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* node.setAttrs({
|
|
* x: 5,
|
|
* fill: 'red'
|
|
* });
|
|
*/
|
|
setAttrs: function(config) {
|
|
var key, method;
|
|
|
|
if (!config) {
|
|
return this;
|
|
}
|
|
for (key in config) {
|
|
if (key === CHILDREN) {
|
|
continue;
|
|
}
|
|
method = SET + Konva.Util._capitalize(key);
|
|
// use setter if available
|
|
if (Konva.Util._isFunction(this[method])) {
|
|
this[method](config[key]);
|
|
} else {
|
|
// otherwise set directly
|
|
this._setAttr(key, config[key]);
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* determine if node is listening for events by taking into account ancestors.
|
|
*
|
|
* Parent | Self | isListening
|
|
* listening | listening |
|
|
* ----------+-----------+------------
|
|
* T | T | T
|
|
* T | F | F
|
|
* F | T | T
|
|
* F | F | F
|
|
* ----------+-----------+------------
|
|
* T | I | T
|
|
* F | I | F
|
|
* I | I | T
|
|
*
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean}
|
|
*/
|
|
isListening: function() {
|
|
return this._getCache(LISTENING, this._isListening);
|
|
},
|
|
_isListening: function() {
|
|
var listening = this.getListening(),
|
|
parent = this.getParent();
|
|
|
|
// the following conditions are a simplification of the truth table above.
|
|
// please modify carefully
|
|
if (listening === 'inherit') {
|
|
if (parent) {
|
|
return parent.isListening();
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
return listening;
|
|
}
|
|
},
|
|
/**
|
|
* determine if node is visible by taking into account ancestors.
|
|
*
|
|
* Parent | Self | isVisible
|
|
* visible | visible |
|
|
* ----------+-----------+------------
|
|
* T | T | T
|
|
* T | F | F
|
|
* F | T | T
|
|
* F | F | F
|
|
* ----------+-----------+------------
|
|
* T | I | T
|
|
* F | I | F
|
|
* I | I | T
|
|
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean}
|
|
*/
|
|
isVisible: function() {
|
|
return this._getCache(VISIBLE, this._isVisible);
|
|
},
|
|
_isVisible: function() {
|
|
var visible = this.getVisible(),
|
|
parent = this.getParent();
|
|
|
|
// the following conditions are a simplification of the truth table above.
|
|
// please modify carefully
|
|
if (visible === 'inherit') {
|
|
if (parent) {
|
|
return parent.isVisible();
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
return visible;
|
|
}
|
|
},
|
|
/**
|
|
* determine if listening is enabled by taking into account descendants. If self or any children
|
|
* have _isListeningEnabled set to true, then self also has listening enabled.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean}
|
|
*/
|
|
shouldDrawHit: function(canvas) {
|
|
var layer = this.getLayer();
|
|
return (
|
|
(canvas && canvas.isCache) ||
|
|
(layer &&
|
|
layer.hitGraphEnabled() &&
|
|
this.isListening() &&
|
|
this.isVisible())
|
|
);
|
|
},
|
|
/**
|
|
* show node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Node}
|
|
*/
|
|
show: function() {
|
|
this.setVisible(true);
|
|
return this;
|
|
},
|
|
/**
|
|
* hide node. Hidden nodes are no longer detectable
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Node}
|
|
*/
|
|
hide: function() {
|
|
this.setVisible(false);
|
|
return this;
|
|
},
|
|
/**
|
|
* get zIndex relative to the node's siblings who share the same parent
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Integer}
|
|
*/
|
|
getZIndex: function() {
|
|
return this.index || 0;
|
|
},
|
|
/**
|
|
* get absolute z-index which takes into account sibling
|
|
* and ancestor indices
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Integer}
|
|
*/
|
|
getAbsoluteZIndex: function() {
|
|
var depth = this.getDepth(),
|
|
that = this,
|
|
index = 0,
|
|
nodes,
|
|
len,
|
|
n,
|
|
child;
|
|
|
|
function addChildren(children) {
|
|
nodes = [];
|
|
len = children.length;
|
|
for (n = 0; n < len; n++) {
|
|
child = children[n];
|
|
index++;
|
|
|
|
if (child.nodeType !== SHAPE) {
|
|
nodes = nodes.concat(child.getChildren().toArray());
|
|
}
|
|
|
|
if (child._id === that._id) {
|
|
n = len;
|
|
}
|
|
}
|
|
|
|
if (nodes.length > 0 && nodes[0].getDepth() <= depth) {
|
|
addChildren(nodes);
|
|
}
|
|
}
|
|
if (that.nodeType !== UPPER_STAGE) {
|
|
addChildren(that.getStage().getChildren());
|
|
}
|
|
|
|
return index;
|
|
},
|
|
/**
|
|
* get node depth in node tree. Returns an integer.
|
|
* e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always
|
|
* be >= 2
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Integer}
|
|
*/
|
|
getDepth: function() {
|
|
var depth = 0,
|
|
parent = this.parent;
|
|
|
|
while (parent) {
|
|
depth++;
|
|
parent = parent.parent;
|
|
}
|
|
return depth;
|
|
},
|
|
setPosition: function(pos) {
|
|
this.setX(pos.x);
|
|
this.setY(pos.y);
|
|
return this;
|
|
},
|
|
getPosition: function() {
|
|
return {
|
|
x: this.getX(),
|
|
y: this.getY()
|
|
};
|
|
},
|
|
/**
|
|
* get absolute position relative to the top left corner of the stage container div
|
|
* or relative to passed node
|
|
* @method
|
|
* @param {Object} [top] optional parent node
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Object}
|
|
*/
|
|
getAbsolutePosition: function(top) {
|
|
var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(),
|
|
absoluteTransform = new Konva.Transform(),
|
|
offset = this.offset();
|
|
|
|
// clone the matrix array
|
|
absoluteTransform.m = absoluteMatrix.slice();
|
|
absoluteTransform.translate(offset.x, offset.y);
|
|
|
|
return absoluteTransform.getTranslation();
|
|
},
|
|
/**
|
|
* set absolute position
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} pos
|
|
* @param {Number} pos.x
|
|
* @param {Number} pos.y
|
|
* @returns {Konva.Node}
|
|
*/
|
|
setAbsolutePosition: function(pos) {
|
|
var origTrans = this._clearTransform(),
|
|
it;
|
|
|
|
// don't clear translation
|
|
this.attrs.x = origTrans.x;
|
|
this.attrs.y = origTrans.y;
|
|
delete origTrans.x;
|
|
delete origTrans.y;
|
|
|
|
// unravel transform
|
|
it = this.getAbsoluteTransform();
|
|
|
|
it.invert();
|
|
it.translate(pos.x, pos.y);
|
|
pos = {
|
|
x: this.attrs.x + it.getTranslation().x,
|
|
y: this.attrs.y + it.getTranslation().y
|
|
};
|
|
|
|
this.setPosition({ x: pos.x, y: pos.y });
|
|
this._setTransform(origTrans);
|
|
|
|
return this;
|
|
},
|
|
_setTransform: function(trans) {
|
|
var key;
|
|
|
|
for (key in trans) {
|
|
this.attrs[key] = trans[key];
|
|
}
|
|
|
|
this._clearCache(TRANSFORM);
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM);
|
|
},
|
|
_clearTransform: function() {
|
|
var trans = {
|
|
x: this.getX(),
|
|
y: this.getY(),
|
|
rotation: this.getRotation(),
|
|
scaleX: this.getScaleX(),
|
|
scaleY: this.getScaleY(),
|
|
offsetX: this.getOffsetX(),
|
|
offsetY: this.getOffsetY(),
|
|
skewX: this.getSkewX(),
|
|
skewY: this.getSkewY()
|
|
};
|
|
|
|
this.attrs.x = 0;
|
|
this.attrs.y = 0;
|
|
this.attrs.rotation = 0;
|
|
this.attrs.scaleX = 1;
|
|
this.attrs.scaleY = 1;
|
|
this.attrs.offsetX = 0;
|
|
this.attrs.offsetY = 0;
|
|
this.attrs.skewX = 0;
|
|
this.attrs.skewY = 0;
|
|
|
|
this._clearCache(TRANSFORM);
|
|
this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM);
|
|
|
|
// return original transform
|
|
return trans;
|
|
},
|
|
/**
|
|
* move node by an amount relative to its current position
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} change
|
|
* @param {Number} change.x
|
|
* @param {Number} change.y
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // move node in x direction by 1px and y direction by 2px
|
|
* node.move({
|
|
* x: 1,
|
|
* y: 2)
|
|
* });
|
|
*/
|
|
move: function(change) {
|
|
var changeX = change.x,
|
|
changeY = change.y,
|
|
x = this.getX(),
|
|
y = this.getY();
|
|
|
|
if (changeX !== undefined) {
|
|
x += changeX;
|
|
}
|
|
|
|
if (changeY !== undefined) {
|
|
y += changeY;
|
|
}
|
|
|
|
this.setPosition({ x: x, y: y });
|
|
return this;
|
|
},
|
|
_eachAncestorReverse: function(func, top) {
|
|
var family = [],
|
|
parent = this.getParent(),
|
|
len,
|
|
n;
|
|
|
|
// if top node is defined, and this node is top node,
|
|
// there's no need to build a family tree. just execute
|
|
// func with this because it will be the only node
|
|
if (top && top._id === this._id) {
|
|
func(this);
|
|
return;
|
|
}
|
|
|
|
family.unshift(this);
|
|
|
|
while (parent && (!top || parent._id !== top._id)) {
|
|
family.unshift(parent);
|
|
parent = parent.parent;
|
|
}
|
|
|
|
len = family.length;
|
|
for (n = 0; n < len; n++) {
|
|
func(family[n]);
|
|
}
|
|
},
|
|
/**
|
|
* rotate node by an amount in degrees relative to its current rotation
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} theta
|
|
* @returns {Konva.Node}
|
|
*/
|
|
rotate: function(theta) {
|
|
this.setRotation(this.getRotation() + theta);
|
|
return this;
|
|
},
|
|
/**
|
|
* move node to the top of its siblings
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean}
|
|
*/
|
|
moveToTop: function() {
|
|
if (!this.parent) {
|
|
Konva.Util.warn('Node has no parent. moveToTop function is ignored.');
|
|
return false;
|
|
}
|
|
var index = this.index;
|
|
this.parent.children.splice(index, 1);
|
|
this.parent.children.push(this);
|
|
this.parent._setChildrenIndices();
|
|
return true;
|
|
},
|
|
/**
|
|
* move node up
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean} flag is moved or not
|
|
*/
|
|
moveUp: function() {
|
|
if (!this.parent) {
|
|
Konva.Util.warn('Node has no parent. moveUp function is ignored.');
|
|
return false;
|
|
}
|
|
var index = this.index,
|
|
len = this.parent.getChildren().length;
|
|
if (index < len - 1) {
|
|
this.parent.children.splice(index, 1);
|
|
this.parent.children.splice(index + 1, 0, this);
|
|
this.parent._setChildrenIndices();
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* move node down
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean}
|
|
*/
|
|
moveDown: function() {
|
|
if (!this.parent) {
|
|
Konva.Util.warn('Node has no parent. moveDown function is ignored.');
|
|
return false;
|
|
}
|
|
var index = this.index;
|
|
if (index > 0) {
|
|
this.parent.children.splice(index, 1);
|
|
this.parent.children.splice(index - 1, 0, this);
|
|
this.parent._setChildrenIndices();
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* move node to the bottom of its siblings
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Boolean}
|
|
*/
|
|
moveToBottom: function() {
|
|
if (!this.parent) {
|
|
Konva.Util.warn(
|
|
'Node has no parent. moveToBottom function is ignored.'
|
|
);
|
|
return false;
|
|
}
|
|
var index = this.index;
|
|
if (index > 0) {
|
|
this.parent.children.splice(index, 1);
|
|
this.parent.children.unshift(this);
|
|
this.parent._setChildrenIndices();
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* set zIndex relative to siblings
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Integer} zIndex
|
|
* @returns {Konva.Node}
|
|
*/
|
|
setZIndex: function(zIndex) {
|
|
if (!this.parent) {
|
|
Konva.Util.warn('Node has no parent. zIndex parameter is ignored.');
|
|
return false;
|
|
}
|
|
var index = this.index;
|
|
this.parent.children.splice(index, 1);
|
|
this.parent.children.splice(zIndex, 0, this);
|
|
this.parent._setChildrenIndices();
|
|
return this;
|
|
},
|
|
/**
|
|
* get absolute opacity
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Number}
|
|
*/
|
|
getAbsoluteOpacity: function() {
|
|
return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity);
|
|
},
|
|
_getAbsoluteOpacity: function() {
|
|
var absOpacity = this.getOpacity();
|
|
var parent = this.getParent();
|
|
if (parent && !parent._isUnderCache) {
|
|
absOpacity *= this.getParent().getAbsoluteOpacity();
|
|
}
|
|
return absOpacity;
|
|
},
|
|
/**
|
|
* move node to another container
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Container} newContainer
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // move node from current layer into layer2
|
|
* node.moveTo(layer2);
|
|
*/
|
|
moveTo: function(newContainer) {
|
|
// do nothing if new container is already parent
|
|
if (this.getParent() !== newContainer) {
|
|
// this.remove my be overrided by drag and drop
|
|
// buy we need original
|
|
(this.__originalRemove || this.remove).call(this);
|
|
newContainer.add(this);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* convert Node into an object for serialization. Returns an object.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Object}
|
|
*/
|
|
toObject: function() {
|
|
var obj = {},
|
|
attrs = this.getAttrs(),
|
|
key,
|
|
val,
|
|
getter,
|
|
defaultValue;
|
|
|
|
obj.attrs = {};
|
|
|
|
for (key in attrs) {
|
|
val = attrs[key];
|
|
getter = typeof this[key] === 'function' && this[key];
|
|
// remove attr value so that we can extract the default value from the getter
|
|
delete attrs[key];
|
|
defaultValue = getter ? getter.call(this) : null;
|
|
// restore attr value
|
|
attrs[key] = val;
|
|
if (defaultValue !== val) {
|
|
obj.attrs[key] = val;
|
|
}
|
|
}
|
|
|
|
obj.className = this.getClassName();
|
|
return Konva.Util._prepareToStringify(obj);
|
|
},
|
|
/**
|
|
* convert Node into a JSON string. Returns a JSON string.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {String}}
|
|
*/
|
|
toJSON: function() {
|
|
return JSON.stringify(this.toObject());
|
|
},
|
|
/**
|
|
* get parent container
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Node}
|
|
*/
|
|
getParent: function() {
|
|
return this.parent;
|
|
},
|
|
/**
|
|
* get all ancestros (parent then parent of the parent, etc) of the node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} [selector] selector for search
|
|
* @param {Boolean} [includeSelf] show we think that node is ancestro itself?
|
|
* @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors)
|
|
* @returns {Array} [ancestors]
|
|
* @example
|
|
* // get one of the parent group
|
|
* var parentGroups = node.findAncestors('Group');
|
|
*/
|
|
findAncestors: function(selector, includeSelf, stopNode) {
|
|
var res = [];
|
|
|
|
if (includeSelf && this._isMatch(selector)) {
|
|
res.push(this);
|
|
}
|
|
var ancestor = this.parent;
|
|
while (ancestor) {
|
|
if (ancestor === stopNode) {
|
|
return res;
|
|
}
|
|
if (ancestor._isMatch(selector)) {
|
|
res.push(ancestor);
|
|
}
|
|
ancestor = ancestor.parent;
|
|
}
|
|
return res;
|
|
},
|
|
/**
|
|
* get ancestor (parent or parent of the parent, etc) of the node that match passed selector
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} [selector] selector for search
|
|
* @param {Boolean} [includeSelf] show we think that node is ancestro itself?
|
|
* @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors)
|
|
* @returns {Konva.Node} ancestor
|
|
* @example
|
|
* // get one of the parent group
|
|
* var group = node.findAncestors('.mygroup');
|
|
*/
|
|
findAncestor: function(selector, includeSelf, stopNode) {
|
|
return this.findAncestors(selector, includeSelf, stopNode)[0];
|
|
},
|
|
// is current node match passed selector?
|
|
_isMatch: function(selector) {
|
|
if (!selector) {
|
|
return false;
|
|
}
|
|
var selectorArr = selector.replace(/ /g, '').split(','),
|
|
len = selectorArr.length,
|
|
n,
|
|
sel;
|
|
|
|
for (n = 0; n < len; n++) {
|
|
sel = selectorArr[n];
|
|
if (!Konva.Util.isValidSelector(sel)) {
|
|
Konva.Util.warn(
|
|
'Selector "' +
|
|
sel +
|
|
'" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".'
|
|
);
|
|
Konva.Util.warn(
|
|
'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".'
|
|
);
|
|
Konva.Util.warn('Konva is awesome, right?');
|
|
}
|
|
// id selector
|
|
if (sel.charAt(0) === '#') {
|
|
if (this.id() === sel.slice(1)) {
|
|
return true;
|
|
}
|
|
} else if (sel.charAt(0) === '.') {
|
|
// name selector
|
|
if (this.hasName(sel.slice(1))) {
|
|
return true;
|
|
}
|
|
} else if (this._get(sel).length !== 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* get layer ancestor
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Layer}
|
|
*/
|
|
getLayer: function() {
|
|
var parent = this.getParent();
|
|
return parent ? parent.getLayer() : null;
|
|
},
|
|
/**
|
|
* get stage ancestor
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Stage}
|
|
*/
|
|
getStage: function() {
|
|
return this._getCache(STAGE, this._getStage);
|
|
},
|
|
_getStage: function() {
|
|
var parent = this.getParent();
|
|
if (parent) {
|
|
return parent.getStage();
|
|
} else {
|
|
return undefined;
|
|
}
|
|
},
|
|
/**
|
|
* fire event
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent
|
|
* @param {Event} [evt] event object
|
|
* @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event
|
|
* not bubbling. Setting the value to true will result in the event bubbling.
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // manually fire click event
|
|
* node.fire('click');
|
|
*
|
|
* // fire custom event
|
|
* node.fire('foo');
|
|
*
|
|
* // fire custom event with custom event object
|
|
* node.fire('foo', {
|
|
* bar: 10
|
|
* });
|
|
*
|
|
* // fire click event that bubbles
|
|
* node.fire('click', null, true);
|
|
*/
|
|
fire: function(eventType, evt, bubble) {
|
|
evt = evt || {};
|
|
evt.target = evt.target || this;
|
|
// bubble
|
|
if (bubble) {
|
|
this._fireAndBubble(eventType, evt);
|
|
} else {
|
|
// no bubble
|
|
this._fire(eventType, evt);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* get absolute transform of the node which takes into
|
|
* account its ancestor transforms
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Transform}
|
|
*/
|
|
getAbsoluteTransform: function(top) {
|
|
// if using an argument, we can't cache the result.
|
|
if (top) {
|
|
return this._getAbsoluteTransform(top);
|
|
} else {
|
|
// if no argument, we can cache the result
|
|
return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform);
|
|
}
|
|
},
|
|
_getAbsoluteTransform: function(top) {
|
|
var at = new Konva.Transform();
|
|
|
|
// start with stage and traverse downwards to self
|
|
this._eachAncestorReverse(function(node) {
|
|
var transformsEnabled = node.transformsEnabled();
|
|
|
|
if (transformsEnabled === 'all') {
|
|
at.multiply(node.getTransform());
|
|
} else if (transformsEnabled === 'position') {
|
|
at.translate(
|
|
node.getX() - node.getOffsetX(),
|
|
node.getY() - node.getOffsetY()
|
|
);
|
|
}
|
|
}, top);
|
|
return at;
|
|
},
|
|
/**
|
|
* get absolute scale of the node which takes into
|
|
* account its ancestor scales
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Transform}
|
|
*/
|
|
getAbsoluteScale: function(top) {
|
|
// if using an argument, we can't cache the result.
|
|
if (top) {
|
|
return this._getAbsoluteScale(top);
|
|
} else {
|
|
// if no argument, we can cache the result
|
|
return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale);
|
|
}
|
|
},
|
|
_getAbsoluteScale: function(top) {
|
|
// this is special logic for caching with some shapes with shadow
|
|
var parent = this;
|
|
while (parent) {
|
|
if (parent._isUnderCache) {
|
|
top = parent;
|
|
}
|
|
parent = parent.getParent();
|
|
}
|
|
|
|
var scaleX = 1,
|
|
scaleY = 1;
|
|
|
|
// start with stage and traverse downwards to self
|
|
this._eachAncestorReverse(function(node) {
|
|
scaleX *= node.scaleX();
|
|
scaleY *= node.scaleY();
|
|
}, top);
|
|
return {
|
|
x: scaleX,
|
|
y: scaleY
|
|
};
|
|
},
|
|
/**
|
|
* get transform of the node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Transform}
|
|
*/
|
|
getTransform: function() {
|
|
return this._getCache(TRANSFORM, this._getTransform);
|
|
},
|
|
_getTransform: function() {
|
|
var m = new Konva.Transform(),
|
|
x = this.getX(),
|
|
y = this.getY(),
|
|
rotation = Konva.getAngle(this.getRotation()),
|
|
scaleX = this.getScaleX(),
|
|
scaleY = this.getScaleY(),
|
|
skewX = this.getSkewX(),
|
|
skewY = this.getSkewY(),
|
|
offsetX = this.getOffsetX(),
|
|
offsetY = this.getOffsetY();
|
|
|
|
if (x !== 0 || y !== 0) {
|
|
m.translate(x, y);
|
|
}
|
|
if (rotation !== 0) {
|
|
m.rotate(rotation);
|
|
}
|
|
if (skewX !== 0 || skewY !== 0) {
|
|
m.skew(skewX, skewY);
|
|
}
|
|
if (scaleX !== 1 || scaleY !== 1) {
|
|
m.scale(scaleX, scaleY);
|
|
}
|
|
if (offsetX !== 0 || offsetY !== 0) {
|
|
m.translate(-1 * offsetX, -1 * offsetY);
|
|
}
|
|
|
|
return m;
|
|
},
|
|
/**
|
|
* clone node. Returns a new Node instance with identical attributes. You can also override
|
|
* the node properties with an object literal, enabling you to use an existing node as a template
|
|
* for another node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} obj override attrs
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* // simple clone
|
|
* var clone = node.clone();
|
|
*
|
|
* // clone a node and override the x position
|
|
* var clone = rect.clone({
|
|
* x: 5
|
|
* });
|
|
*/
|
|
clone: function(obj) {
|
|
// instantiate new node
|
|
var attrs = Konva.Util.cloneObject(this.attrs),
|
|
key,
|
|
allListeners,
|
|
len,
|
|
n,
|
|
listener;
|
|
// filter black attrs
|
|
for (var i in CLONE_BLACK_LIST) {
|
|
var blockAttr = CLONE_BLACK_LIST[i];
|
|
delete attrs[blockAttr];
|
|
}
|
|
// apply attr overrides
|
|
for (key in obj) {
|
|
attrs[key] = obj[key];
|
|
}
|
|
|
|
var node = new this.constructor(attrs);
|
|
// copy over listeners
|
|
for (key in this.eventListeners) {
|
|
allListeners = this.eventListeners[key];
|
|
len = allListeners.length;
|
|
for (n = 0; n < len; n++) {
|
|
listener = allListeners[n];
|
|
/*
|
|
* don't include konva namespaced listeners because
|
|
* these are generated by the constructors
|
|
*/
|
|
if (listener.name.indexOf(KONVA) < 0) {
|
|
// if listeners array doesn't exist, then create it
|
|
if (!node.eventListeners[key]) {
|
|
node.eventListeners[key] = [];
|
|
}
|
|
node.eventListeners[key].push(listener);
|
|
}
|
|
}
|
|
}
|
|
return node;
|
|
},
|
|
_toKonvaCanvas: function(config) {
|
|
config = config || {};
|
|
|
|
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 Konva.SceneCanvas({
|
|
width: config.width || box.width || (stage ? stage.getWidth() : 0),
|
|
height:
|
|
config.height || box.height || (stage ? stage.getHeight() : 0),
|
|
pixelRatio: pixelRatio
|
|
}),
|
|
context = canvas.getContext();
|
|
|
|
context.save();
|
|
|
|
if (x || y) {
|
|
context.translate(-1 * x, -1 * y);
|
|
}
|
|
|
|
this.drawScene(canvas);
|
|
context.restore();
|
|
|
|
return canvas;
|
|
},
|
|
/**
|
|
* converts node into an canvas element.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} config
|
|
* @param {Function} config.callback function executed when the composite has completed
|
|
* @param {Number} [config.x] x position of canvas section
|
|
* @param {Number} [config.y] y position of canvas section
|
|
* @param {Number} [config.width] width of canvas section
|
|
* @param {Number} [config.height] height of canvas section
|
|
* @paremt {Number} [config.pixelRatio] pixelRatio of ouput image. Default is 1.
|
|
* @example
|
|
* var canvas = node.toCanvas();
|
|
*/
|
|
toCanvas: function(config) {
|
|
return this._toKonvaCanvas(config)._canvas;
|
|
},
|
|
/**
|
|
* Creates a composite data URL. If MIME type is not
|
|
* specified, then "image/png" will result. For "image/jpeg", specify a quality
|
|
* level as quality (range 0.0 - 1.0)
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} config
|
|
* @param {String} [config.mimeType] can be "image/png" or "image/jpeg".
|
|
* "image/png" is the default
|
|
* @param {Number} [config.x] x position of canvas section
|
|
* @param {Number} [config.y] y position of canvas section
|
|
* @param {Number} [config.width] width of canvas section
|
|
* @param {Number} [config.height] height of canvas section
|
|
* @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType,
|
|
* you can specify the quality from 0 to 1, where 0 is very poor quality and 1
|
|
* is very high quality
|
|
* @param {Number} [config.pixelRatio] pixelRatio of output image url. Default is 1
|
|
* @returns {String}
|
|
*/
|
|
toDataURL: function(config) {
|
|
config = config || {};
|
|
var mimeType = config.mimeType || null,
|
|
quality = config.quality || null;
|
|
var url = this._toKonvaCanvas(config).toDataURL(mimeType, quality);
|
|
if (config.callback) {
|
|
config.callback(url);
|
|
}
|
|
return url;
|
|
},
|
|
/**
|
|
* converts node into an image. Since the toImage
|
|
* method is asynchronous, a callback is required. toImage is most commonly used
|
|
* to cache complex drawings as an image so that they don't have to constantly be redrawn
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} config
|
|
* @param {Function} config.callback function executed when the composite has completed
|
|
* @param {String} [config.mimeType] can be "image/png" or "image/jpeg".
|
|
* "image/png" is the default
|
|
* @param {Number} [config.x] x position of canvas section
|
|
* @param {Number} [config.y] y position of canvas section
|
|
* @param {Number} [config.width] width of canvas section
|
|
* @param {Number} [config.height] height of canvas section
|
|
* @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType,
|
|
* you can specify the quality from 0 to 1, where 0 is very poor quality and 1
|
|
* is very high quality
|
|
* @paremt {Number} [config.pixelRatio] pixelRatio of ouput image. Default is 1.
|
|
* @example
|
|
* var image = node.toImage({
|
|
* callback: function(img) {
|
|
* // do stuff with img
|
|
* }
|
|
* });
|
|
*/
|
|
toImage: function(config) {
|
|
if (!config || !config.callback) {
|
|
throw 'callback required for toImage method config argument';
|
|
}
|
|
var callback = config.callback;
|
|
delete config.callback;
|
|
Konva.Util._getImage(this.toDataURL(config), function(img) {
|
|
callback(img);
|
|
});
|
|
},
|
|
setSize: function(size) {
|
|
this.setWidth(size.width);
|
|
this.setHeight(size.height);
|
|
return this;
|
|
},
|
|
getSize: function() {
|
|
return {
|
|
width: this.getWidth(),
|
|
height: this.getHeight()
|
|
};
|
|
},
|
|
getWidth: function() {
|
|
return this.attrs.width || 0;
|
|
},
|
|
getHeight: function() {
|
|
return this.attrs.height || 0;
|
|
},
|
|
/**
|
|
* get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc.
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {String}
|
|
*/
|
|
getClassName: function() {
|
|
return this.className || this.nodeType;
|
|
},
|
|
/**
|
|
* get the node type, which may return Stage, Layer, Group, or Node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {String}
|
|
*/
|
|
getType: function() {
|
|
return this.nodeType;
|
|
},
|
|
getDragDistance: function() {
|
|
// compare with undefined because we need to track 0 value
|
|
if (this.attrs.dragDistance !== undefined) {
|
|
return this.attrs.dragDistance;
|
|
} else if (this.parent) {
|
|
return this.parent.getDragDistance();
|
|
} else {
|
|
return Konva.dragDistance;
|
|
}
|
|
},
|
|
_get: function(selector) {
|
|
return this.className === selector || this.nodeType === selector
|
|
? [this]
|
|
: [];
|
|
},
|
|
_off: function(type, name, callback) {
|
|
var evtListeners = this.eventListeners[type],
|
|
i,
|
|
evtName,
|
|
handler;
|
|
|
|
for (i = 0; i < evtListeners.length; i++) {
|
|
evtName = evtListeners[i].name;
|
|
handler = evtListeners[i].handler;
|
|
|
|
// the following two conditions must be true in order to remove a handler:
|
|
// 1) the current event name cannot be konva unless the event name is konva
|
|
// this enables developers to force remove a konva specific listener for whatever reason
|
|
// 2) an event name is not specified, or if one is specified, it matches the current event name
|
|
if (
|
|
(evtName !== 'konva' || name === 'konva') &&
|
|
(!name || evtName === name) &&
|
|
(!callback || callback === handler)
|
|
) {
|
|
evtListeners.splice(i, 1);
|
|
if (evtListeners.length === 0) {
|
|
delete this.eventListeners[type];
|
|
break;
|
|
}
|
|
i--;
|
|
}
|
|
}
|
|
},
|
|
_fireChangeEvent: function(attr, oldVal, newVal) {
|
|
this._fire(attr + CHANGE, {
|
|
oldVal: oldVal,
|
|
newVal: newVal
|
|
});
|
|
},
|
|
setId: function(id) {
|
|
var oldId = this.getId();
|
|
|
|
Konva._removeId(oldId);
|
|
Konva._addId(this, id);
|
|
this._setAttr(ID, id);
|
|
return this;
|
|
},
|
|
setName: function(name) {
|
|
var oldNames = (this.getName() || '').split(/\s/g);
|
|
var newNames = (name || '').split(/\s/g);
|
|
var subname, i;
|
|
// remove all subnames
|
|
for (i = 0; i < oldNames.length; i++) {
|
|
subname = oldNames[i];
|
|
if (newNames.indexOf(subname) === -1 && subname) {
|
|
Konva._removeName(subname, this._id);
|
|
}
|
|
}
|
|
|
|
// add new names
|
|
for (i = 0; i < newNames.length; i++) {
|
|
subname = newNames[i];
|
|
if (oldNames.indexOf(subname) === -1 && subname) {
|
|
Konva._addName(this, subname);
|
|
}
|
|
}
|
|
|
|
this._setAttr(NAME, name);
|
|
return this;
|
|
},
|
|
// naming methods
|
|
/**
|
|
* add name to node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} name
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* node.name('red');
|
|
* node.addName('selected');
|
|
* node.name(); // return 'red selected'
|
|
*/
|
|
addName: function(name) {
|
|
if (!this.hasName(name)) {
|
|
var oldName = this.name();
|
|
var newName = oldName ? oldName + ' ' + name : name;
|
|
this.setName(newName);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* check is node has name
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} name
|
|
* @returns {Boolean}
|
|
* @example
|
|
* node.name('red');
|
|
* node.hasName('red'); // return true
|
|
* node.hasName('selected'); // return false
|
|
*/
|
|
hasName: function(name) {
|
|
var names = (this.name() || '').split(/\s/g);
|
|
return names.indexOf(name) !== -1;
|
|
},
|
|
/**
|
|
* remove name from node
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} name
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* node.name('red selected');
|
|
* node.removeName('selected');
|
|
* node.hasName('selected'); // return false
|
|
* node.name(); // return 'red'
|
|
*/
|
|
removeName: function(name) {
|
|
var names = (this.name() || '').split(/\s/g);
|
|
var index = names.indexOf(name);
|
|
if (index !== -1) {
|
|
names.splice(index, 1);
|
|
this.setName(names.join(' '));
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* set attr
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} attr
|
|
* @param {*} val
|
|
* @returns {Konva.Node}
|
|
* @example
|
|
* node.setAttr('x', 5);
|
|
*/
|
|
setAttr: function(attr, val) {
|
|
var method = SET + Konva.Util._capitalize(attr),
|
|
func = this[method];
|
|
|
|
if (Konva.Util._isFunction(func)) {
|
|
func.call(this, val);
|
|
} else {
|
|
// otherwise set directly
|
|
this._setAttr(attr, val);
|
|
}
|
|
return this;
|
|
},
|
|
_setAttr: function(key, val) {
|
|
var oldVal;
|
|
oldVal = this.attrs[key];
|
|
var same = oldVal === val;
|
|
if (same && !Konva.Util.isObject(val)) {
|
|
return;
|
|
}
|
|
if (val === undefined || val === null) {
|
|
delete this.attrs[key];
|
|
} else {
|
|
this.attrs[key] = val;
|
|
}
|
|
this._fireChangeEvent(key, oldVal, val);
|
|
},
|
|
_setComponentAttr: function(key, component, val) {
|
|
var oldVal;
|
|
if (val !== undefined) {
|
|
oldVal = this.attrs[key];
|
|
|
|
if (!oldVal) {
|
|
// set value to default value using getAttr
|
|
this.attrs[key] = this.getAttr(key);
|
|
}
|
|
|
|
this.attrs[key][component] = val;
|
|
this._fireChangeEvent(key, oldVal, val);
|
|
}
|
|
},
|
|
_fireAndBubble: function(eventType, evt, compareShape) {
|
|
var okayToRun = true;
|
|
|
|
if (evt && this.nodeType === SHAPE) {
|
|
evt.target = this;
|
|
}
|
|
|
|
if (
|
|
eventType === MOUSEENTER &&
|
|
compareShape &&
|
|
(this._id === compareShape._id ||
|
|
(this.isAncestorOf && this.isAncestorOf(compareShape)))
|
|
) {
|
|
okayToRun = false;
|
|
} else if (
|
|
eventType === MOUSELEAVE &&
|
|
compareShape &&
|
|
(this._id === compareShape._id ||
|
|
(this.isAncestorOf && this.isAncestorOf(compareShape)))
|
|
) {
|
|
okayToRun = false;
|
|
}
|
|
if (okayToRun) {
|
|
this._fire(eventType, evt);
|
|
|
|
// simulate event bubbling
|
|
var stopBubble =
|
|
(eventType === MOUSEENTER || eventType === MOUSELEAVE) &&
|
|
(compareShape &&
|
|
compareShape.isAncestorOf &&
|
|
compareShape.isAncestorOf(this) &&
|
|
!compareShape.isAncestorOf(this.parent));
|
|
if (
|
|
((evt && !evt.cancelBubble) || !evt) &&
|
|
this.parent &&
|
|
this.parent.isListening() &&
|
|
!stopBubble
|
|
) {
|
|
if (compareShape && compareShape.parent) {
|
|
this._fireAndBubble.call(
|
|
this.parent,
|
|
eventType,
|
|
evt,
|
|
compareShape.parent
|
|
);
|
|
} else {
|
|
this._fireAndBubble.call(this.parent, eventType, evt);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_fire: function(eventType, evt) {
|
|
var events = this.eventListeners[eventType],
|
|
i;
|
|
|
|
evt = evt || {};
|
|
evt.currentTarget = this;
|
|
evt.type = eventType;
|
|
|
|
if (events) {
|
|
for (i = 0; i < events.length; i++) {
|
|
events[i].handler.call(this, evt);
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Konva.Node}
|
|
*/
|
|
draw: function() {
|
|
this.drawScene();
|
|
this.drawHit();
|
|
return this;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* create node with JSON string or an Object. De-serializtion does not generate custom
|
|
* shape drawing functions, images, or event handlers (this would make the
|
|
* serialized object huge). If your app uses custom shapes, images, and
|
|
* event handlers (it probably does), then you need to select the appropriate
|
|
* shapes after loading the stage and set these properties via on(), setSceneFunc(),
|
|
* and setImage() methods
|
|
* @method
|
|
* @memberof Konva.Node
|
|
* @param {String|Object} json string or object
|
|
* @param {Element} [container] optional container dom element used only if you're
|
|
* creating a stage node
|
|
*/
|
|
Konva.Node.create = function(data, container) {
|
|
if (Konva.Util._isString(data)) {
|
|
data = JSON.parse(data);
|
|
}
|
|
return this._createNode(data, container);
|
|
};
|
|
Konva.Node._createNode = function(obj, container) {
|
|
var className = Konva.Node.prototype.getClassName.call(obj),
|
|
children = obj.children,
|
|
no,
|
|
len,
|
|
n;
|
|
|
|
// if container was passed in, add it to attrs
|
|
if (container) {
|
|
obj.attrs.container = container;
|
|
}
|
|
|
|
no = new Konva[className](obj.attrs);
|
|
if (children) {
|
|
len = children.length;
|
|
for (n = 0; n < len; n++) {
|
|
no.add(this._createNode(children[n]));
|
|
}
|
|
}
|
|
|
|
return no;
|
|
};
|
|
|
|
// =========================== add getters setters ===========================
|
|
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position');
|
|
/**
|
|
* get/set node position relative to parent
|
|
* @name position
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} pos
|
|
* @param {Number} pos.x
|
|
* @param {Number} pos.y
|
|
* @returns {Object}
|
|
* @example
|
|
* // get position
|
|
* var position = node.position();
|
|
*
|
|
* // set position
|
|
* node.position({
|
|
* x: 5
|
|
* y: 10
|
|
* });
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'x',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set x position
|
|
* @name x
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} x
|
|
* @returns {Object}
|
|
* @example
|
|
* // get x
|
|
* var x = node.x();
|
|
*
|
|
* // set x
|
|
* node.x(5);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'y',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set y position
|
|
* @name y
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} y
|
|
* @returns {Integer}
|
|
* @example
|
|
* // get y
|
|
* var y = node.y();
|
|
*
|
|
* // set y
|
|
* node.y(5);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'globalCompositeOperation',
|
|
'source-over',
|
|
Konva.Validators.getStringValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set globalCompositeOperation of a shape
|
|
* @name globalCompositeOperation
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} type
|
|
* @returns {String}
|
|
* @example
|
|
* // get globalCompositeOperation
|
|
* var globalCompositeOperation = shape.globalCompositeOperation();
|
|
*
|
|
* // set globalCompositeOperation
|
|
* shape.globalCompositeOperation('source-in');
|
|
*/
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'opacity',
|
|
1,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set opacity. Opacity values range from 0 to 1.
|
|
* A node with an opacity of 0 is fully transparent, and a node
|
|
* with an opacity of 1 is fully opaque
|
|
* @name opacity
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} opacity
|
|
* @returns {Number}
|
|
* @example
|
|
* // get opacity
|
|
* var opacity = node.opacity();
|
|
*
|
|
* // set opacity
|
|
* node.opacity(0.5);
|
|
*/
|
|
|
|
Konva.Factory.addGetter(Konva.Node, 'name');
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name');
|
|
|
|
/**
|
|
* get/set name
|
|
* @name name
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} name
|
|
* @returns {String}
|
|
* @example
|
|
* // get name
|
|
* var name = node.name();
|
|
*
|
|
* // set name
|
|
* node.name('foo');
|
|
*
|
|
* // also node may have multiple names (as css classes)
|
|
* node.name('foo bar');
|
|
*/
|
|
|
|
Konva.Factory.addGetter(Konva.Node, 'id');
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id');
|
|
|
|
/**
|
|
* get/set id. Id is global for whole page.
|
|
* @name id
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} id
|
|
* @returns {String}
|
|
* @example
|
|
* // get id
|
|
* var name = node.id();
|
|
*
|
|
* // set id
|
|
* node.id('foo');
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'rotation',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set rotation in degrees
|
|
* @name rotation
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} rotation
|
|
* @returns {Number}
|
|
* @example
|
|
* // get rotation in degrees
|
|
* var rotation = node.rotation();
|
|
*
|
|
* // set rotation in degrees
|
|
* node.rotation(45);
|
|
*/
|
|
|
|
Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']);
|
|
|
|
/**
|
|
* get/set scale
|
|
* @name scale
|
|
* @param {Object} scale
|
|
* @param {Number} scale.x
|
|
* @param {Number} scale.y
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Object}
|
|
* @example
|
|
* // get scale
|
|
* var scale = node.scale();
|
|
*
|
|
* // set scale
|
|
* shape.scale({
|
|
* x: 2
|
|
* y: 3
|
|
* });
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'scaleX',
|
|
1,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set scale x
|
|
* @name scaleX
|
|
* @param {Number} x
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Number}
|
|
* @example
|
|
* // get scale x
|
|
* var scaleX = node.scaleX();
|
|
*
|
|
* // set scale x
|
|
* node.scaleX(2);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'scaleY',
|
|
1,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set scale y
|
|
* @name scaleY
|
|
* @param {Number} y
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Number}
|
|
* @example
|
|
* // get scale y
|
|
* var scaleY = node.scaleY();
|
|
*
|
|
* // set scale y
|
|
* node.scaleY(2);
|
|
*/
|
|
|
|
Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']);
|
|
|
|
/**
|
|
* get/set skew
|
|
* @name skew
|
|
* @param {Object} skew
|
|
* @param {Number} skew.x
|
|
* @param {Number} skew.y
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Object}
|
|
* @example
|
|
* // get skew
|
|
* var skew = node.skew();
|
|
*
|
|
* // set skew
|
|
* node.skew({
|
|
* x: 20
|
|
* y: 10
|
|
* });
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'skewX',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set skew x
|
|
* @name skewX
|
|
* @param {Number} x
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Number}
|
|
* @example
|
|
* // get skew x
|
|
* var skewX = node.skewX();
|
|
*
|
|
* // set skew x
|
|
* node.skewX(3);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'skewY',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set skew y
|
|
* @name skewY
|
|
* @param {Number} y
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @returns {Number}
|
|
* @example
|
|
* // get skew y
|
|
* var skewY = node.skewY();
|
|
*
|
|
* // set skew y
|
|
* node.skewY(3);
|
|
*/
|
|
|
|
Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']);
|
|
|
|
/**
|
|
* get/set offset. Offsets the default position and rotation point
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} offset
|
|
* @param {Number} offset.x
|
|
* @param {Number} offset.y
|
|
* @returns {Object}
|
|
* @example
|
|
* // get offset
|
|
* var offset = node.offset();
|
|
*
|
|
* // set offset
|
|
* node.offset({
|
|
* x: 20
|
|
* y: 10
|
|
* });
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'offsetX',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set offset x
|
|
* @name offsetX
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} x
|
|
* @returns {Number}
|
|
* @example
|
|
* // get offset x
|
|
* var offsetX = node.offsetX();
|
|
*
|
|
* // set offset x
|
|
* node.offsetX(3);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'offsetY',
|
|
0,
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set offset y
|
|
* @name offsetY
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} y
|
|
* @returns {Number}
|
|
* @example
|
|
* // get offset y
|
|
* var offsetY = node.offsetY();
|
|
*
|
|
* // set offset y
|
|
* node.offsetY(3);
|
|
*/
|
|
|
|
Konva.Factory.addSetter(
|
|
Konva.Node,
|
|
'dragDistance',
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance');
|
|
|
|
/**
|
|
* get/set drag distance
|
|
* @name dragDistance
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} distance
|
|
* @returns {Number}
|
|
* @example
|
|
* // get drag distance
|
|
* var dragDistance = node.dragDistance();
|
|
*
|
|
* // set distance
|
|
* // node starts dragging only if pointer moved more then 3 pixels
|
|
* node.dragDistance(3);
|
|
* // or set globally
|
|
* Konva.dragDistance = 3;
|
|
*/
|
|
|
|
Konva.Factory.addSetter(
|
|
Konva.Node,
|
|
'width',
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width');
|
|
/**
|
|
* get/set width
|
|
* @name width
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} width
|
|
* @returns {Number}
|
|
* @example
|
|
* // get width
|
|
* var width = node.width();
|
|
*
|
|
* // set width
|
|
* node.width(100);
|
|
*/
|
|
|
|
Konva.Factory.addSetter(
|
|
Konva.Node,
|
|
'height',
|
|
Konva.Validators.getNumberValidator()
|
|
);
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height');
|
|
/**
|
|
* get/set height
|
|
* @name height
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} height
|
|
* @returns {Number}
|
|
* @example
|
|
* // get height
|
|
* var height = node.height();
|
|
*
|
|
* // set height
|
|
* node.height(100);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit', function(
|
|
val
|
|
) {
|
|
var isValid = val === true || val === false || val === 'inherit';
|
|
if (!isValid) {
|
|
Konva.Util.warn(
|
|
val +
|
|
' is a not valid value for "listening" attribute. The value may be true, false or "inherit".'
|
|
);
|
|
}
|
|
return val;
|
|
});
|
|
/**
|
|
* get/set listenig attr. If you need to determine if a node is listening or not
|
|
* by taking into account its parents, use the isListening() method
|
|
* @name listening
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Boolean|String} listening Can be "inherit", true, or false. The default is "inherit".
|
|
* @returns {Boolean|String}
|
|
* @example
|
|
* // get listening attr
|
|
* var listening = node.listening();
|
|
*
|
|
* // stop listening for events
|
|
* node.listening(false);
|
|
*
|
|
* // listen for events
|
|
* node.listening(true);
|
|
*
|
|
* // listen to events according to the parent
|
|
* node.listening('inherit');
|
|
*/
|
|
|
|
/**
|
|
* get/set preventDefault
|
|
* By default all shapes will prevent default behaviour
|
|
* of a browser on a pointer move or tap.
|
|
* that will prevent native scrolling when you are trying to drag&drop a node
|
|
* but sometimes you may need to enable default actions
|
|
* in that case you can set the property to false
|
|
* @name preventDefault
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Number} preventDefault
|
|
* @returns {Number}
|
|
* @example
|
|
* // get preventDefault
|
|
* var shouldPrevent = shape.preventDefault();
|
|
*
|
|
* // set preventDefault
|
|
* shape.preventDefault(false);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'preventDefault',
|
|
true,
|
|
Konva.Validators.getBooleanValidator()
|
|
);
|
|
|
|
Konva.Factory.addGetterSetter(Konva.Node, 'filters', null, function(val) {
|
|
this._filterUpToDate = false;
|
|
return val;
|
|
});
|
|
/**
|
|
* get/set filters. Filters are applied to cached canvases
|
|
* @name filters
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Array} filters array of filters
|
|
* @returns {Array}
|
|
* @example
|
|
* // get filters
|
|
* var filters = node.filters();
|
|
*
|
|
* // set a single filter
|
|
* node.cache();
|
|
* node.filters([Konva.Filters.Blur]);
|
|
*
|
|
* // set multiple filters
|
|
* node.cache();
|
|
* node.filters([
|
|
* Konva.Filters.Blur,
|
|
* Konva.Filters.Sepia,
|
|
* Konva.Filters.Invert
|
|
* ]);
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit', function(
|
|
val
|
|
) {
|
|
var isValid = val === true || val === false || val === 'inherit';
|
|
if (!isValid) {
|
|
Konva.Util.warn(
|
|
val +
|
|
' is a not valid value for "visible" attribute. The value may be true, false or "inherit".'
|
|
);
|
|
}
|
|
return val;
|
|
});
|
|
/**
|
|
* get/set visible attr. Can be "inherit", true, or false. The default is "inherit".
|
|
* If you need to determine if a node is visible or not
|
|
* by taking into account its parents, use the isVisible() method
|
|
* @name visible
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Boolean|String} visible
|
|
* @returns {Boolean|String}
|
|
* @example
|
|
* // get visible attr
|
|
* var visible = node.visible();
|
|
*
|
|
* // make invisible
|
|
* node.visible(false);
|
|
*
|
|
* // make visible
|
|
* node.visible(true);
|
|
*
|
|
* // make visible according to the parent
|
|
* node.visible('inherit');
|
|
*/
|
|
|
|
Konva.Factory.addGetterSetter(
|
|
Konva.Node,
|
|
'transformsEnabled',
|
|
'all',
|
|
Konva.Validators.getStringValidator()
|
|
);
|
|
|
|
/**
|
|
* get/set transforms that are enabled. Can be "all", "none", or "position". The default
|
|
* is "all"
|
|
* @name transformsEnabled
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {String} enabled
|
|
* @returns {String}
|
|
* @example
|
|
* // enable position transform only to improve draw performance
|
|
* node.transformsEnabled('position');
|
|
*
|
|
* // enable all transforms
|
|
* node.transformsEnabled('all');
|
|
*/
|
|
|
|
/**
|
|
* get/set node size
|
|
* @name size
|
|
* @method
|
|
* @memberof Konva.Node.prototype
|
|
* @param {Object} size
|
|
* @param {Number} size.width
|
|
* @param {Number} size.height
|
|
* @returns {Object}
|
|
* @example
|
|
* // get node size
|
|
* var size = node.size();
|
|
* var x = size.x;
|
|
* var y = size.y;
|
|
*
|
|
* // set size
|
|
* node.size({
|
|
* width: 100,
|
|
* height: 200
|
|
* });
|
|
*/
|
|
Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size');
|
|
|
|
Konva.Factory.backCompat(Konva.Node, {
|
|
rotateDeg: 'rotate',
|
|
setRotationDeg: 'setRotation',
|
|
getRotationDeg: 'getRotation'
|
|
});
|
|
|
|
Konva.Collection.mapMethods(Konva.Node);
|
|
})(Konva);
|