konva
Version:
<p align="center"> <img src="https://raw.githubusercontent.com/konvajs/konvajs.github.io/master/apple-touch-icon-180x180.png" alt="Konva logo" height="180" /> </p>
1,842 lines (1,762 loc) • 575 kB
JavaScript
/*
* Konva JavaScript Framework v2.0.2
* http://konvajs.github.io/
* Licensed under the MIT
* Date: Fri Mar 16 2018
*
* Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS)
* Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva)
*
* @license
*/
// runtime check for already included Konva
(function() {
'use strict';
/**
* @namespace Konva
*/
var PI_OVER_180 = Math.PI / 180;
var Konva = {
// public
version: '2.0.2',
// private
stages: [],
idCounter: 0,
ids: {},
names: {},
shapes: {},
listenClickTap: false,
inDblClickWindow: false,
isBrowser:
typeof window !== 'undefined' &&
// browser case
({}.toString.call(window) === '[object Window]' ||
// electron case
{}.toString.call(window) === '[object global]'),
// configurations
enableTrace: false,
traceArrMax: 100,
dblClickWindow: 400,
/**
* Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device.
* But you may override such property, if you want to use your value.
* @property pixelRatio
* @default undefined
* @memberof Konva
* @example
* Konva.pixelRatio = 1;
*/
pixelRatio: undefined,
/**
* Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point,
* only then start dragging. Default is 3px.
* @property dragDistance
* @default 0
* @memberof Konva
* @example
* Konva.dragDistance = 10;
*/
dragDistance: 3,
/**
* Use degree values for angle properties. You may set this property to false if you want to use radiant values.
* @property angleDeg
* @default true
* @memberof Konva
* @example
* node.rotation(45); // 45 degrees
* Konva.angleDeg = false;
* node.rotation(Math.PI / 2); // PI/2 radian
*/
angleDeg: true,
/**
* Show different warnings about errors or wrong API usage
* @property showWarnings
* @default true
* @memberof Konva
* @example
* Konva.showWarnings = false;
*/
showWarnings: true,
/**
* @namespace Filters
* @memberof Konva
*/
Filters: {},
/**
* returns whether or not drag and drop is currently active
* @method
* @memberof Konva
*/
isDragging: function() {
var dd = Konva.DD;
// if DD is not included with the build, then
// drag and drop is not even possible
if (dd) {
return dd.isDragging;
}
return false;
},
/**
* returns whether or not a drag and drop operation is ready, but may
* not necessarily have started
* @method
* @memberof Konva
*/
isDragReady: function() {
var dd = Konva.DD;
// if DD is not included with the build, then
// drag and drop is not even possible
if (dd) {
return !!dd.node;
}
return false;
},
_addId: function(node, id) {
if (id !== undefined) {
this.ids[id] = node;
}
},
_removeId: function(id) {
if (id !== undefined) {
delete this.ids[id];
}
},
_addName: function(node, name) {
if (name) {
if (!this.names[name]) {
this.names[name] = [];
}
this.names[name].push(node);
}
},
_removeName: function(name, _id) {
if (!name) {
return;
}
var nodes = this.names[name];
if (!nodes) {
return;
}
for (var n = 0; n < nodes.length; n++) {
var no = nodes[n];
if (no._id === _id) {
nodes.splice(n, 1);
}
}
if (nodes.length === 0) {
delete this.names[name];
}
},
getAngle: function(angle) {
return this.angleDeg ? angle * PI_OVER_180 : angle;
},
_detectIE: function(ua) {
var msie = ua.indexOf('msie ');
if (msie > 0) {
// IE 10 or older => return version number
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
var trident = ua.indexOf('trident/');
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('edge/');
if (edge > 0) {
// Edge (IE 12+) => return version number
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
}
// other browser
return false;
},
_parseUA: function(userAgent) {
var ua = userAgent.toLowerCase(),
// jQuery UA regex
match =
/(chrome)[ /]([\w.]+)/.exec(ua) ||
/(webkit)[ /]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ /]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
(ua.indexOf('compatible') < 0 &&
/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)) ||
[],
// adding mobile flag as well
mobile = !!userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i
),
ieMobile = !!userAgent.match(/IEMobile/i);
return {
browser: match[1] || '',
version: match[2] || '0',
isIE: Konva._detectIE(ua),
// adding mobile flab
mobile: mobile,
ieMobile: ieMobile // If this is true (i.e., WP8), then Konva touch events are executed instead of equivalent Konva mouse events
};
},
// user agent
UA: undefined
};
var glob =
typeof global !== 'undefined'
? global
: typeof window !== 'undefined'
? window
: typeof WorkerGlobalScope !== 'undefined' ? self : {};
Konva.UA = Konva._parseUA((glob.navigator && glob.navigator.userAgent) || '');
if (glob.Konva) {
console.error(
'Konva instance is already exist in current eviroment. ' +
'Please use only one instance.'
);
}
glob.Konva = Konva;
Konva.global = glob;
Konva.window = glob;
Konva.document = glob.document;
if (typeof exports === 'object') {
module.exports = Konva;
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
return Konva;
});
}
})();
/*eslint-disable eqeqeq, no-cond-assign, no-empty*/
(function() {
'use strict';
/**
* Collection constructor. Collection extends
* Array. This class is used in conjunction with {@link Konva.Container#get}
* @constructor
* @memberof Konva
*/
Konva.Collection = function() {
var args = [].slice.call(arguments),
length = args.length,
i = 0;
this.length = length;
for (; i < length; i++) {
this[i] = args[i];
}
return this;
};
Konva.Collection.prototype = [];
/**
* iterate through node array and run a function for each node.
* The node and index is passed into the function
* @method
* @memberof Konva.Collection.prototype
* @param {Function} func
* @example
* // get all nodes with name foo inside layer, and set x to 10 for each
* layer.get('.foo').each(function(shape, n) {
* shape.setX(10);
* });
*/
Konva.Collection.prototype.each = function(func) {
for (var n = 0; n < this.length; n++) {
func(this[n], n);
}
};
/**
* convert collection into an array
* @method
* @memberof Konva.Collection.prototype
*/
Konva.Collection.prototype.toArray = function() {
var arr = [],
len = this.length,
n;
for (n = 0; n < len; n++) {
arr.push(this[n]);
}
return arr;
};
/**
* convert array into a collection
* @method
* @memberof Konva.Collection
* @param {Array} arr
*/
Konva.Collection.toCollection = function(arr) {
var collection = new Konva.Collection(),
len = arr.length,
n;
for (n = 0; n < len; n++) {
collection.push(arr[n]);
}
return collection;
};
// map one method by it's name
Konva.Collection._mapMethod = function(methodName) {
Konva.Collection.prototype[methodName] = function() {
var len = this.length,
i;
var args = [].slice.call(arguments);
for (i = 0; i < len; i++) {
this[i][methodName].apply(this[i], args);
}
return this;
};
};
Konva.Collection.mapMethods = function(constructor) {
var prot = constructor.prototype;
for (var methodName in prot) {
Konva.Collection._mapMethod(methodName);
}
};
/*
* Last updated November 2011
* By Simon Sarris
* www.simonsarris.com
* sarris@acm.org
*
* Free to use and distribute at will
* So long as you are nice to people, etc
*/
/*
* The usage of this class was inspired by some of the work done by a forked
* project, KineticJS-Ext by Wappworks, which is based on Simon's Transform
* class. Modified by Eric Rowell
*/
/**
* Transform constructor
* @constructor
* @param {Array} [m] Optional six-element matrix
* @memberof Konva
*/
Konva.Transform = function(m) {
this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0];
};
Konva.Transform.prototype = {
/**
* Copy Konva.Transform object
* @method
* @memberof Konva.Transform.prototype
* @returns {Konva.Transform}
*/
copy: function() {
return new Konva.Transform(this.m);
},
/**
* Transform point
* @method
* @memberof Konva.Transform.prototype
* @param {Object} point 2D point(x, y)
* @returns {Object} 2D point(x, y)
*/
point: function(point) {
var m = this.m;
return {
x: m[0] * point.x + m[2] * point.y + m[4],
y: m[1] * point.x + m[3] * point.y + m[5]
};
},
/**
* Apply translation
* @method
* @memberof Konva.Transform.prototype
* @param {Number} x
* @param {Number} y
* @returns {Konva.Transform}
*/
translate: function(x, y) {
this.m[4] += this.m[0] * x + this.m[2] * y;
this.m[5] += this.m[1] * x + this.m[3] * y;
return this;
},
/**
* Apply scale
* @method
* @memberof Konva.Transform.prototype
* @param {Number} sx
* @param {Number} sy
* @returns {Konva.Transform}
*/
scale: function(sx, sy) {
this.m[0] *= sx;
this.m[1] *= sx;
this.m[2] *= sy;
this.m[3] *= sy;
return this;
},
/**
* Apply rotation
* @method
* @memberof Konva.Transform.prototype
* @param {Number} rad Angle in radians
* @returns {Konva.Transform}
*/
rotate: function(rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m11 = this.m[0] * c + this.m[2] * s;
var m12 = this.m[1] * c + this.m[3] * s;
var m21 = this.m[0] * -s + this.m[2] * c;
var m22 = this.m[1] * -s + this.m[3] * c;
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
return this;
},
/**
* Returns the translation
* @method
* @memberof Konva.Transform.prototype
* @returns {Object} 2D point(x, y)
*/
getTranslation: function() {
return {
x: this.m[4],
y: this.m[5]
};
},
/**
* Apply skew
* @method
* @memberof Konva.Transform.prototype
* @param {Number} sx
* @param {Number} sy
* @returns {Konva.Transform}
*/
skew: function(sx, sy) {
var m11 = this.m[0] + this.m[2] * sy;
var m12 = this.m[1] + this.m[3] * sy;
var m21 = this.m[2] + this.m[0] * sx;
var m22 = this.m[3] + this.m[1] * sx;
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
return this;
},
/**
* Transform multiplication
* @method
* @memberof Konva.Transform.prototype
* @param {Konva.Transform} matrix
* @returns {Konva.Transform}
*/
multiply: function(matrix) {
var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
this.m[4] = dx;
this.m[5] = dy;
return this;
},
/**
* Invert the matrix
* @method
* @memberof Konva.Transform.prototype
* @returns {Konva.Transform}
*/
invert: function() {
var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]);
var m0 = this.m[3] * d;
var m1 = -this.m[1] * d;
var m2 = -this.m[2] * d;
var m3 = this.m[0] * d;
var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]);
var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]);
this.m[0] = m0;
this.m[1] = m1;
this.m[2] = m2;
this.m[3] = m3;
this.m[4] = m4;
this.m[5] = m5;
return this;
},
/**
* return matrix
* @method
* @memberof Konva.Transform.prototype
*/
getMatrix: function() {
return this.m;
},
/**
* set to absolute position via translation
* @method
* @memberof Konva.Transform.prototype
* @returns {Konva.Transform}
* @author ericdrowell
*/
setAbsolutePosition: function(x, y) {
var m0 = this.m[0],
m1 = this.m[1],
m2 = this.m[2],
m3 = this.m[3],
m4 = this.m[4],
m5 = this.m[5],
yt = (m0 * (y - m5) - m1 * (x - m4)) / (m0 * m3 - m1 * m2),
xt = (x - m4 - m2 * yt) / m0;
return this.translate(xt, yt);
}
};
// CONSTANTS
var CONTEXT_2D = '2d',
OBJECT_ARRAY = '[object Array]',
OBJECT_NUMBER = '[object Number]',
OBJECT_STRING = '[object String]',
PI_OVER_DEG180 = Math.PI / 180,
DEG180_OVER_PI = 180 / Math.PI,
HASH = '#',
EMPTY_STRING = '',
ZERO = '0',
KONVA_WARNING = 'Konva warning: ',
KONVA_ERROR = 'Konva error: ',
RGB_PAREN = 'rgb(',
COLORS = {
aliceblue: [240, 248, 255],
antiquewhite: [250, 235, 215],
aqua: [0, 255, 255],
aquamarine: [127, 255, 212],
azure: [240, 255, 255],
beige: [245, 245, 220],
bisque: [255, 228, 196],
black: [0, 0, 0],
blanchedalmond: [255, 235, 205],
blue: [0, 0, 255],
blueviolet: [138, 43, 226],
brown: [165, 42, 42],
burlywood: [222, 184, 135],
cadetblue: [95, 158, 160],
chartreuse: [127, 255, 0],
chocolate: [210, 105, 30],
coral: [255, 127, 80],
cornflowerblue: [100, 149, 237],
cornsilk: [255, 248, 220],
crimson: [220, 20, 60],
cyan: [0, 255, 255],
darkblue: [0, 0, 139],
darkcyan: [0, 139, 139],
darkgoldenrod: [184, 132, 11],
darkgray: [169, 169, 169],
darkgreen: [0, 100, 0],
darkgrey: [169, 169, 169],
darkkhaki: [189, 183, 107],
darkmagenta: [139, 0, 139],
darkolivegreen: [85, 107, 47],
darkorange: [255, 140, 0],
darkorchid: [153, 50, 204],
darkred: [139, 0, 0],
darksalmon: [233, 150, 122],
darkseagreen: [143, 188, 143],
darkslateblue: [72, 61, 139],
darkslategray: [47, 79, 79],
darkslategrey: [47, 79, 79],
darkturquoise: [0, 206, 209],
darkviolet: [148, 0, 211],
deeppink: [255, 20, 147],
deepskyblue: [0, 191, 255],
dimgray: [105, 105, 105],
dimgrey: [105, 105, 105],
dodgerblue: [30, 144, 255],
firebrick: [178, 34, 34],
floralwhite: [255, 255, 240],
forestgreen: [34, 139, 34],
fuchsia: [255, 0, 255],
gainsboro: [220, 220, 220],
ghostwhite: [248, 248, 255],
gold: [255, 215, 0],
goldenrod: [218, 165, 32],
gray: [128, 128, 128],
green: [0, 128, 0],
greenyellow: [173, 255, 47],
grey: [128, 128, 128],
honeydew: [240, 255, 240],
hotpink: [255, 105, 180],
indianred: [205, 92, 92],
indigo: [75, 0, 130],
ivory: [255, 255, 240],
khaki: [240, 230, 140],
lavender: [230, 230, 250],
lavenderblush: [255, 240, 245],
lawngreen: [124, 252, 0],
lemonchiffon: [255, 250, 205],
lightblue: [173, 216, 230],
lightcoral: [240, 128, 128],
lightcyan: [224, 255, 255],
lightgoldenrodyellow: [250, 250, 210],
lightgray: [211, 211, 211],
lightgreen: [144, 238, 144],
lightgrey: [211, 211, 211],
lightpink: [255, 182, 193],
lightsalmon: [255, 160, 122],
lightseagreen: [32, 178, 170],
lightskyblue: [135, 206, 250],
lightslategray: [119, 136, 153],
lightslategrey: [119, 136, 153],
lightsteelblue: [176, 196, 222],
lightyellow: [255, 255, 224],
lime: [0, 255, 0],
limegreen: [50, 205, 50],
linen: [250, 240, 230],
magenta: [255, 0, 255],
maroon: [128, 0, 0],
mediumaquamarine: [102, 205, 170],
mediumblue: [0, 0, 205],
mediumorchid: [186, 85, 211],
mediumpurple: [147, 112, 219],
mediumseagreen: [60, 179, 113],
mediumslateblue: [123, 104, 238],
mediumspringgreen: [0, 250, 154],
mediumturquoise: [72, 209, 204],
mediumvioletred: [199, 21, 133],
midnightblue: [25, 25, 112],
mintcream: [245, 255, 250],
mistyrose: [255, 228, 225],
moccasin: [255, 228, 181],
navajowhite: [255, 222, 173],
navy: [0, 0, 128],
oldlace: [253, 245, 230],
olive: [128, 128, 0],
olivedrab: [107, 142, 35],
orange: [255, 165, 0],
orangered: [255, 69, 0],
orchid: [218, 112, 214],
palegoldenrod: [238, 232, 170],
palegreen: [152, 251, 152],
paleturquoise: [175, 238, 238],
palevioletred: [219, 112, 147],
papayawhip: [255, 239, 213],
peachpuff: [255, 218, 185],
peru: [205, 133, 63],
pink: [255, 192, 203],
plum: [221, 160, 203],
powderblue: [176, 224, 230],
purple: [128, 0, 128],
rebeccapurple: [102, 51, 153],
red: [255, 0, 0],
rosybrown: [188, 143, 143],
royalblue: [65, 105, 225],
saddlebrown: [139, 69, 19],
salmon: [250, 128, 114],
sandybrown: [244, 164, 96],
seagreen: [46, 139, 87],
seashell: [255, 245, 238],
sienna: [160, 82, 45],
silver: [192, 192, 192],
skyblue: [135, 206, 235],
slateblue: [106, 90, 205],
slategray: [119, 128, 144],
slategrey: [119, 128, 144],
snow: [255, 255, 250],
springgreen: [0, 255, 127],
steelblue: [70, 130, 180],
tan: [210, 180, 140],
teal: [0, 128, 128],
thistle: [216, 191, 216],
transparent: [255, 255, 255, 0],
tomato: [255, 99, 71],
turquoise: [64, 224, 208],
violet: [238, 130, 238],
wheat: [245, 222, 179],
white: [255, 255, 255],
whitesmoke: [245, 245, 245],
yellow: [255, 255, 0],
yellowgreen: [154, 205, 5]
},
RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/;
/**
* @namespace Util
* @memberof Konva
*/
Konva.Util = {
/*
* cherry-picked utilities from underscore.js
*/
_isElement: function(obj) {
return !!(obj && obj.nodeType == 1);
},
_isFunction: function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
},
_isObject: function(obj) {
return !!obj && obj.constructor === Object;
},
_isArray: function(obj) {
return Object.prototype.toString.call(obj) === OBJECT_ARRAY;
},
_isNumber: function(obj) {
return Object.prototype.toString.call(obj) === OBJECT_NUMBER;
},
_isString: function(obj) {
return Object.prototype.toString.call(obj) === OBJECT_STRING;
},
isValidSelector: function(selector) {
if (typeof selector !== 'string') {
return false;
}
var firstChar = selector[0];
return (
firstChar === '#' ||
firstChar === '.' ||
firstChar === firstChar.toUpperCase()
);
},
createCanvasElement: function() {
var canvas = Konva.isBrowser
? Konva.document.createElement('canvas')
: new Konva._nodeCanvas();
// on some environments canvas.style is readonly
try {
canvas.style = canvas.style || {};
} catch (e) {}
return canvas;
},
_isInDocument: function(el) {
while ((el = el.parentNode)) {
if (el == Konva.document) {
return true;
}
}
return false;
},
_simplifyArray: function(arr) {
var retArr = [],
len = arr.length,
util = Konva.Util,
n,
val;
for (n = 0; n < len; n++) {
val = arr[n];
if (util._isNumber(val)) {
val = Math.round(val * 1000) / 1000;
} else if (!util._isString(val)) {
val = val.toString();
}
retArr.push(val);
}
return retArr;
},
/*
* arg can be an image object or image data
*/
_getImage: function(arg, callback) {
var imageObj, canvas;
// if arg is null or undefined
if (!arg) {
callback(null);
} else if (this._isElement(arg)) {
// if arg is already an image object
callback(arg);
} else if (this._isString(arg)) {
// if arg is a string, then it's a data url
imageObj = new Konva.window.Image();
imageObj.onload = function() {
callback(imageObj);
};
imageObj.src = arg;
} else if (arg.data) {
//if arg is an object that contains the data property, it's an image object
canvas = Konva.Util.createCanvasElement();
canvas.width = arg.width;
canvas.height = arg.height;
var _context = canvas.getContext(CONTEXT_2D);
_context.putImageData(arg, 0, 0);
this._getImage(canvas.toDataURL(), callback);
} else {
callback(null);
}
},
_getRGBAString: function(obj) {
var red = obj.red || 0,
green = obj.green || 0,
blue = obj.blue || 0,
alpha = obj.alpha || 1;
return ['rgba(', red, ',', green, ',', blue, ',', alpha, ')'].join(
EMPTY_STRING
);
},
_rgbToHex: function(r, g, b) {
return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
},
_hexToRgb: function(hex) {
hex = hex.replace(HASH, EMPTY_STRING);
var bigint = parseInt(hex, 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255
};
},
/**
* return random hex color
* @method
* @memberof Konva.Util.prototype
*/
getRandomColor: function() {
var randColor = ((Math.random() * 0xffffff) << 0).toString(16);
while (randColor.length < 6) {
randColor = ZERO + randColor;
}
return HASH + randColor;
},
/**
* return value with default fallback
* @method
* @memberof Konva.Util.prototype
*/
get: function(val, def) {
if (val === undefined) {
return def;
} else {
return val;
}
},
/**
* get RGB components of a color
* @method
* @memberof Konva.Util.prototype
* @param {String} color
* @example
* // each of the following examples return {r:0, g:0, b:255}
* var rgb = Konva.Util.getRGB('blue');
* var rgb = Konva.Util.getRGB('#0000ff');
* var rgb = Konva.Util.getRGB('rgb(0,0,255)');
*/
getRGB: function(color) {
var rgb;
// color string
if (color in COLORS) {
rgb = COLORS[color];
return {
r: rgb[0],
g: rgb[1],
b: rgb[2]
};
} else if (color[0] === HASH) {
// hex
return this._hexToRgb(color.substring(1));
} else if (color.substr(0, 4) === RGB_PAREN) {
// rgb string
rgb = RGB_REGEX.exec(color.replace(/ /g, ''));
return {
r: parseInt(rgb[1], 10),
g: parseInt(rgb[2], 10),
b: parseInt(rgb[3], 10)
};
} else {
// default
return {
r: 0,
g: 0,
b: 0
};
}
},
// convert any color string to RGBA object
// from https://github.com/component/color-parser
colorToRGBA: function(str) {
str = str || 'black';
return (
Konva.Util._namedColorToRBA(str) ||
Konva.Util._hex3ColorToRGBA(str) ||
Konva.Util._hex6ColorToRGBA(str) ||
Konva.Util._rgbColorToRGBA(str) ||
Konva.Util._rgbaColorToRGBA(str)
);
},
// Parse named css color. Like "green"
_namedColorToRBA: function(str) {
var c = COLORS[str.toLowerCase()];
if (!c) {
return null;
}
return {
r: c[0],
g: c[1],
b: c[2],
a: 1
};
},
// Parse rgb(n, n, n)
_rgbColorToRGBA: function(str) {
if (str.indexOf('rgb(') === 0) {
str = str.match(/rgb\(([^)]+)\)/)[1];
var parts = str.split(/ *, */).map(Number);
return {
r: parts[0],
g: parts[1],
b: parts[2],
a: 1
};
}
},
// Parse rgba(n, n, n, n)
_rgbaColorToRGBA: function(str) {
if (str.indexOf('rgba(') === 0) {
str = str.match(/rgba\(([^)]+)\)/)[1];
var parts = str.split(/ *, */).map(Number);
return {
r: parts[0],
g: parts[1],
b: parts[2],
a: parts[3]
};
}
},
// Parse #nnnnnn
_hex6ColorToRGBA: function(str) {
if (str[0] === '#' && str.length === 7) {
return {
r: parseInt(str.slice(1, 3), 16),
g: parseInt(str.slice(3, 5), 16),
b: parseInt(str.slice(5, 7), 16),
a: 1
};
}
},
// Parse #nnn
_hex3ColorToRGBA: function(str) {
if (str[0] === '#' && str.length === 4) {
return {
r: parseInt(str[1] + str[1], 16),
g: parseInt(str[2] + str[2], 16),
b: parseInt(str[3] + str[3], 16),
a: 1
};
}
},
// o1 takes precedence over o2
_merge: function(o1, o2) {
var retObj = this._clone(o2);
for (var key in o1) {
if (this._isObject(o1[key])) {
retObj[key] = this._merge(o1[key], retObj[key]);
} else {
retObj[key] = o1[key];
}
}
return retObj;
},
/**
* check intersection of two client rectangles
* @method
* @memberof Konva.Util.prototype
*/
haveIntersection: function(r1, r2) {
return !(
r2.x > r1.x + r1.width ||
r2.x + r2.width < r1.x ||
r2.y > r1.y + r1.height ||
r2.y + r2.height < r1.y
);
},
cloneObject: function(obj) {
var retObj = {};
for (var key in obj) {
if (this._isObject(obj[key])) {
retObj[key] = this.cloneObject(obj[key]);
} else if (this._isArray(obj[key])) {
retObj[key] = this.cloneArray(obj[key]);
} else {
retObj[key] = obj[key];
}
}
return retObj;
},
cloneArray: function(arr) {
return arr.slice(0);
},
_degToRad: function(deg) {
return deg * PI_OVER_DEG180;
},
_radToDeg: function(rad) {
return rad * DEG180_OVER_PI;
},
_capitalize: function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
throw: function(str) {
throw new Error(KONVA_ERROR + str);
},
error: function(str) {
console.error(KONVA_ERROR + str);
},
warn: function(str) {
/*
* IE9 on Windows7 64bit will throw a JS error
* if we don't use window.console in the conditional
*/
if (Konva.global.console && console.warn && Konva.showWarnings) {
console.warn(KONVA_WARNING + str);
}
},
extend: function(child, parent) {
function Ctor() {
this.constructor = child;
}
Ctor.prototype = parent.prototype;
var oldProto = child.prototype;
child.prototype = new Ctor();
for (var key in oldProto) {
if (oldProto.hasOwnProperty(key)) {
child.prototype[key] = oldProto[key];
}
}
child.__super__ = parent.prototype;
// create reference to parent
child.super = parent;
},
/**
* adds methods to a constructor prototype
* @method
* @memberof Konva.Util.prototype
* @param {Function} constructor
* @param {Object} methods
*/
addMethods: function(constructor, methods) {
var key;
for (key in methods) {
constructor.prototype[key] = methods[key];
}
},
_getControlPoints: function(x0, y0, x1, y1, x2, y2, t) {
var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)),
d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
fa = t * d01 / (d01 + d12),
fb = t * d12 / (d01 + d12),
p1x = x1 - fa * (x2 - x0),
p1y = y1 - fa * (y2 - y0),
p2x = x1 + fb * (x2 - x0),
p2y = y1 + fb * (y2 - y0);
return [p1x, p1y, p2x, p2y];
},
_expandPoints: function(p, tension) {
var len = p.length,
allPoints = [],
n,
cp;
for (n = 2; n < len - 2; n += 2) {
cp = Konva.Util._getControlPoints(
p[n - 2],
p[n - 1],
p[n],
p[n + 1],
p[n + 2],
p[n + 3],
tension
);
allPoints.push(cp[0]);
allPoints.push(cp[1]);
allPoints.push(p[n]);
allPoints.push(p[n + 1]);
allPoints.push(cp[2]);
allPoints.push(cp[3]);
}
return allPoints;
},
_removeLastLetter: function(str) {
return str.substring(0, str.length - 1);
},
each: function(obj, func) {
for (var key in obj) {
func(key, obj[key]);
}
},
_inRange: function(val, left, right) {
return left <= val && val < right;
},
_getProjectionToSegment: function(x1, y1, x2, y2, x3, y3) {
var x, y, dist;
var pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
if (pd2 == 0) {
x = x1;
y = y1;
dist = (x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2);
} else {
var u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / pd2;
if (u < 0) {
x = x1;
y = y1;
dist = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3);
} else if (u > 1.0) {
x = x2;
y = y2;
dist = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3);
} else {
x = x1 + u * (x2 - x1);
y = y1 + u * (y2 - y1);
dist = (x - x3) * (x - x3) + (y - y3) * (y - y3);
}
}
return [x, y, dist];
},
// line as array of points.
// line might be closed
_getProjectionToLine: function(pt, line, isClosed) {
var pc = Konva.Util.cloneObject(pt);
var dist = Number.MAX_VALUE;
line.forEach(function(p1, i) {
if (!isClosed && i === line.length - 1) {
return;
}
var p2 = line[(i + 1) % line.length];
var proj = Konva.Util._getProjectionToSegment(
p1.x,
p1.y,
p2.x,
p2.y,
pt.x,
pt.y
);
var px = proj[0],
py = proj[1],
pdist = proj[2];
if (pdist < dist) {
pc.x = px;
pc.y = py;
dist = pdist;
}
});
return pc;
},
_prepareArrayForTween: function(startArray, endArray, isClosed) {
var n,
start = [],
end = [];
if (startArray.length > endArray.length) {
var temp = endArray;
endArray = startArray;
startArray = temp;
}
for (n = 0; n < startArray.length; n += 2) {
start.push({
x: startArray[n],
y: startArray[n + 1]
});
}
for (n = 0; n < endArray.length; n += 2) {
end.push({
x: endArray[n],
y: endArray[n + 1]
});
}
var newStart = [];
end.forEach(function(point) {
var pr = Konva.Util._getProjectionToLine(point, start, isClosed);
newStart.push(pr.x);
newStart.push(pr.y);
});
return newStart;
},
_prepareToStringify: function(obj) {
var desc;
obj.visitedByCircularReferenceRemoval = true;
for (var key in obj) {
if (
!(obj.hasOwnProperty(key) && obj[key] && typeof obj[key] == 'object')
) {
continue;
}
desc = Object.getOwnPropertyDescriptor(obj, key);
if (
obj[key].visitedByCircularReferenceRemoval ||
Konva.Util._isElement(obj[key])
) {
if (desc.configurable) {
delete obj[key];
} else {
return null;
}
} else if (Konva.Util._prepareToStringify(obj[key]) === null) {
if (desc.configurable) {
delete obj[key];
} else {
return null;
}
}
}
delete obj.visitedByCircularReferenceRemoval;
return obj;
}
};
})();
(function() {
'use strict';
// calculate pixel ratio
var _pixelRatio;
function getDevicePixelRatio() {
if (_pixelRatio) {
return _pixelRatio;
}
var canvas = Konva.Util.createCanvasElement();
var context = canvas.getContext('2d');
_pixelRatio = (function() {
var devicePixelRatio = Konva.window.devicePixelRatio || 1,
backingStoreRatio =
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio ||
1;
return devicePixelRatio / backingStoreRatio;
})();
return _pixelRatio;
}
/**
* Canvas Renderer constructor
* @constructor
* @abstract
* @memberof Konva
* @param {Object} config
* @param {Number} config.width
* @param {Number} config.height
* @param {Number} config.pixelRatio KonvaJS automatically handles pixel ratio adjustments in order to render crisp drawings
* on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios
* of 1. Some high end tablets and phones, like iPhones and iPads (not the mini) have a device pixel ratio
* of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel
* ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise
* specified, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel
* ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1.
*/
Konva.Canvas = function(config) {
this.init(config);
};
Konva.Canvas.prototype = {
init: function(config) {
var conf = config || {};
var pixelRatio =
conf.pixelRatio || Konva.pixelRatio || getDevicePixelRatio();
this.pixelRatio = pixelRatio;
this._canvas = Konva.Util.createCanvasElement();
// set inline styles
this._canvas.style.padding = 0;
this._canvas.style.margin = 0;
this._canvas.style.border = 0;
this._canvas.style.background = 'transparent';
this._canvas.style.position = 'absolute';
this._canvas.style.top = 0;
this._canvas.style.left = 0;
},
/**
* get canvas context
* @method
* @memberof Konva.Canvas.prototype
* @returns {CanvasContext} context
*/
getContext: function() {
return this.context;
},
/**
* get pixel ratio
* @method
* @memberof Konva.Canvas.prototype
* @returns {Number} pixel ratio
*/
getPixelRatio: function() {
return this.pixelRatio;
},
/**
* get pixel ratio
* @method
* @memberof Konva.Canvas.prototype
* @param {Number} pixelRatio KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings
* on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios
* of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio
* of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel
* ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise
* specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel
* ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1.
*/
setPixelRatio: function(pixelRatio) {
var previousRatio = this.pixelRatio;
this.pixelRatio = pixelRatio;
this.setSize(
this.getWidth() / previousRatio,
this.getHeight() / previousRatio
);
},
/**
* set width
* @method
* @memberof Konva.Canvas.prototype
* @param {Number} width
*/
setWidth: function(width) {
// take into account pixel ratio
this.width = this._canvas.width = width * this.pixelRatio;
this._canvas.style.width = width + 'px';
var pixelRatio = this.pixelRatio,
_context = this.getContext()._context;
_context.scale(pixelRatio, pixelRatio);
},
/**
* set height
* @method
* @memberof Konva.Canvas.prototype
* @param {Number} height
*/
setHeight: function(height) {
// take into account pixel ratio
this.height = this._canvas.height = height * this.pixelRatio;
this._canvas.style.height = height + 'px';
var pixelRatio = this.pixelRatio,
_context = this.getContext()._context;
_context.scale(pixelRatio, pixelRatio);
},
/**
* get width
* @method
* @memberof Konva.Canvas.prototype
* @returns {Number} width
*/
getWidth: function() {
return this.width;
},
/**
* get height
* @method
* @memberof Konva.Canvas.prototype
* @returns {Number} height
*/
getHeight: function() {
return this.height;
},
/**
* set size
* @method
* @memberof Konva.Canvas.prototype
* @param {Number} width
* @param {Number} height
*/
setSize: function(width, height) {
this.setWidth(width);
this.setHeight(height);
},
/**
* to data url
* @method
* @memberof Konva.Canvas.prototype
* @param {String} mimeType
* @param {Number} quality between 0 and 1 for jpg mime types
* @returns {String} data url string
*/
toDataURL: function(mimeType, quality) {
try {
// If this call fails (due to browser bug, like in Firefox 3.6),
// then revert to previous no-parameter image/png behavior
return this._canvas.toDataURL(mimeType, quality);
} catch (e) {
try {
return this._canvas.toDataURL();
} catch (err) {
Konva.Util.warn('Unable to get data URL. ' + err.message);
return '';
}
}
}
};
Konva.SceneCanvas = function(config) {
var conf = config || {};
var width = conf.width || 0,
height = conf.height || 0;
Konva.Canvas.call(this, conf);
this.context = new Konva.SceneContext(this);
this.setSize(width, height);
};
Konva.Util.extend(Konva.SceneCanvas, Konva.Canvas);
Konva.HitCanvas = function(config) {
var conf = config || {};
var width = conf.width || 0,
height = conf.height || 0;
Konva.Canvas.call(this, conf);
this.context = new Konva.HitContext(this);
this.setSize(width, height);
this.hitCanvas = true;
};
Konva.Util.extend(Konva.HitCanvas, Konva.Canvas);
})();
(function() {
'use strict';
var COMMA = ',',
OPEN_PAREN = '(',
CLOSE_PAREN = ')',
OPEN_PAREN_BRACKET = '([',
CLOSE_BRACKET_PAREN = '])',
SEMICOLON = ';',
DOUBLE_PAREN = '()',
// EMPTY_STRING = '',
EQUALS = '=',
// SET = 'set',
CONTEXT_METHODS = [
'arc',
'arcTo',
'beginPath',
'bezierCurveTo',
'clearRect',
'clip',
'closePath',
'createLinearGradient',
'createPattern',
'createRadialGradient',
'drawImage',
'fill',
'fillText',
'getImageData',
'createImageData',
'lineTo',
'moveTo',
'putImageData',
'quadraticCurveTo',
'rect',
'restore',
'rotate',
'save',
'scale',
'setLineDash',
'setTransform',
'stroke',
'strokeText',
'transform',
'translate'
];
var CONTEXT_PROPERTIES = [
'fillStyle',
'strokeStyle',
'shadowColor',
'shadowBlur',
'shadowOffsetX',
'shadowOffsetY',
'lineCap',
'lineDashOffset',
'lineJoin',
'lineWidth',
'miterLimit',
'font',
'textAlign',
'textBaseline',
'globalAlpha',
'globalCompositeOperation'
];
/**
* Canvas Context constructor
* @constructor
* @abstract
* @memberof Konva
*/
Konva.Context = function(canvas) {
this.init(canvas);
};
Konva.Context.prototype = {
init: function(canvas) {
this.canvas = canvas;
this._context = canvas._canvas.getContext('2d');
if (Konva.enableTrace) {
this.traceArr = [];
this._enableTrace();
}
},
/**
* fill shape
* @method
* @memberof Konva.Context.prototype
* @param {Konva.Shape} shape
*/
fillShape: function(shape) {
if (shape.getFillEnabled()) {
this._fill(shape);
}
},
/**
* stroke shape
* @method
* @memberof Konva.Context.prototype
* @param {Konva.Shape} shape
*/
strokeShape: function(shape) {
if (shape.getStrokeEnabled()) {
this._stroke(shape);
}
},
/**
* fill then stroke
* @method
* @memberof Konva.Context.prototype
* @param {Konva.Shape} shape
*/
fillStrokeShape: function(shape) {
var fillEnabled = shape.getFillEnabled();
if (fillEnabled) {
this._fill(shape);
}
if (shape.getStrokeEnabled()) {
this._stroke(shape);
}
},
/**
* get context trace if trace is enabled
* @method
* @memberof Konva.Context.prototype
* @param {Boolean} relaxed if false, return strict context trace, which includes method names, method parameters
* properties, and property values. If true, return relaxed context trace, which only returns method names and
* properites.
* @returns {String}
*/
getTrace: function(relaxed) {
var traceArr = this.traceArr,
len = traceArr.length,
str = '',
n,
trace,
method,
args;
for (n = 0; n < len; n++) {
trace = traceArr[n];
method = trace.method;
// methods
if (method) {
args = trace.args;
str += method;
if (relaxed) {
str += DOUBLE_PAREN;
} else {
if (Konva.Util._isArray(args[0])) {
str +=
OPEN_PAREN_BRACKET + args.join(COMMA) + CLOSE_BRACKET_PAREN;
} else {
str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN;
}
}
} else {
// properties
str += trace.property;
if (!relaxed) {
str += EQUALS + trace.val;
}
}
str += SEMICOLON;
}
return str;
},
/**
* clear trace if trace is enabled
* @method
* @memberof Konva.Context.prototype
*/
clearTrace: function() {
this.traceArr = [];
},
_trace: function(str) {
var traceArr = this.traceArr,
len;
traceArr.push(str);
len = traceArr.length;
if (len >= Konva.traceArrMax) {
traceArr.shift();
}
},
/**
* reset canvas context transform
* @method
* @memberof Konva.Context.prototype
*/
reset: function() {
var pixelRatio = this.getCanvas().getPixelRatio();
this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0);
},
/**
* get canvas
* @method
* @memberof Konva.Context.prototype
* @returns {Konva.Canvas}
*/
getCanvas: function() {
return this.canvas;
},
/**
* clear canvas
* @method
* @memberof Konva.Context.prototype
* @param {Object} [bounds]
* @param {Number} [bounds.x]
* @param {Number} [bounds.y]
* @param {Number} [bounds.width]
* @param {Number} [bounds.height]
*/
clear: function(bounds) {
var canvas = this.getCanvas();
if (bounds) {
this.clearRect(
bounds.x || 0,
bounds.y || 0,
bounds.width || 0,
bounds.height || 0
);
} else {
this.clearRect(
0,
0,
canvas.getWidth() / canvas.pixelRatio,
canvas.getHeight() / canvas.pixelRatio
);
}
},
_applyLineCap: function(shape) {
var lineCap = shape.getLineCap();
if (lineCap) {
this.setAttr('lineCap', lineCap);
}
},
_applyOpacity: function(shape) {
var absOpacity = shape.getAbsoluteOpacity();
if (absOpacity !== 1) {
this.setAttr('globalAlpha', absOpacity);
}
},
_applyLineJoin: function(shape) {
var lineJoin = shape.getLineJoin();
if (lineJoin) {
this.setAttr('lineJoin', lineJoin);
}
},
setAttr: function(attr, val) {
this._context[attr] = val;
},
// context pass through methods
arc: function() {
var a = arguments;
this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]);
},
beginPath: function() {
this._context.beginPath();
},
bezierCurveTo: function() {
var a = arguments;
this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]);
},
clearRect: function() {
var a = arguments;
this._context.clearRect(a[0], a[1], a[2], a[3]);
},
clip: function() {
this._context.clip();
},
closePath: function() {
this._context.closePath();
},
createImageData: function() {
var a = arguments;
if (a.length === 2) {
return this._context.createImageData(a[0], a[1]);
} else if (a.length === 1) {
return this._context.createImageData(a[0]);
}
},
createLinearGradient: function() {
var a = arguments;
return this._context.createLinearGradient(a[0], a[1], a[2], a[3]);
},
createPattern: function() {
var a = arguments;
return this._context.createPattern(a[0], a[1]);
},
createRadialGradient: function() {
var a = arguments;
return this._context.createRadialGradient(
a[0],
a[1],
a[2],
a[3],
a[4],
a[5]
);
},
drawImage: function() {
var a = arguments,
_context = this._context;
if (a.length === 3) {
_context.drawImage(a[0], a[1], a[2]);
} else if (a.length === 5) {
_context.drawImage(a[0], a[1], a[2], a[3], a[4]);
} else if (a.length === 9) {
_context.drawImage(
a[0],
a[1],
a[2],
a[3],
a[4],
a[5],
a[6],
a[7],
a[8]
);
}
},
isPointInPath: function(x, y) {
return this._context.isPointInPath(x, y);
},
fill: function() {
this._context.fill();
},
fillRect: function(x, y, width, height) {
this._context.fillRect(x, y, width, height);
},
strokeRect: function(x, y, width, height) {
this._context.strokeRect(x, y, width, height);
},
fillText: function() {
var a = arguments;
this._context.fillText(a[0], a[1], a[2]);
},
measureText: function(text) {
return this._context.measureText(text);
},
getImageData: function() {
var a = arguments;
return this._context.getImageData(a[0], a[1], a[2], a[3]);
},
lineTo: function() {
var a = arguments;
this._context.lineTo(a[0], a[1]);
},
moveTo: function() {
var a = arguments;
this._context.moveTo(a[0], a[1]);
},
rect: function() {
var a = arguments;
this._context.rect(a[0], a[1], a[2], a[3]);
},
putImageData: function() {
var a = arguments;
this._context.putImageData(a[0], a[1], a[2]);
},
quadraticCurveTo: function() {
var a = arguments;
this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]);
},
restore: function() {
this._context.restore();
},
rotate: function() {
var a = arguments;
this._context.rotate(a[0]);
},
save: function() {
this._context.save();
},
scale: function() {
var a = arguments;
this._context.scale(a[0], a[1]);
},
setLineDash: function() {
var a = arguments,
_context = this._context;
// works for Chrome and IE11
if (this._context.setLineDash) {