/* * KineticJS JavaScript Framework v5.1.1 * http://www.kineticjs.com/ * Copyright 2013, Eric Rowell * Licensed under the MIT or GPL Version 2 licenses. * Date: 2014-10-03 * * Copyright (C) 2011 - 2013 by Eric Rowell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * @namespace Kinetic */ /*jshint -W079, -W020*/ var Kinetic = {}; (function(root) { var PI_OVER_180 = Math.PI / 180; Kinetic = { // public version: '5.1.1', // private stages: [], idCounter: 0, ids: {}, names: {}, shapes: {}, listenClickTap: false, inDblClickWindow: false, // configurations enableTrace: false, traceArrMax: 100, dblClickWindow: 400, /** * Global pixel ratio configuration. KineticJS 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 Kinetic * @example * Kinetic.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. * @property dragDistance * @default 0 * @memberof Kinetic * @example * Kinetic.dragDistance = 10; */ dragDistance : 0, /** * 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 Kinetic * @example * node.rotation(45); // 45 degrees * Kinetic.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 Kinetic * @example * Kinetic.showWarnings = false; */ showWarnings : true, /** * @namespace Filters * @memberof Kinetic */ Filters: {}, /** * 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 Kinetic * @abstract * @param {Object} config * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] */ Node: function(config) { this._init(config); }, /** * Shape constructor. Shapes are primitive objects such as rectangles, * circles, text, lines, etc. * @constructor * @memberof Kinetic * @augments Kinetic.Node * @param {Object} config * @param {String} [config.fill] fill color * @param {Integer} [config.fillRed] set fill red component * @param {Integer} [config.fillGreen] set fill green component * @param {Integer} [config.fillBlue] set fill blue component * @param {Integer} [config.fillAlpha] set fill alpha component * @param {Image} [config.fillPatternImage] fill pattern image * @param {Number} [config.fillPatternX] * @param {Number} [config.fillPatternY] * @param {Object} [config.fillPatternOffset] object with x and y component * @param {Number} [config.fillPatternOffsetX] * @param {Number} [config.fillPatternOffsetY] * @param {Object} [config.fillPatternScale] object with x and y component * @param {Number} [config.fillPatternScaleX] * @param {Number} [config.fillPatternScaleY] * @param {Number} [config.fillPatternRotation] * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component * @param {Number} [config.fillLinearGradientStartPointX] * @param {Number} [config.fillLinearGradientStartPointY] * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component * @param {Number} [config.fillLinearGradientEndPointX] * @param {Number} [config.fillLinearGradientEndPointY] * @param {Array} [config.fillLinearGradientColorStops] array of color stops * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component * @param {Number} [config.fillRadialGradientStartPointX] * @param {Number} [config.fillRadialGradientStartPointY] * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component * @param {Number} [config.fillRadialGradientEndPointX] * @param {Number} [config.fillRadialGradientEndPointY] * @param {Number} [config.fillRadialGradientStartRadius] * @param {Number} [config.fillRadialGradientEndRadius] * @param {Array} [config.fillRadialGradientColorStops] array of color stops * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration * @param {String} [config.stroke] stroke color * @param {Integer} [config.strokeRed] set stroke red component * @param {Integer} [config.strokeGreen] set stroke green component * @param {Integer} [config.strokeBlue] set stroke blue component * @param {Integer} [config.strokeAlpha] set stroke alpha component * @param {Number} [config.strokeWidth] stroke width * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true * @param {String} [config.lineJoin] can be miter, round, or bevel. The default * is miter * @param {String} [config.lineCap] can be butt, round, or sqare. The default * is butt * @param {String} [config.shadowColor] * @param {Integer} [config.shadowRed] set shadow color red component * @param {Integer} [config.shadowGreen] set shadow color green component * @param {Integer} [config.shadowBlue] set shadow color blue component * @param {Integer} [config.shadowAlpha] set shadow color alpha component * @param {Number} [config.shadowBlur] * @param {Object} [config.shadowOffset] object with x and y component * @param {Number} [config.shadowOffsetX] * @param {Number} [config.shadowOffsetY] * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number * between 0 and 1 * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true * @param {Array} [config.dash] * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] * @example * var customShape = new Kinetic.Shape({ * x: 5, * y: 10, * fill: 'red', * // a Kinetic.Canvas renderer is passed into the drawFunc function * drawFunc: function(context) { * context.beginPath(); * context.moveTo(200, 50); * context.lineTo(420, 80); * context.quadraticCurveTo(300, 100, 260, 170); * context.closePath(); * context.fillStrokeShape(this); * } *}); */ Shape: function(config) { this.__init(config); }, /** * Container constructor.  Containers are used to contain nodes or other containers * @constructor * @memberof Kinetic * @augments Kinetic.Node * @abstract * @param {Object} config * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] * * @param {Object} [config.clip] set clip * @param {Number} [config.clipX] set clip x * @param {Number} [config.clipY] set clip y * @param {Number} [config.clipWidth] set clip width * @param {Number} [config.clipHeight] set clip height */ Container: function(config) { this.__init(config); }, /** * Stage constructor. A stage is used to contain multiple layers * @constructor * @memberof Kinetic * @augments Kinetic.Container * @param {Object} config * @param {String|Element} config.container Container id or DOM element * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] * @example * var stage = new Kinetic.Stage({ * width: 500, * height: 800, * container: 'containerId' * }); */ Stage: function(config) { this.___init(config); }, /** * BaseLayer constructor. * @constructor * @memberof Kinetic * @augments Kinetic.Container * @param {Object} config * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want * to clear the canvas before each layer draw. The default value is true. * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] * * @param {Object} [config.clip] set clip * @param {Number} [config.clipX] set clip x * @param {Number} [config.clipY] set clip y * @param {Number} [config.clipWidth] set clip width * @param {Number} [config.clipHeight] set clip height * @example * var layer = new Kinetic.Layer(); */ BaseLayer: function(config) { this.___init(config); }, /** * Layer constructor. Layers are tied to their own canvas element and are used * to contain groups or shapes. * @constructor * @memberof Kinetic * @augments Kinetic.BaseLayer * @param {Object} config * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want * to clear the canvas before each layer draw. The default value is true. * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] * * @param {Object} [config.clip] set clip * @param {Number} [config.clipX] set clip x * @param {Number} [config.clipY] set clip y * @param {Number} [config.clipWidth] set clip width * @param {Number} [config.clipHeight] set clip height * @example * var layer = new Kinetic.Layer(); */ Layer: function(config) { this.____init(config); }, /** * FastLayer constructor. Layers are tied to their own canvas element and are used * to contain shapes only. If you don't need node nesting, mouse and touch interactions, * or event pub/sub, you should use FastLayer instead of Layer to create your layers. * It renders about 2x faster than normal layers. * @constructor * @memberof Kinetic * @augments Kinetic.BaseLayer * @param {Object} config * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want * to clear the canvas before each layer draw. The default value is true. * @param {Boolean} [config.visible] * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * * @param {Object} [config.clip] set clip * @param {Number} [config.clipX] set clip x * @param {Number} [config.clipY] set clip y * @param {Number} [config.clipWidth] set clip width * @param {Number} [config.clipHeight] set clip height * @example * var layer = new Kinetic.FastLayer(); */ FastLayer: function(config) { this.____init(config); }, /** * Group constructor. Groups are used to contain shapes or other groups. * @constructor * @memberof Kinetic * @augments Kinetic.Container * @param {Object} config * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.visible] * @param {Boolean} [config.listening] whether or not the node is listening for events * @param {String} [config.id] unique id * @param {String} [config.name] non-unique name * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 * @param {Object} [config.scale] set scale * @param {Number} [config.scaleX] set scale x * @param {Number} [config.scaleY] set scale y * @param {Number} [config.rotation] rotation in degrees * @param {Object} [config.offset] offset from center point and rotation point * @param {Number} [config.offsetX] set offset x * @param {Number} [config.offsetY] set offset y * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop * the entire stage by dragging any portion of the stage * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] * * @param {Object} [config.clip] set clip * @param {Number} [config.clipX] set clip x * @param {Number} [config.clipY] set clip y * @param {Number} [config.clipWidth] set clip width * @param {Number} [config.clipHeight] set clip height * @example * var group = new Kinetic.Group(); */ Group: function(config) { this.___init(config); }, /** * returns whether or not drag and drop is currently active * @method * @memberof Kinetic */ isDragging: function() { var dd = Kinetic.DD; // if DD is not included with the build, then // drag and drop is not even possible if (dd) { return dd.isDragging; } else { return false; } }, /** * returns whether or not a drag and drop operation is ready, but may * not necessarily have started * @method * @memberof Kinetic */ isDragReady: function() { var dd = Kinetic.DD; // if DD is not included with the build, then // drag and drop is not even possible if (dd) { return !!dd.node; } else { 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 !== undefined) { var names = name.split(/\W+/g); for(var n = 0; n < names.length; n++) { if (names[n]) { if(this.names[names[n]] === undefined) { this.names[names[n]] = []; } this.names[names[n]].push(node); } } } }, _removeName: function(name, _id) { if(name !== undefined) { var nodes = this.names[name]; if(nodes !== undefined) { 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; }, _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', // adding mobile flab mobile: mobile, ieMobile: ieMobile // If this is true (i.e., WP8), then Kinetic touch events are executed instead of equivalent Kinetic mouse events }; }, // user agent UA: undefined }; Kinetic.UA = Kinetic._parseUA((root.navigator && root.navigator.userAgent) || ''); })(this); // Uses Node, AMD or browser globals to create a module. // If you want something that will work in other stricter CommonJS environments, // or if you need to create a circular dependency, see commonJsStrict.js // Defines a module "returnExports" that depends another module called "b". // Note that the name of the module is implied by the file name. It is best // if the file name and the exported global have matching names. // If the 'b' module also uses this type of boilerplate, then // in the browser, it will create a global .b that is used below. // If you do not want to support the browser global path, then you // can remove the `root` use and the passing `this` as the first arg to // the top function. // if the module has no dependencies, the above pattern can be simplified to ( function(root, factory) { if( typeof exports === 'object') { var KineticJS = factory(); // runtime-check for browserify if(global.window === global) { Kinetic.document = global.document; Kinetic.window = global; } else { // Node. Does not work with strict CommonJS, but // only CommonJS-like enviroments that support module.exports, // like Node. var Canvas = require('canvas'); var jsdom = require('jsdom').jsdom; Kinetic.document = jsdom(''); Kinetic.window = Kinetic.document.createWindow(); Kinetic.window.Image = Canvas.Image; Kinetic._nodeCanvas = Canvas; } Kinetic.root = root; module.exports = KineticJS; return; } else if( typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(factory); } Kinetic.document = document; Kinetic.window = window; Kinetic.root = root; }(this, function() { // Just return a value to define the module export. // This example returns an object, but the module // can return a function as the exported value. return Kinetic; })); ;(function() { /** * Collection constructor. Collection extends * Array. This class is used in conjunction with {@link Kinetic.Container#get} * @constructor * @memberof Kinetic */ Kinetic.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; }; Kinetic.Collection.prototype = []; /** * iterate through node array and run a function for each node. * The node and index is passed into the function * @method * @memberof Kinetic.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); * }); */ Kinetic.Collection.prototype.each = function(func) { for(var n = 0; n < this.length; n++) { func(this[n], n); } }; /** * convert collection into an array * @method * @memberof Kinetic.Collection.prototype */ Kinetic.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 Kinetic.Collection * @param {Array} arr */ Kinetic.Collection.toCollection = function(arr) { var collection = new Kinetic.Collection(), len = arr.length, n; for(n = 0; n < len; n++) { collection.push(arr[n]); } return collection; }; // map one method by it's name Kinetic.Collection._mapMethod = function(methodName) { Kinetic.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; }; }; Kinetic.Collection.mapMethods = function(constructor) { var prot = constructor.prototype; for(var methodName in prot) { Kinetic.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 Kinetic */ Kinetic.Transform = function(m) { this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0]; }; Kinetic.Transform.prototype = { /** * Copy Kinetic.Transform object * @method * @memberof Kinetic.Transform.prototype * @returns {Kinetic.Transform} */ copy: function() { return new Kinetic.Transform(this.m); }, /** * Transform point * @method * @memberof Kinetic.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 Kinetic.Transform.prototype * @param {Number} x * @param {Number} y * @returns {Kinetic.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 Kinetic.Transform.prototype * @param {Number} sx * @param {Number} sy * @returns {Kinetic.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 Kinetic.Transform.prototype * @param {Number} rad Angle in radians * @returns {Kinetic.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 Kinetic.Transform.prototype * @returns {Object} 2D point(x, y) */ getTranslation: function() { return { x: this.m[4], y: this.m[5] }; }, /** * Apply skew * @method * @memberof Kinetic.Transform.prototype * @param {Number} sx * @param {Number} sy * @returns {Kinetic.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 Kinetic.Transform.prototype * @param {Kinetic.Transform} matrix * @returns {Kinetic.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 Kinetic.Transform.prototype * @returns {Kinetic.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 Kinetic.Transform.prototype */ getMatrix: function() { return this.m; }, /** * set to absolute position via translation * @method * @memberof Kinetic.Transform.prototype * @returns {Kinetic.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', KINETIC_WARNING = 'Kinetic warning: ', KINETIC_ERROR = 'Kinetic error: ', RGB_PAREN = 'rgb(', COLORS = { aqua: [0,255,255], lime: [0,255,0], silver: [192,192,192], black: [0,0,0], maroon: [128,0,0], teal: [0,128,128], blue: [0,0,255], navy: [0,0,128], white: [255,255,255], fuchsia: [255,0,255], olive:[128,128,0], yellow: [255,255,0], orange: [255,165,0], gray: [128,128,128], purple: [128,0,128], green: [0,128,0], red: [255,0,0], pink: [255,192,203], cyan: [0,255,255], transparent: [255,255,255,0] }, RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/; /** * @namespace Util * @memberof Kinetic */ Kinetic.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; }, // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. _throttle: function(func, wait, opts) { var context, args, result; var timeout = null; var previous = 0; var options = opts || {}; var later = function() { previous = options.leading === false ? 0 : new Date().getTime(); timeout = null; result = func.apply(context, args); context = args = null; }; return function() { var now = new Date().getTime(); if (!previous && options.leading === false) { previous = now; } var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; }, /* * other utils */ _hasMethods: function(obj) { var names = [], key; for(key in obj) { if(this._isFunction(obj[key])) { names.push(key); } } return names.length > 0; }, createCanvasElement: function() { var canvas = Kinetic.document.createElement('canvas'); // on some environments canvas.style is readonly try { canvas.style = canvas.style || {}; } catch (e) { } return canvas; }, isBrowser: function() { return (typeof exports !== 'object'); }, _isInDocument: function(el) { while(el = el.parentNode) { if(el == Kinetic.document) { return true; } } return false; }, _simplifyArray: function(arr) { var retArr = [], len = arr.length, util = Kinetic.Util, n, val; for (n=0; n> 16) & 255, g: (bigint >> 8) & 255, b: bigint & 255 }; }, /** * return random hex color * @method * @memberof Kinetic.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 Kinetic.Util.prototype */ get: function(val, def) { if (val === undefined) { return def; } else { return val; } }, /** * get RGB components of a color * @method * @memberof Kinetic.Util.prototype * @param {String} color * @example * // each of the following examples return {r:0, g:0, b:255} * var rgb = Kinetic.Util.getRGB('blue'); * var rgb = Kinetic.Util.getRGB('#0000ff'); * var rgb = Kinetic.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] }; } // hex else if (color[0] === HASH) { return this._hexToRgb(color.substring(1)); } // rgb string else if (color.substr(0, 4) === RGB_PAREN) { rgb = RGB_REGEX.exec(color.replace(/ /g,'')); return { r: parseInt(rgb[1], 10), g: parseInt(rgb[2], 10), b: parseInt(rgb[3], 10) }; } // default else { return { r: 0, g: 0, b: 0 }; } }, // 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; }, 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); }, error: function(str) { throw new Error(KINETIC_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(Kinetic.root.console && console.warn && Kinetic.showWarnings) { console.warn(KINETIC_WARNING + str); } }, extend: function(c1, c2) { for(var key in c2.prototype) { if(!( key in c1.prototype)) { c1.prototype[key] = c2.prototype[key]; } } }, /** * adds methods to a constructor prototype * @method * @memberof Kinetic.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= Kinetic.traceArrMax) { traceArr.shift(); } }, /** * reset canvas context transform * @method * @memberof Kinetic.Context.prototype */ reset: function() { var pixelRatio = this.getCanvas().getPixelRatio(); this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); }, /** * get canvas * @method * @memberof Kinetic.Context.prototype * @returns {Kinetic.Canvas} */ getCanvas: function() { return this.canvas; }, /** * clear canvas * @method * @memberof Kinetic.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.getHeight()); } }, _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]); } }, fill: function() { this._context.fill(); }, fillText: function() { var a = arguments; this._context.fillText(a[0], a[1], a[2]); }, 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) { _context.setLineDash(a[0]); } // verified that this works in firefox else if('mozDash' in _context) { _context.mozDash = a[0]; } // does not currently work for Safari else if('webkitLineDash' in _context) { _context.webkitLineDash = a[0]; } // no support for IE9 and IE10 }, setTransform: function() { var a = arguments; this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); }, stroke: function() { this._context.stroke(); }, strokeText: function() { var a = arguments; this._context.strokeText(a[0], a[1], a[2]); }, transform: function() { var a = arguments; this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); }, translate: function() { var a = arguments; this._context.translate(a[0], a[1]); }, _enableTrace: function() { var that = this, len = CONTEXT_METHODS.length, _simplifyArray = Kinetic.Util._simplifyArray, origSetter = this.setAttr, n, args; // to prevent creating scope function at each loop var func = function(methodName) { var origMethod = that[methodName], ret; that[methodName] = function() { args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); ret = origMethod.apply(that, arguments); that._trace({ method: methodName, args: args }); return ret; }; }; // methods for (n=0; n 255) { return 255; } else if (val < 0) { return 0; } else { return Math.round(val); } }, alphaComponent: function(val) { if (val > 1) { return 1; } // chrome does not honor alpha values of 0 else if (val < 0.0001) { return 0.0001; } else { return val; } } }; })();;(function() { // CONSTANTS var ABSOLUTE_OPACITY = 'absoluteOpacity', ABSOLUTE_TRANSFORM = 'absoluteTransform', CHANGE = 'Change', CHILDREN = 'children', DOT = '.', EMPTY_STRING = '', GET = 'get', ID = 'id', KINETIC = 'kinetic', 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.kinetic', 'yChange.kinetic', 'scaleXChange.kinetic', 'scaleYChange.kinetic', 'skewXChange.kinetic', 'skewYChange.kinetic', 'rotationChange.kinetic', 'offsetXChange.kinetic', 'offsetYChange.kinetic', 'transformsEnabledChange.kinetic' ].join(SPACE); Kinetic.Util.addMethods(Kinetic.Node, { _init: function(config) { var that = this; this._id = Kinetic.idCounter++; this.eventListeners = {}; this.attrs = {}; this._cache = {}; this._filterUpToDate = false; this.setAttrs(config); // event bindings for cache handling this.on(TRANSFORM_CHANGE_STR, function() { this._clearCache(TRANSFORM); that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); }); this.on('visibleChange.kinetic', function() { that._clearSelfAndDescendantCache(VISIBLE); }); this.on('listeningChange.kinetic', function() { that._clearSelfAndDescendantCache(LISTENING); }); this.on('opacityChange.kinetic', function() { that._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 Kinetic.Node.prototype * @returns {Kinetic.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 * @method * @memberof Kinetic.Node.prototype * @param {Object} config * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached * region for debugging purposes * @returns {Kinetic.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, * drawBorder: true * }); */ cache: function(config) { var conf = config || {}, x = conf.x || 0, y = conf.y || 0, width = conf.width || this.width(), height = conf.height || this.height(), drawBorder = conf.drawBorder || false; if (width === 0 || height === 0) { Kinetic.Util.warn('Width or height of caching configuration equals 0. Cache is ignored.'); return; } var cachedSceneCanvas = new Kinetic.SceneCanvas({ pixelRatio: 1, width: width, height: height }), cachedFilterCanvas = new Kinetic.SceneCanvas({ pixelRatio: 1, width: width, height: height }), cachedHitCanvas = new Kinetic.HitCanvas({ width: width, height: height }), sceneContext = cachedSceneCanvas.getContext(), hitContext = cachedHitCanvas.getContext(); cachedHitCanvas.isCache = true; this.clearCache(); sceneContext.save(); hitContext.save(); // 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(); } sceneContext.translate(x * -1, y * -1); hitContext.translate(x * -1, y * -1); // don't need to translate canvas if shape is not added to layer if (this.nodeType === 'Shape') { sceneContext.translate(this.x() * -1, this.y() * -1); hitContext.translate(this.x() * -1, this.y() * -1); } this.drawScene(cachedSceneCanvas, this); this.drawHit(cachedHitCanvas, this); sceneContext.restore(); hitContext.restore(); this._cache.canvas = { scene: cachedSceneCanvas, filter: cachedFilterCanvas, hit: cachedHitCanvas }; return this; }, _drawCachedSceneCanvas: function(context) { context.save(); this.getLayer()._applyTransform(this, context); context._applyOpacity(this); context.drawImage(this._getCachedSceneCanvas()._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) { try { len = filters.length; filterContext.clear(); // copy cached canvas onto filter context filterContext.drawImage(sceneCanvas._canvas, 0, 0); imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight()); // apply filters to filter context for (n=0; n