/*! * createjs * Visit http://createjs.com/ for documentation, updates and examples. * * Copyright (c) 2010 gskinner.com, inc. * * 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. */ this.createjs = this.createjs||{}; //############################################################################## // extend.js //############################################################################## /** * @class Utility Methods */ /** * Sets up the prototype chain and constructor property for a new class. * * This should be called right after creating the class constructor. * * function MySubClass() {} * createjs.extend(MySubClass, MySuperClass); * MySubClass.prototype.doSomething = function() { } * * var foo = new MySubClass(); * console.log(foo instanceof MySuperClass); // true * console.log(foo.prototype.constructor === MySubClass); // true * * @method extend * @param {Function} subclass The subclass. * @param {Function} superclass The superclass to extend. * @return {Function} Returns the subclass's new prototype. */ createjs.extend = function(subclass, superclass) { "use strict"; function o() { this.constructor = subclass; } o.prototype = superclass.prototype; return (subclass.prototype = new o()); }; //############################################################################## // promote.js //############################################################################## /** * @class Utility Methods */ /** * Promotes any methods on the super class that were overridden, by creating an alias in the format `prefix_methodName`. * It is recommended to use the super class's name as the prefix. * An alias to the super class's constructor is always added in the format `prefix_constructor`. * This allows the subclass to call super class methods without using `function.call`, providing better performance. * * For example, if `MySubClass` extends `MySuperClass`, and both define a `draw` method, then calling `promote(MySubClass, "MySuperClass")` * would add a `MySuperClass_constructor` method to MySubClass and promote the `draw` method on `MySuperClass` to the * prototype of `MySubClass` as `MySuperClass_draw`. * * This should be called after the class's prototype is fully defined. * * function ClassA(name) { * this.name = name; * } * ClassA.prototype.greet = function() { * return "Hello "+this.name; * } * * function ClassB(name, punctuation) { * this.ClassA_constructor(name); * this.punctuation = punctuation; * } * createjs.extend(ClassB, ClassA); * ClassB.prototype.greet = function() { * return this.ClassA_greet()+this.punctuation; * } * createjs.promote(ClassB, "ClassA"); * * var foo = new ClassB("World", "!?!"); * console.log(foo.greet()); // Hello World!?! * * @method promote * @param {Function} subclass The class to promote super class methods on. * @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass. * @return {Function} Returns the subclass. */ createjs.promote = function(subclass, prefix) { "use strict"; var subP = subclass.prototype, supP = (Object.getPrototypeOf&&Object.getPrototypeOf(subP))||subP.__proto__; if (supP) { subP[(prefix+="_") + "constructor"] = supP.constructor; // constructor is not always innumerable for (var n in supP) { if (subP.hasOwnProperty(n) && (typeof supP[n] == "function")) { subP[prefix + n] = supP[n]; } } } return subclass; }; //############################################################################## // indexOf.js //############################################################################## /** * @class Utility Methods */ /** * Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of * that value. Returns -1 if value is not found. * * var i = createjs.indexOf(myArray, myElementToFind); * * @method indexOf * @param {Array} array Array to search for searchElement * @param searchElement Element to find in array. * @return {Number} The first index of searchElement in array. */ createjs.indexOf = function (array, searchElement){ "use strict"; for (var i = 0,l=array.length; i < l; i++) { if (searchElement === array[i]) { return i; } } return -1; }; //############################################################################## // UID.js //############################################################################## (function() { "use strict"; // constructor: /** * Global utility for generating sequential unique ID numbers. The UID class uses a static interface (ex. UID.get()) * and should not be instantiated. * @class UID * @static **/ function UID() { throw "UID cannot be instantiated"; } // private static properties: /** * @property _nextID * @type Number * @protected **/ UID._nextID = 0; // public static methods: /** * Returns the next unique id. * @method get * @return {Number} The next unique id * @static **/ UID.get = function() { return UID._nextID++; }; createjs.UID = UID; }()); //############################################################################## // deprecate.js //############################################################################## /** * @class Utility Methods */ /** * Wraps deprecated methods so they still be used, but throw warnings to developers. * * obj.deprecatedMethod = createjs.deprecate("Old Method Name", obj._fallbackMethod); * * The recommended approach for deprecated properties is: * * try { * Obj ect.defineProperties(object, { * readyOnlyProp: { get: createjs.deprecate("readOnlyProp", function() { return this.alternateProp; }) }, * readWriteProp: { * get: createjs.deprecate("readOnlyProp", function() { return this.alternateProp; }), * set: createjs.deprecate("readOnlyProp", function(val) { this.alternateProp = val; }) * }); * } catch (e) {} * * @method deprecate * @param {Function} [fallbackMethod=null] A method to call when the deprecated method is used. See the example for how * @param {String} [name=null] The name of the method or property to display in the console warning. * to deprecate properties. * @return {Function} If a fallbackMethod is supplied, returns a closure that will call the fallback method after * logging the warning in the console. */ createjs.deprecate = function(fallbackMethod, name) { "use strict"; return function() { var msg = "Deprecated property or method '"+name+"'. See docs for info."; console && (console.warn ? console.warn(msg) : console.log(msg)); return fallbackMethod && fallbackMethod.apply(this, arguments); } }; //############################################################################## // Event.js //############################################################################## (function() { "use strict"; // constructor: /** * Contains properties and methods shared by all events for use with * {{#crossLink "EventDispatcher"}}{{/crossLink}}. * * Note that Event objects are often reused, so you should never * rely on an event object's state outside of the call stack it was received in. * @class Event * @param {String} type The event type. * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. * @constructor **/ function Event(type, bubbles, cancelable) { // public properties: /** * The type of event. * @property type * @type String **/ this.type = type; /** * The object that generated an event. * @property target * @type Object * @default null * @readonly */ this.target = null; /** * The current target that a bubbling event is being dispatched from. For non-bubbling events, this will * always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event * is generated from childObj, then a listener on parentObj would receive the event with * target=childObj (the original target) and currentTarget=parentObj (where the listener was added). * @property currentTarget * @type Object * @default null * @readonly */ this.currentTarget = null; /** * For bubbling events, this indicates the current event phase:
    *
  1. capture phase: starting from the top parent to the target
  2. *
  3. at target phase: currently being dispatched from the target
  4. *
  5. bubbling phase: from the target to the top parent
  6. *
* @property eventPhase * @type Number * @default 0 * @readonly */ this.eventPhase = 0; /** * Indicates whether the event will bubble through the display list. * @property bubbles * @type Boolean * @default false * @readonly */ this.bubbles = !!bubbles; /** * Indicates whether the default behaviour of this event can be cancelled via * {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor. * @property cancelable * @type Boolean * @default false * @readonly */ this.cancelable = !!cancelable; /** * The epoch time at which this event was created. * @property timeStamp * @type Number * @default 0 * @readonly */ this.timeStamp = (new Date()).getTime(); /** * Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called * on this event. * @property defaultPrevented * @type Boolean * @default false * @readonly */ this.defaultPrevented = false; /** * Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or * {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event. * @property propagationStopped * @type Boolean * @default false * @readonly */ this.propagationStopped = false; /** * Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called * on this event. * @property immediatePropagationStopped * @type Boolean * @default false * @readonly */ this.immediatePropagationStopped = false; /** * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. * @property removed * @type Boolean * @default false * @readonly */ this.removed = false; } var p = Event.prototype; // public methods: /** * Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true if the event is cancelable. * Mirrors the DOM level 2 event standard. In general, cancelable events that have `preventDefault()` called will * cancel the default behaviour associated with the event. * @method preventDefault **/ p.preventDefault = function() { this.defaultPrevented = this.cancelable&&true; }; /** * Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} to true. * Mirrors the DOM event standard. * @method stopPropagation **/ p.stopPropagation = function() { this.propagationStopped = true; }; /** * Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} and * {{#crossLink "Event/immediatePropagationStopped"}}{{/crossLink}} to true. * Mirrors the DOM event standard. * @method stopImmediatePropagation **/ p.stopImmediatePropagation = function() { this.immediatePropagationStopped = this.propagationStopped = true; }; /** * Causes the active listener to be removed via removeEventListener(); * * myBtn.addEventListener("click", function(evt) { * // do stuff... * evt.remove(); // removes this listener. * }); * * @method remove **/ p.remove = function() { this.removed = true; }; /** * Returns a clone of the Event instance. * @method clone * @return {Event} a clone of the Event instance. **/ p.clone = function() { return new Event(this.type, this.bubbles, this.cancelable); }; /** * Provides a chainable shortcut method for setting a number of properties on the instance. * * @method set * @param {Object} props A generic object containing properties to copy to the instance. * @return {Event} Returns the instance the method is called on (useful for chaining calls.) * @chainable */ p.set = function(props) { for (var n in props) { this[n] = props[n]; } return this; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Event (type="+this.type+")]"; }; createjs.Event = Event; }()); //############################################################################## // EventDispatcher.js //############################################################################## (function() { "use strict"; // constructor: /** * EventDispatcher provides methods for managing queues of event listeners and dispatching events. * * You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the * EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method. * * Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the * DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports * bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent. * * EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier * to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The * {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to * {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}. * * Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}} * method, which can be used to listeners for all events, or listeners for a specific event. The Event object also * includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener. * *

Example

* Add EventDispatcher capabilities to the "MyClass" class. * * EventDispatcher.initialize(MyClass.prototype); * * Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}). * * instance.addEventListener("eventName", handlerMethod); * function handlerMethod(event) { * console.log(event.target + " Was Clicked"); * } * * Maintaining proper scope
* Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}} * method to subscribe to events simplifies this. * * instance.addEventListener("click", function(event) { * console.log(instance == this); // false, scope is ambiguous. * }); * * instance.on("click", function(event) { * console.log(instance == this); // true, "on" uses dispatcher scope by default. * }); * * If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage * scope. * * Browser support * The event model in CreateJS can be used separately from the suite in any project, however the inheritance model * requires modern browsers (IE9+). * * * @class EventDispatcher * @constructor **/ function EventDispatcher() { // private properties: /** * @protected * @property _listeners * @type Object **/ this._listeners = null; /** * @protected * @property _captureListeners * @type Object **/ this._captureListeners = null; } var p = EventDispatcher.prototype; // static public methods: /** * Static initializer to mix EventDispatcher methods into a target object or prototype. * * EventDispatcher.initialize(MyClass.prototype); // add to the prototype of the class * EventDispatcher.initialize(myObject); // add to a specific instance * * @method initialize * @static * @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a * prototype. **/ EventDispatcher.initialize = function(target) { target.addEventListener = p.addEventListener; target.on = p.on; target.removeEventListener = target.off = p.removeEventListener; target.removeAllEventListeners = p.removeAllEventListeners; target.hasEventListener = p.hasEventListener; target.dispatchEvent = p.dispatchEvent; target._dispatchEvent = p._dispatchEvent; target.willTrigger = p.willTrigger; }; // public methods: /** * Adds the specified event listener. Note that adding multiple listeners to the same function will result in * multiple callbacks getting fired. * *

Example

* * displayObject.addEventListener("click", handleClick); * function handleClick(event) { * // Click happened. * } * * @method addEventListener * @param {String} type The string type of the event. * @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when * the event is dispatched. * @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. * @return {Function | Object} Returns the listener for chaining or assignment. **/ p.addEventListener = function(type, listener, useCapture) { var listeners; if (useCapture) { listeners = this._captureListeners = this._captureListeners||{}; } else { listeners = this._listeners = this._listeners||{}; } var arr = listeners[type]; if (arr) { this.removeEventListener(type, listener, useCapture); } arr = listeners[type]; // remove may have deleted the array if (!arr) { listeners[type] = [listener]; } else { arr.push(listener); } return listener; }; /** * A shortcut method for using addEventListener that makes it easier to specify an execution scope, have a listener * only run once, associate arbitrary data with the listener, and remove the listener. * * This method works by creating an anonymous wrapper function and subscribing it with addEventListener. * The wrapper function is returned for use with `removeEventListener` (or `off`). * * IMPORTANT: To remove a listener added with `on`, you must pass in the returned wrapper function as the listener, or use * {{#crossLink "Event/remove"}}{{/crossLink}}. Likewise, each time you call `on` a NEW wrapper function is subscribed, so multiple calls * to `on` with the same params will create multiple listeners. * *

Example

* * var listener = myBtn.on("click", handleClick, null, false, {count:3}); * function handleClick(evt, data) { * data.count -= 1; * console.log(this == myBtn); // true - scope defaults to the dispatcher * if (data.count == 0) { * alert("clicked 3 times!"); * myBtn.off("click", listener); * // alternately: evt.remove(); * } * } * * @method on * @param {String} type The string type of the event. * @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when * the event is dispatched. * @param {Object} [scope] The scope to execute the listener in. Defaults to the dispatcher/currentTarget for function listeners, and to the listener itself for object listeners (ie. using handleEvent). * @param {Boolean} [once=false] If true, the listener will remove itself after the first time it is triggered. * @param {*} [data] Arbitrary data that will be included as the second parameter when the listener is called. * @param {Boolean} [useCapture=false] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. * @return {Function} Returns the anonymous function that was created and assigned as the listener. This is needed to remove the listener later using .removeEventListener. **/ p.on = function(type, listener, scope, once, data, useCapture) { if (listener.handleEvent) { scope = scope||listener; listener = listener.handleEvent; } scope = scope||this; return this.addEventListener(type, function(evt) { listener.call(scope, evt, data); once&&evt.remove(); }, useCapture); }; /** * Removes the specified event listener. * * Important Note: that you must pass the exact function reference used when the event was added. If a proxy * function, or function closure is used as the callback, the proxy/closure reference must be used - a new proxy or * closure will not work. * *

Example

* * displayObject.removeEventListener("click", handleClick); * * @method removeEventListener * @param {String} type The string type of the event. * @param {Function | Object} listener The listener function or object. * @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. **/ p.removeEventListener = function(type, listener, useCapture) { var listeners = useCapture ? this._captureListeners : this._listeners; if (!listeners) { return; } var arr = listeners[type]; if (!arr) { return; } for (var i=0,l=arr.length; iIMPORTANT: To remove a listener added with `on`, you must pass in the returned wrapper function as the listener. See * {{#crossLink "EventDispatcher/on"}}{{/crossLink}} for an example. * * @method off * @param {String} type The string type of the event. * @param {Function | Object} listener The listener function or object. * @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. **/ p.off = p.removeEventListener; /** * Removes all listeners for the specified type, or all listeners of all types. * *

Example

* * // Remove all listeners * displayObject.removeAllEventListeners(); * * // Remove all click listeners * displayObject.removeAllEventListeners("click"); * * @method removeAllEventListeners * @param {String} [type] The string type of the event. If omitted, all listeners for all types will be removed. **/ p.removeAllEventListeners = function(type) { if (!type) { this._listeners = this._captureListeners = null; } else { if (this._listeners) { delete(this._listeners[type]); } if (this._captureListeners) { delete(this._captureListeners[type]); } } }; /** * Dispatches the specified event to all listeners. * *

Example

* * // Use a string event * this.dispatchEvent("complete"); * * // Use an Event instance * var event = new createjs.Event("progress"); * this.dispatchEvent(event); * * @method dispatchEvent * @param {Object | String | Event} eventObj An object with a "type" property, or a string type. * While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used, * dispatchEvent will construct an Event instance if necessary with the specified type. This latter approach can * be used to avoid event object instantiation for non-bubbling events that may not have any listeners. * @param {Boolean} [bubbles] Specifies the `bubbles` value when a string was passed to eventObj. * @param {Boolean} [cancelable] Specifies the `cancelable` value when a string was passed to eventObj. * @return {Boolean} Returns false if `preventDefault()` was called on a cancelable event, true otherwise. **/ p.dispatchEvent = function(eventObj, bubbles, cancelable) { if (typeof eventObj == "string") { // skip everything if there's no listeners and it doesn't bubble: var listeners = this._listeners; if (!bubbles && (!listeners || !listeners[eventObj])) { return true; } eventObj = new createjs.Event(eventObj, bubbles, cancelable); } else if (eventObj.target && eventObj.clone) { // redispatching an active event object, so clone it: eventObj = eventObj.clone(); } // TODO: it would be nice to eliminate this. Maybe in favour of evtObj instanceof Event? Or !!evtObj.createEvent try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events if (!eventObj.bubbles || !this.parent) { this._dispatchEvent(eventObj, 2); } else { var top=this, list=[top]; while (top.parent) { list.push(top = top.parent); } var i, l=list.length; // capture & atTarget for (i=l-1; i>=0 && !eventObj.propagationStopped; i--) { list[i]._dispatchEvent(eventObj, 1+(i==0)); } // bubbling for (i=1; iExample * * createjs.Ticker.addEventListener("tick", handleTick); * function handleTick(event) { * // Actions carried out each tick (aka frame) * if (!event.paused) { * // Actions carried out when the Ticker is not paused. * } * } * * @class Ticker * @uses EventDispatcher * @static **/ function Ticker() { throw "Ticker cannot be instantiated."; } // constants: /** * In this mode, Ticker uses the requestAnimationFrame API, but attempts to synch the ticks to target framerate. It * uses a simple heuristic that compares the time of the RAF return to the target time for the current frame and * dispatches the tick when the time is within a certain threshold. * * This mode has a higher variance for time between frames than {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}}, * but does not require that content be time based as with {{#crossLink "Ticker/RAF:property"}}{{/crossLink}} while * gaining the benefits of that API (screen synch, background throttling). * * Variance is usually lowest for framerates that are a divisor of the RAF frequency. This is usually 60, so * framerates of 10, 12, 15, 20, and 30 work well. * * Falls back to {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}} if the requestAnimationFrame API is not * supported. * @property RAF_SYNCHED * @static * @type {String} * @default "synched" * @readonly **/ Ticker.RAF_SYNCHED = "synched"; /** * In this mode, Ticker passes through the requestAnimationFrame heartbeat, ignoring the target framerate completely. * Because requestAnimationFrame frequency is not deterministic, any content using this mode should be time based. * You can leverage {{#crossLink "Ticker/getTime"}}{{/crossLink}} and the {{#crossLink "Ticker/tick:event"}}{{/crossLink}} * event object's "delta" properties to make this easier. * * Falls back on {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}} if the requestAnimationFrame API is not * supported. * @property RAF * @static * @type {String} * @default "raf" * @readonly **/ Ticker.RAF = "raf"; /** * In this mode, Ticker uses the setTimeout API. This provides predictable, adaptive frame timing, but does not * provide the benefits of requestAnimationFrame (screen synch, background throttling). * @property TIMEOUT * @static * @type {String} * @default "timeout" * @readonly **/ Ticker.TIMEOUT = "timeout"; // static events: /** * Dispatched each tick. The event will be dispatched to each listener even when the Ticker has been paused using * {{#crossLink "Ticker/paused:property"}}{{/crossLink}}. * *

Example

* * createjs.Ticker.addEventListener("tick", handleTick); * function handleTick(event) { * console.log("Paused:", event.paused, event.delta); * } * * @event tick * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Boolean} paused Indicates whether the ticker is currently paused. * @param {Number} delta The time elapsed in ms since the last tick. * @param {Number} time The total time in ms since Ticker was initialized. * @param {Number} runTime The total time in ms that Ticker was not paused since it was initialized. For example, * you could determine the amount of time that the Ticker has been paused since initialization with `time-runTime`. * @since 0.6.0 */ // public static properties: /** * Specifies the timing api (setTimeout or requestAnimationFrame) and mode to use. See * {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}}, {{#crossLink "Ticker/RAF:property"}}{{/crossLink}}, and * {{#crossLink "Ticker/RAF_SYNCHED:property"}}{{/crossLink}} for mode details. * @property timingMode * @static * @type {String} * @default Ticker.TIMEOUT **/ Ticker.timingMode = null; /** * Specifies a maximum value for the delta property in the tick event object. This is useful when building time * based animations and systems to prevent issues caused by large time gaps caused by background tabs, system sleep, * alert dialogs, or other blocking routines. Double the expected frame duration is often an effective value * (ex. maxDelta=50 when running at 40fps). * * This does not impact any other values (ex. time, runTime, etc), so you may experience issues if you enable maxDelta * when using both delta and other values. * * If 0, there is no maximum. * @property maxDelta * @static * @type {number} * @default 0 */ Ticker.maxDelta = 0; /** * When the ticker is paused, all listeners will still receive a tick event, but the paused property * of the event will be `true`. Also, while paused the `runTime` will not increase. See {{#crossLink "Ticker/tick:event"}}{{/crossLink}}, * {{#crossLink "Ticker/getTime"}}{{/crossLink}}, and {{#crossLink "Ticker/getEventTime"}}{{/crossLink}} for more * info. * *

Example

* * createjs.Ticker.addEventListener("tick", handleTick); * createjs.Ticker.paused = true; * function handleTick(event) { * console.log(event.paused, * createjs.Ticker.getTime(false), * createjs.Ticker.getTime(true)); * } * * @property paused * @static * @type {Boolean} * @default false **/ Ticker.paused = false; // mix-ins: // EventDispatcher methods: Ticker.removeEventListener = null; Ticker.removeAllEventListeners = null; Ticker.dispatchEvent = null; Ticker.hasEventListener = null; Ticker._listeners = null; createjs.EventDispatcher.initialize(Ticker); // inject EventDispatcher methods. Ticker._addEventListener = Ticker.addEventListener; Ticker.addEventListener = function() { !Ticker._inited&&Ticker.init(); return Ticker._addEventListener.apply(Ticker, arguments); }; // private static properties: /** * @property _inited * @static * @type {Boolean} * @private **/ Ticker._inited = false; /** * @property _startTime * @static * @type {Number} * @private **/ Ticker._startTime = 0; /** * @property _pausedTime * @static * @type {Number} * @private **/ Ticker._pausedTime=0; /** * The number of ticks that have passed * @property _ticks * @static * @type {Number} * @private **/ Ticker._ticks = 0; /** * The number of ticks that have passed while Ticker has been paused * @property _pausedTicks * @static * @type {Number} * @private **/ Ticker._pausedTicks = 0; /** * @property _interval * @static * @type {Number} * @private **/ Ticker._interval = 50; /** * @property _lastTime * @static * @type {Number} * @private **/ Ticker._lastTime = 0; /** * @property _times * @static * @type {Array} * @private **/ Ticker._times = null; /** * @property _tickTimes * @static * @type {Array} * @private **/ Ticker._tickTimes = null; /** * Stores the timeout or requestAnimationFrame id. * @property _timerId * @static * @type {Number} * @private **/ Ticker._timerId = null; /** * True if currently using requestAnimationFrame, false if using setTimeout. This may be different than timingMode * if that property changed and a tick hasn't fired. * @property _raf * @static * @type {Boolean} * @private **/ Ticker._raf = true; // static getter / setters: /** * Use the {{#crossLink "Ticker/interval:property"}}{{/crossLink}} property instead. * @method _setInterval * @private * @static * @param {Number} interval **/ Ticker._setInterval = function(interval) { Ticker._interval = interval; if (!Ticker._inited) { return; } Ticker._setupTick(); }; // Ticker.setInterval is @deprecated. Remove for 1.1+ Ticker.setInterval = createjs.deprecate(Ticker._setInterval, "Ticker.setInterval"); /** * Use the {{#crossLink "Ticker/interval:property"}}{{/crossLink}} property instead. * @method _getInterval * @private * @static * @return {Number} **/ Ticker._getInterval = function() { return Ticker._interval; }; // Ticker.getInterval is @deprecated. Remove for 1.1+ Ticker.getInterval = createjs.deprecate(Ticker._getInterval, "Ticker.getInterval"); /** * Use the {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} property instead. * @method _setFPS * @private * @static * @param {Number} value **/ Ticker._setFPS = function(value) { Ticker._setInterval(1000/value); }; // Ticker.setFPS is @deprecated. Remove for 1.1+ Ticker.setFPS = createjs.deprecate(Ticker._setFPS, "Ticker.setFPS"); /** * Use the {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} property instead. * @method _getFPS * @static * @private * @return {Number} **/ Ticker._getFPS = function() { return 1000/Ticker._interval; }; // Ticker.getFPS is @deprecated. Remove for 1.1+ Ticker.getFPS = createjs.deprecate(Ticker._getFPS, "Ticker.getFPS"); /** * Indicates the target time (in milliseconds) between ticks. Default is 50 (20 FPS). * Note that actual time between ticks may be more than specified depending on CPU load. * This property is ignored if the ticker is using the `RAF` timing mode. * @property interval * @static * @type {Number} **/ /** * Indicates the target frame rate in frames per second (FPS). Effectively just a shortcut to `interval`, where * `framerate == 1000/interval`. * @property framerate * @static * @type {Number} **/ try { Object.defineProperties(Ticker, { interval: { get: Ticker._getInterval, set: Ticker._setInterval }, framerate: { get: Ticker._getFPS, set: Ticker._setFPS } }); } catch (e) { console.log(e); } // public static methods: /** * Starts the tick. This is called automatically when the first listener is added. * @method init * @static **/ Ticker.init = function() { if (Ticker._inited) { return; } Ticker._inited = true; Ticker._times = []; Ticker._tickTimes = []; Ticker._startTime = Ticker._getTime(); Ticker._times.push(Ticker._lastTime = 0); Ticker.interval = Ticker._interval; }; /** * Stops the Ticker and removes all listeners. Use init() to restart the Ticker. * @method reset * @static **/ Ticker.reset = function() { if (Ticker._raf) { var f = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame; f&&f(Ticker._timerId); } else { clearTimeout(Ticker._timerId); } Ticker.removeAllEventListeners("tick"); Ticker._timerId = Ticker._times = Ticker._tickTimes = null; Ticker._startTime = Ticker._lastTime = Ticker._ticks = Ticker._pausedTime = 0; Ticker._inited = false; }; /** * Returns the average time spent within a tick. This can vary significantly from the value provided by getMeasuredFPS * because it only measures the time spent within the tick execution stack. * * Example 1: With a target FPS of 20, getMeasuredFPS() returns 20fps, which indicates an average of 50ms between * the end of one tick and the end of the next. However, getMeasuredTickTime() returns 15ms. This indicates that * there may be up to 35ms of "idle" time between the end of one tick and the start of the next. * * Example 2: With a target FPS of 30, {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} returns 10fps, which * indicates an average of 100ms between the end of one tick and the end of the next. However, {{#crossLink "Ticker/getMeasuredTickTime"}}{{/crossLink}} * returns 20ms. This would indicate that something other than the tick is using ~80ms (another script, DOM * rendering, etc). * @method getMeasuredTickTime * @static * @param {Number} [ticks] The number of previous ticks over which to measure the average time spent in a tick. * Defaults to the number of ticks per second. To get only the last tick's time, pass in 1. * @return {Number} The average time spent in a tick in milliseconds. **/ Ticker.getMeasuredTickTime = function(ticks) { var ttl=0, times=Ticker._tickTimes; if (!times || times.length < 1) { return -1; } // by default, calculate average for the past ~1 second: ticks = Math.min(times.length, ticks||(Ticker._getFPS()|0)); for (var i=0; i= (Ticker._interval-1)*0.97) { Ticker._tick(); } }; /** * @method _handleRAF * @static * @private **/ Ticker._handleRAF = function() { Ticker._timerId = null; Ticker._setupTick(); Ticker._tick(); }; /** * @method _handleTimeout * @static * @private **/ Ticker._handleTimeout = function() { Ticker._timerId = null; Ticker._setupTick(); Ticker._tick(); }; /** * @method _setupTick * @static * @private **/ Ticker._setupTick = function() { if (Ticker._timerId != null) { return; } // avoid duplicates var mode = Ticker.timingMode; if (mode == Ticker.RAF_SYNCHED || mode == Ticker.RAF) { var f = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame; if (f) { Ticker._timerId = f(mode == Ticker.RAF ? Ticker._handleRAF : Ticker._handleSynch); Ticker._raf = true; return; } } Ticker._raf = false; Ticker._timerId = setTimeout(Ticker._handleTimeout, Ticker._interval); }; /** * @method _tick * @static * @private **/ Ticker._tick = function() { var paused = Ticker.paused; var time = Ticker._getTime(); var elapsedTime = time-Ticker._lastTime; Ticker._lastTime = time; Ticker._ticks++; if (paused) { Ticker._pausedTicks++; Ticker._pausedTime += elapsedTime; } if (Ticker.hasEventListener("tick")) { var event = new createjs.Event("tick"); var maxDelta = Ticker.maxDelta; event.delta = (maxDelta && elapsedTime > maxDelta) ? maxDelta : elapsedTime; event.paused = paused; event.time = time; event.runTime = time-Ticker._pausedTime; Ticker.dispatchEvent(event); } Ticker._tickTimes.unshift(Ticker._getTime()-time); while (Ticker._tickTimes.length > 100) { Ticker._tickTimes.pop(); } Ticker._times.unshift(time); while (Ticker._times.length > 100) { Ticker._times.pop(); } }; /** * @method _getTime * @static * @private **/ var w=window, now=w.performance.now || w.performance.mozNow || w.performance.msNow || w.performance.oNow || w.performance.webkitNow; Ticker._getTime = function() { return ((now&&now.call(w.performance))||(new Date().getTime())) - Ticker._startTime; }; createjs.Ticker = Ticker; }()); //############################################################################## // VideoBuffer.js //############################################################################## (function() { "use strict"; // constructor: /** * When an HTML video seeks, including when looping, there is an indeterminate period before a new frame is available. * This can result in the video blinking or flashing when it is drawn to a canvas. The VideoBuffer class resolves * this issue by drawing each frame to an off-screen canvas and preserving the prior frame during a seek. * * var myBuffer = new createjs.VideoBuffer(myVideo); * var myBitmap = new Bitmap(myBuffer); * * @class VideoBuffer * @param {HTMLVideoElement} video The HTML video element to buffer. * @constructor **/ function VideoBuffer(video) { // private properties: /** * Used by Bitmap to determine when the video buffer is ready to be drawn. Not intended for general use. * @property readyState * @protected * @type {Number} * @default 0 **/ this.readyState = video.readyState; /** * @property _video * @protected * @type {HTMLVideoElement} * @default 0 **/ this._video = video; /** * @property _canvas * @protected * @type {HTMLCanvasElement} * @default 0 **/ this._canvas = null; /** * @property _lastTime * @protected * @type {Number} * @default -1 **/ this._lastTime = -1; if (this.readyState < 2) { video.addEventListener("canplaythrough", this._videoReady.bind(this)); } //once:true isn't supported everywhere, but its a non-critical optimization here. } var p = VideoBuffer.prototype; // public methods: /** * Gets an HTML canvas element showing the current video frame, or the previous frame if in a seek / loop. * Primarily for use by {{#crossLink "Bitmap"}}{{/crossLink}}. * @method getImage **/ p.getImage = function() { if (this.readyState < 2) { return; } var canvas=this._canvas, video = this._video; if (!canvas) { canvas = this._canvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; } if (video.readyState >= 2 && video.currentTime !== this._lastTime) { var ctx = canvas.getContext("2d"); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(video,0,0,canvas.width,canvas.height); this._lastTime = video.currentTime; } return canvas; }; // private methods: /** * @method _videoReady * @protected **/ p._videoReady = function() { this.readyState = 2; }; createjs.VideoBuffer = VideoBuffer; }()); //############################################################################## // MouseEvent.js //############################################################################## (function() { "use strict"; // constructor: /** * Passed as the parameter to all mouse/pointer/touch related events. For a listing of mouse events and their properties, * see the {{#crossLink "DisplayObject"}}{{/crossLink}} and {{#crossLink "Stage"}}{{/crossLink}} event listings. * @class MouseEvent * @param {String} type The event type. * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. * @param {Number} stageX The normalized x position relative to the stage. * @param {Number} stageY The normalized y position relative to the stage. * @param {MouseEvent} nativeEvent The native DOM event related to this mouse event. * @param {Number} pointerID The unique id for the pointer. * @param {Boolean} primary Indicates whether this is the primary pointer in a multitouch environment. * @param {Number} rawX The raw x position relative to the stage. * @param {Number} rawY The raw y position relative to the stage. * @param {DisplayObject} relatedTarget The secondary target for the event. * @extends Event * @constructor **/ function MouseEvent(type, bubbles, cancelable, stageX, stageY, nativeEvent, pointerID, primary, rawX, rawY, relatedTarget) { this.Event_constructor(type, bubbles, cancelable); // public properties: /** * The normalized x position on the stage. This will always be within the range 0 to stage width. * @property stageX * @type Number */ this.stageX = stageX; /** * The normalized y position on the stage. This will always be within the range 0 to stage height. * @property stageY * @type Number **/ this.stageY = stageY; /** * The raw x position relative to the stage. Normally this will be the same as the stageX value, unless * stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. * @property rawX * @type Number */ this.rawX = (rawX==null)?stageX:rawX; /** * The raw y position relative to the stage. Normally this will be the same as the stageY value, unless * stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. * @property rawY * @type Number */ this.rawY = (rawY==null)?stageY:rawY; /** * The native MouseEvent generated by the browser. The properties and API for this * event may differ between browsers. This property will be null if the * EaselJS property was not directly generated from a native MouseEvent. * @property nativeEvent * @type HtmlMouseEvent * @default null **/ this.nativeEvent = nativeEvent; /** * The unique id for the pointer (touch point or cursor). This will be either -1 for the mouse, or the system * supplied id value. * @property pointerID * @type {Number} */ this.pointerID = pointerID; /** * Indicates whether this is the primary pointer in a multitouch environment. This will always be true for the mouse. * For touch pointers, the first pointer in the current stack will be considered the primary pointer. * @property primary * @type {Boolean} */ this.primary = !!primary; /** * The secondary target for the event, if applicable. This is used for mouseout/rollout * events to indicate the object that the mouse entered from, mouseover/rollover for the object the mouse exited, * and stagemousedown/stagemouseup events for the object that was the under the cursor, if any. * * Only valid interaction targets will be returned (ie. objects with mouse listeners or a cursor set). * @property relatedTarget * @type {DisplayObject} */ this.relatedTarget = relatedTarget; } var p = createjs.extend(MouseEvent, createjs.Event); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. // getter / setters: /** * Returns the x position of the mouse in the local coordinate system of the current target (ie. the dispatcher). * @property localX * @type {Number} * @readonly */ p._get_localX = function() { return this.currentTarget.globalToLocal(this.rawX, this.rawY).x; }; /** * Returns the y position of the mouse in the local coordinate system of the current target (ie. the dispatcher). * @property localY * @type {Number} * @readonly */ p._get_localY = function() { return this.currentTarget.globalToLocal(this.rawX, this.rawY).y; }; /** * Indicates whether the event was generated by a touch input (versus a mouse input). * @property isTouch * @type {Boolean} * @readonly */ p._get_isTouch = function() { return this.pointerID !== -1; }; try { Object.defineProperties(p, { localX: { get: p._get_localX }, localY: { get: p._get_localY }, isTouch: { get: p._get_isTouch } }); } catch (e) {} // TODO: use Log // public methods: /** * Returns a clone of the MouseEvent instance. * @method clone * @return {MouseEvent} a clone of the MouseEvent instance. **/ p.clone = function() { return new MouseEvent(this.type, this.bubbles, this.cancelable, this.stageX, this.stageY, this.nativeEvent, this.pointerID, this.primary, this.rawX, this.rawY); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[MouseEvent (type="+this.type+" stageX="+this.stageX+" stageY="+this.stageY+")]"; }; createjs.MouseEvent = createjs.promote(MouseEvent, "Event"); }()); //############################################################################## // Matrix2D.js //############################################################################## (function() { "use strict"; // constructor: /** * Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices. * * This matrix can be visualized as: * * [ a c tx * b d ty * 0 0 1 ] * * Note the locations of b and c. * * @class Matrix2D * @param {Number} [a=1] Specifies the a property for the new matrix. * @param {Number} [b=0] Specifies the b property for the new matrix. * @param {Number} [c=0] Specifies the c property for the new matrix. * @param {Number} [d=1] Specifies the d property for the new matrix. * @param {Number} [tx=0] Specifies the tx property for the new matrix. * @param {Number} [ty=0] Specifies the ty property for the new matrix. * @constructor **/ function Matrix2D(a, b, c, d, tx, ty) { this.setValues(a,b,c,d,tx,ty); // public properties: // assigned in the setValues method. /** * Position (0, 0) in a 3x3 affine transformation matrix. * @property a * @type Number **/ /** * Position (0, 1) in a 3x3 affine transformation matrix. * @property b * @type Number **/ /** * Position (1, 0) in a 3x3 affine transformation matrix. * @property c * @type Number **/ /** * Position (1, 1) in a 3x3 affine transformation matrix. * @property d * @type Number **/ /** * Position (2, 0) in a 3x3 affine transformation matrix. * @property tx * @type Number **/ /** * Position (2, 1) in a 3x3 affine transformation matrix. * @property ty * @type Number **/ } var p = Matrix2D.prototype; // constants: /** * Multiplier for converting degrees to radians. Used internally by Matrix2D. * @property DEG_TO_RAD * @static * @final * @type Number * @readonly **/ Matrix2D.DEG_TO_RAD = Math.PI/180; // static public properties: /** * An identity matrix, representing a null transformation. * @property identity * @static * @type Matrix2D * @readonly **/ Matrix2D.identity = null; // set at bottom of class definition. // public methods: /** * Sets the specified values on this instance. * @method setValues * @param {Number} [a=1] Specifies the a property for the new matrix. * @param {Number} [b=0] Specifies the b property for the new matrix. * @param {Number} [c=0] Specifies the c property for the new matrix. * @param {Number} [d=1] Specifies the d property for the new matrix. * @param {Number} [tx=0] Specifies the tx property for the new matrix. * @param {Number} [ty=0] Specifies the ty property for the new matrix. * @return {Matrix2D} This instance. Useful for chaining method calls. */ p.setValues = function(a, b, c, d, tx, ty) { // don't forget to update docs in the constructor if these change: this.a = (a == null) ? 1 : a; this.b = b || 0; this.c = c || 0; this.d = (d == null) ? 1 : d; this.tx = tx || 0; this.ty = ty || 0; return this; }; /** * Appends the specified matrix properties to this matrix. All parameters are required. * This is the equivalent of multiplying `(this matrix) * (specified matrix)`. * @method append * @param {Number} a * @param {Number} b * @param {Number} c * @param {Number} d * @param {Number} tx * @param {Number} ty * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.append = function(a, b, c, d, tx, ty) { var a1 = this.a; var b1 = this.b; var c1 = this.c; var d1 = this.d; if (a != 1 || b != 0 || c != 0 || d != 1) { this.a = a1*a+c1*b; this.b = b1*a+d1*b; this.c = a1*c+c1*d; this.d = b1*c+d1*d; } this.tx = a1*tx+c1*ty+this.tx; this.ty = b1*tx+d1*ty+this.ty; return this; }; /** * Prepends the specified matrix properties to this matrix. * This is the equivalent of multiplying `(specified matrix) * (this matrix)`. * All parameters are required. * @method prepend * @param {Number} a * @param {Number} b * @param {Number} c * @param {Number} d * @param {Number} tx * @param {Number} ty * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.prepend = function(a, b, c, d, tx, ty) { var a1 = this.a; var c1 = this.c; var tx1 = this.tx; this.a = a*a1+c*this.b; this.b = b*a1+d*this.b; this.c = a*c1+c*this.d; this.d = b*c1+d*this.d; this.tx = a*tx1+c*this.ty+tx; this.ty = b*tx1+d*this.ty+ty; return this; }; /** * Appends the specified matrix to this matrix. * This is the equivalent of multiplying `(this matrix) * (specified matrix)`. * @method appendMatrix * @param {Matrix2D} matrix * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.appendMatrix = function(matrix) { return this.append(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** * Prepends the specified matrix to this matrix. * This is the equivalent of multiplying `(specified matrix) * (this matrix)`. * For example, you could calculate the combined transformation for a child object using: * * var o = myDisplayObject; * var mtx = o.getMatrix(); * while (o = o.parent) { * // prepend each parent's transformation in turn: * o.prependMatrix(o.getMatrix()); * } * @method prependMatrix * @param {Matrix2D} matrix * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.prependMatrix = function(matrix) { return this.prepend(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** * Generates matrix properties from the specified display object transform properties, and appends them to this matrix. * For example, you can use this to generate a matrix representing the transformations of a display object: * * var mtx = new createjs.Matrix2D(); * mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation); * @method appendTransform * @param {Number} x * @param {Number} y * @param {Number} scaleX * @param {Number} scaleY * @param {Number} rotation * @param {Number} skewX * @param {Number} skewY * @param {Number} regX Optional. * @param {Number} regY Optional. * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.appendTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { if (rotation%360) { var r = rotation*Matrix2D.DEG_TO_RAD; var cos = Math.cos(r); var sin = Math.sin(r); } else { cos = 1; sin = 0; } if (skewX || skewY) { // TODO: can this be combined into a single append operation? skewX *= Matrix2D.DEG_TO_RAD; skewY *= Matrix2D.DEG_TO_RAD; this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0); } else { this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y); } if (regX || regY) { // append the registration offset: this.tx -= regX*this.a+regY*this.c; this.ty -= regX*this.b+regY*this.d; } return this; }; /** * Generates matrix properties from the specified display object transform properties, and prepends them to this matrix. * For example, you could calculate the combined transformation for a child object using: * * var o = myDisplayObject; * var mtx = new createjs.Matrix2D(); * do { * // prepend each parent's transformation in turn: * mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); * } while (o = o.parent); * * Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}} * values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does. * @method prependTransform * @param {Number} x * @param {Number} y * @param {Number} scaleX * @param {Number} scaleY * @param {Number} rotation * @param {Number} skewX * @param {Number} skewY * @param {Number} regX Optional. * @param {Number} regY Optional. * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { if (rotation%360) { var r = rotation*Matrix2D.DEG_TO_RAD; var cos = Math.cos(r); var sin = Math.sin(r); } else { cos = 1; sin = 0; } if (regX || regY) { // prepend the registration offset: this.tx -= regX; this.ty -= regY; } if (skewX || skewY) { // TODO: can this be combined into a single prepend operation? skewX *= Matrix2D.DEG_TO_RAD; skewY *= Matrix2D.DEG_TO_RAD; this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0); this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); } else { this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y); } return this; }; /** * Applies a clockwise rotation transformation to the matrix. * @method rotate * @param {Number} angle The angle to rotate by, in degrees. To use a value in radians, multiply it by `180/Math.PI`. * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.rotate = function(angle) { angle = angle*Matrix2D.DEG_TO_RAD; var cos = Math.cos(angle); var sin = Math.sin(angle); var a1 = this.a; var b1 = this.b; this.a = a1*cos+this.c*sin; this.b = b1*cos+this.d*sin; this.c = -a1*sin+this.c*cos; this.d = -b1*sin+this.d*cos; return this; }; /** * Applies a skew transformation to the matrix. * @method skew * @param {Number} skewX The amount to skew horizontally in degrees. To use a value in radians, multiply it by `180/Math.PI`. * @param {Number} skewY The amount to skew vertically in degrees. * @return {Matrix2D} This matrix. Useful for chaining method calls. */ p.skew = function(skewX, skewY) { skewX = skewX*Matrix2D.DEG_TO_RAD; skewY = skewY*Matrix2D.DEG_TO_RAD; this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), 0, 0); return this; }; /** * Applies a scale transformation to the matrix. * @method scale * @param {Number} x The amount to scale horizontally. E.G. a value of 2 will double the size in the X direction, and 0.5 will halve it. * @param {Number} y The amount to scale vertically. * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.scale = function(x, y) { this.a *= x; this.b *= x; this.c *= y; this.d *= y; //this.tx *= x; //this.ty *= y; return this; }; /** * Translates the matrix on the x and y axes. * @method translate * @param {Number} x * @param {Number} y * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.translate = function(x, y) { this.tx += this.a*x + this.c*y; this.ty += this.b*x + this.d*y; return this; }; /** * Sets the properties of the matrix to those of an identity matrix (one that applies a null transformation). * @method identity * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.identity = function() { this.a = this.d = 1; this.b = this.c = this.tx = this.ty = 0; return this; }; /** * Inverts the matrix, causing it to perform the opposite transformation. * @method invert * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.invert = function() { var a1 = this.a; var b1 = this.b; var c1 = this.c; var d1 = this.d; var tx1 = this.tx; var n = a1*d1-b1*c1; this.a = d1/n; this.b = -b1/n; this.c = -c1/n; this.d = a1/n; this.tx = (c1*this.ty-d1*tx1)/n; this.ty = -(a1*this.ty-b1*tx1)/n; return this; }; /** * Returns true if the matrix is an identity matrix. * @method isIdentity * @return {Boolean} **/ p.isIdentity = function() { return this.tx === 0 && this.ty === 0 && this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1; }; /** * Returns true if this matrix is equal to the specified matrix (all property values are equal). * @method equals * @param {Matrix2D} matrix The matrix to compare. * @return {Boolean} **/ p.equals = function(matrix) { return this.tx === matrix.tx && this.ty === matrix.ty && this.a === matrix.a && this.b === matrix.b && this.c === matrix.c && this.d === matrix.d; }; /** * Transforms a point according to this matrix. * @method transformPoint * @param {Number} x The x component of the point to transform. * @param {Number} y The y component of the point to transform. * @param {Point | Object} [pt] An object to copy the result into. If omitted a generic object with x/y properties will be returned. * @return {Point} This matrix. Useful for chaining method calls. **/ p.transformPoint = function(x, y, pt) { pt = pt||{}; pt.x = x*this.a+y*this.c+this.tx; pt.y = x*this.b+y*this.d+this.ty; return pt; }; /** * Decomposes the matrix into transform properties (x, y, scaleX, scaleY, and rotation). Note that these values * may not match the transform properties you used to generate the matrix, though they will produce the same visual * results. * @method decompose * @param {Object} target The object to apply the transform properties to. If null, then a new object will be returned. * @return {Object} The target, or a new generic object with the transform properties applied. */ p.decompose = function(target) { // TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation even when scale is negative if (target == null) { target = {}; } target.x = this.tx; target.y = this.ty; target.scaleX = Math.sqrt(this.a * this.a + this.b * this.b); target.scaleY = Math.sqrt(this.c * this.c + this.d * this.d); var skewX = Math.atan2(-this.c, this.d); var skewY = Math.atan2(this.b, this.a); var delta = Math.abs(1-skewX/skewY); if (delta < 0.00001) { // effectively identical, can use rotation: target.rotation = skewY/Matrix2D.DEG_TO_RAD; if (this.a < 0 && this.d >= 0) { target.rotation += (target.rotation <= 0) ? 180 : -180; } target.skewX = target.skewY = 0; } else { target.skewX = skewX/Matrix2D.DEG_TO_RAD; target.skewY = skewY/Matrix2D.DEG_TO_RAD; } return target; }; /** * Copies all properties from the specified matrix to this matrix. * @method copy * @param {Matrix2D} matrix The matrix to copy properties from. * @return {Matrix2D} This matrix. Useful for chaining method calls. */ p.copy = function(matrix) { return this.setValues(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** * Returns a clone of the Matrix2D instance. * @method clone * @return {Matrix2D} a clone of the Matrix2D instance. **/ p.clone = function() { return new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Matrix2D (a="+this.a+" b="+this.b+" c="+this.c+" d="+this.d+" tx="+this.tx+" ty="+this.ty+")]"; }; // this has to be populated after the class is defined: Matrix2D.identity = new Matrix2D(); createjs.Matrix2D = Matrix2D; }()); //############################################################################## // DisplayProps.js //############################################################################## (function() { "use strict"; /** * Used for calculating and encapsulating display related properties. * @class DisplayProps * @param {Number} [visible=true] Visible value. * @param {Number} [alpha=1] Alpha value. * @param {Number} [shadow=null] A Shadow instance or null. * @param {Number} [compositeOperation=null] A compositeOperation value or null. * @param {Number} [matrix] A transformation matrix. Defaults to a new identity matrix. * @constructor **/ function DisplayProps(visible, alpha, shadow, compositeOperation, matrix) { this.setValues(visible, alpha, shadow, compositeOperation, matrix); // public properties: // assigned in the setValues method. /** * Property representing the alpha that will be applied to a display object. * @property alpha * @type Number **/ /** * Property representing the shadow that will be applied to a display object. * @property shadow * @type Shadow **/ /** * Property representing the compositeOperation that will be applied to a display object. * You can find a list of valid composite operations at: * https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing * @property compositeOperation * @type String **/ /** * Property representing the value for visible that will be applied to a display object. * @property visible * @type Boolean **/ /** * The transformation matrix that will be applied to a display object. * @property matrix * @type Matrix2D **/ } var p = DisplayProps.prototype; // initialization: /** * Reinitializes the instance with the specified values. * @method setValues * @param {Number} [visible=true] Visible value. * @param {Number} [alpha=1] Alpha value. * @param {Number} [shadow=null] A Shadow instance or null. * @param {Number} [compositeOperation=null] A compositeOperation value or null. * @param {Number} [matrix] A transformation matrix. Defaults to an identity matrix. * @return {DisplayProps} This instance. Useful for chaining method calls. * @chainable */ p.setValues = function (visible, alpha, shadow, compositeOperation, matrix) { this.visible = visible == null ? true : !!visible; this.alpha = alpha == null ? 1 : alpha; this.shadow = shadow; this.compositeOperation = compositeOperation; this.matrix = matrix || (this.matrix&&this.matrix.identity()) || new createjs.Matrix2D(); return this; }; // public methods: /** * Appends the specified display properties. This is generally used to apply a child's properties its parent's. * @method append * @param {Boolean} visible desired visible value * @param {Number} alpha desired alpha value * @param {Shadow} shadow desired shadow value * @param {String} compositeOperation desired composite operation value * @param {Matrix2D} [matrix] a Matrix2D instance * @return {DisplayProps} This instance. Useful for chaining method calls. * @chainable */ p.append = function(visible, alpha, shadow, compositeOperation, matrix) { this.alpha *= alpha; this.shadow = shadow || this.shadow; this.compositeOperation = compositeOperation || this.compositeOperation; this.visible = this.visible && visible; matrix&&this.matrix.appendMatrix(matrix); return this; }; /** * Prepends the specified display properties. This is generally used to apply a parent's properties to a child's. * For example, to get the combined display properties that would be applied to a child, you could use: * * var o = myDisplayObject; * var props = new createjs.DisplayProps(); * do { * // prepend each parent's props in turn: * props.prepend(o.visible, o.alpha, o.shadow, o.compositeOperation, o.getMatrix()); * } while (o = o.parent); * * @method prepend * @param {Boolean} visible desired visible value * @param {Number} alpha desired alpha value * @param {Shadow} shadow desired shadow value * @param {String} compositeOperation desired composite operation value * @param {Matrix2D} [matrix] a Matrix2D instance * @return {DisplayProps} This instance. Useful for chaining method calls. * @chainable */ p.prepend = function(visible, alpha, shadow, compositeOperation, matrix) { this.alpha *= alpha; this.shadow = this.shadow || shadow; this.compositeOperation = this.compositeOperation || compositeOperation; this.visible = this.visible && visible; matrix&&this.matrix.prependMatrix(matrix); return this; }; /** * Resets this instance and its matrix to default values. * @method identity * @return {DisplayProps} This instance. Useful for chaining method calls. * @chainable */ p.identity = function() { this.visible = true; this.alpha = 1; this.shadow = this.compositeOperation = null; this.matrix.identity(); return this; }; /** * Returns a clone of the DisplayProps instance. Clones the associated matrix. * @method clone * @return {DisplayProps} a clone of the DisplayProps instance. **/ p.clone = function() { return new DisplayProps(this.alpha, this.shadow, this.compositeOperation, this.visible, this.matrix.clone()); }; // private methods: createjs.DisplayProps = DisplayProps; })(); //############################################################################## // Point.js //############################################################################## (function() { "use strict"; // constructor: /** * Represents a point on a 2 dimensional x / y coordinate system. * *

Example

* * var point = new createjs.Point(0, 100); * * @class Point * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. * @constructor **/ function Point(x, y) { this.setValues(x, y); // public properties: // assigned in the setValues method. /** * X position. * @property x * @type Number **/ /** * Y position. * @property y * @type Number **/ } var p = Point.prototype; // public methods: /** * Sets the specified values on this instance. * @method setValues * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. * @return {Point} This instance. Useful for chaining method calls. * @chainable */ p.setValues = function(x, y) { this.x = x||0; this.y = y||0; return this; }; /** * Copies all properties from the specified point to this point. * @method copy * @param {Point} point The point to copy properties from. * @return {Point} This point. Useful for chaining method calls. * @chainable */ p.copy = function(point) { this.x = point.x; this.y = point.y; return this; }; /** * Returns a clone of the Point instance. * @method clone * @return {Point} a clone of the Point instance. **/ p.clone = function() { return new Point(this.x, this.y); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Point (x="+this.x+" y="+this.y+")]"; }; createjs.Point = Point; }()); //############################################################################## // Rectangle.js //############################################################################## (function() { "use strict"; // constructor: /** * Represents a rectangle as defined by the points (x, y) and (x+width, y+height). * *

Example

* * var rect = new createjs.Rectangle(0, 0, 100, 100); * * @class Rectangle * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. * @param {Number} [width=0] The width of the Rectangle. * @param {Number} [height=0] The height of the Rectangle. * @constructor **/ function Rectangle(x, y, width, height) { this.setValues(x, y, width, height); // public properties: // assigned in the setValues method. /** * X position. * @property x * @type Number **/ /** * Y position. * @property y * @type Number **/ /** * Width. * @property width * @type Number **/ /** * Height. * @property height * @type Number **/ } var p = Rectangle.prototype; // public methods: /** * Sets the specified values on this instance. * @method setValues * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. * @param {Number} [width=0] The width of the Rectangle. * @param {Number} [height=0] The height of the Rectangle. * @return {Rectangle} This instance. Useful for chaining method calls. * @chainable */ p.setValues = function(x, y, width, height) { // don't forget to update docs in the constructor if these change: this.x = x||0; this.y = y||0; this.width = width||0; this.height = height||0; return this; }; /** * Extends the rectangle's bounds to include the described point or rectangle. * @method extend * @param {Number} x X position of the point or rectangle. * @param {Number} y Y position of the point or rectangle. * @param {Number} [width=0] The width of the rectangle. * @param {Number} [height=0] The height of the rectangle. * @return {Rectangle} This instance. Useful for chaining method calls. * @chainable */ p.extend = function(x, y, width, height) { width = width||0; height = height||0; if (x+width > this.x+this.width) { this.width = x+width-this.x; } if (y+height > this.y+this.height) { this.height = y+height-this.y; } if (x < this.x) { this.width += this.x-x; this.x = x; } if (y < this.y) { this.height += this.y-y; this.y = y; } return this; }; /** * Adds the specified padding to the rectangle's bounds. * @method pad * @param {Number} top * @param {Number} left * @param {Number} bottom * @param {Number} right * @return {Rectangle} This instance. Useful for chaining method calls. * @chainable */ p.pad = function(top, left, bottom, right) { this.x -= left; this.y -= top; this.width += left+right; this.height += top+bottom; return this; }; /** * Copies all properties from the specified rectangle to this rectangle. * @method copy * @param {Rectangle} rectangle The rectangle to copy properties from. * @return {Rectangle} This rectangle. Useful for chaining method calls. * @chainable */ p.copy = function(rectangle) { return this.setValues(rectangle.x, rectangle.y, rectangle.width, rectangle.height); }; /** * Returns true if this rectangle fully encloses the described point or rectangle. * @method contains * @param {Number} x X position of the point or rectangle. * @param {Number} y Y position of the point or rectangle. * @param {Number} [width=0] The width of the rectangle. * @param {Number} [height=0] The height of the rectangle. * @return {Boolean} True if the described point or rectangle is contained within this rectangle. */ p.contains = function(x, y, width, height) { width = width||0; height = height||0; return (x >= this.x && x+width <= this.x+this.width && y >= this.y && y+height <= this.y+this.height); }; /** * Returns a new rectangle which contains this rectangle and the specified rectangle. * @method union * @param {Rectangle} rect The rectangle to calculate a union with. * @return {Rectangle} A new rectangle describing the union. */ p.union = function(rect) { return this.clone().extend(rect.x, rect.y, rect.width, rect.height); }; /** * Returns a new rectangle which describes the intersection (overlap) of this rectangle and the specified rectangle, * or null if they do not intersect. * @method intersection * @param {Rectangle} rect The rectangle to calculate an intersection with. * @return {Rectangle} A new rectangle describing the intersection or null. */ p.intersection = function(rect) { var x1 = rect.x, y1 = rect.y, x2 = x1+rect.width, y2 = y1+rect.height; if (this.x > x1) { x1 = this.x; } if (this.y > y1) { y1 = this.y; } if (this.x + this.width < x2) { x2 = this.x + this.width; } if (this.y + this.height < y2) { y2 = this.y + this.height; } return (x2 <= x1 || y2 <= y1) ? null : new Rectangle(x1, y1, x2-x1, y2-y1); }; /** * Returns true if the specified rectangle intersects (has any overlap) with this rectangle. * @method intersects * @param {Rectangle} rect The rectangle to compare. * @return {Boolean} True if the rectangles intersect. */ p.intersects = function(rect) { return (rect.x <= this.x+this.width && this.x <= rect.x+rect.width && rect.y <= this.y+this.height && this.y <= rect.y + rect.height); }; /** * Returns true if the width or height are equal or less than 0. * @method isEmpty * @return {Boolean} True if the rectangle is empty. */ p.isEmpty = function() { return this.width <= 0 || this.height <= 0; }; /** * Returns a clone of the Rectangle instance. * @method clone * @return {Rectangle} a clone of the Rectangle instance. **/ p.clone = function() { return new Rectangle(this.x, this.y, this.width, this.height); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Rectangle (x="+this.x+" y="+this.y+" width="+this.width+" height="+this.height+")]"; }; createjs.Rectangle = Rectangle; }()); //############################################################################## // ButtonHelper.js //############################################################################## (function() { "use strict"; // constructor: /** * The ButtonHelper is a helper class to create interactive buttons from {{#crossLink "MovieClip"}}{{/crossLink}} or * {{#crossLink "Sprite"}}{{/crossLink}} instances. This class will intercept mouse events from an object, and * automatically call {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, * to the respective animation labels, add a pointer cursor, and allows the user to define a hit state frame. * * The ButtonHelper instance does not need to be added to the stage, but a reference should be maintained to prevent * garbage collection. * * Note that over states will not work unless you call {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. * *

Example

* * var helper = new createjs.ButtonHelper(myInstance, "out", "over", "down", false, myInstance, "hit"); * myInstance.addEventListener("click", handleClick); * function handleClick(event) { * // Click Happened. * } * * @class ButtonHelper * @param {Sprite|MovieClip} target The instance to manage. * @param {String} [outLabel="out"] The label or animation to go to when the user rolls out of the button. * @param {String} [overLabel="over"] The label or animation to go to when the user rolls over the button. * @param {String} [downLabel="down"] The label or animation to go to when the user presses the button. * @param {Boolean} [play=false] If the helper should call "gotoAndPlay" or "gotoAndStop" on the button when changing * states. * @param {DisplayObject} [hitArea] An optional item to use as the hit state for the button. If this is not defined, * then the button's visible states will be used instead. Note that the same instance as the "target" argument can be * used for the hitState. * @param {String} [hitLabel] The label or animation on the hitArea instance that defines the hitArea bounds. If this is * null, then the default state of the hitArea will be used. * * @constructor */ function ButtonHelper(target, outLabel, overLabel, downLabel, play, hitArea, hitLabel) { if (!target.addEventListener) { return; } // public properties: /** * The target for this button helper. * @property target * @type MovieClip | Sprite * @readonly **/ this.target = target; /** * The label name or frame number to display when the user mouses out of the target. Defaults to "over". * @property overLabel * @type String | Number **/ this.overLabel = overLabel == null ? "over" : overLabel; /** * The label name or frame number to display when the user mouses over the target. Defaults to "out". * @property outLabel * @type String | Number **/ this.outLabel = outLabel == null ? "out" : outLabel; /** * The label name or frame number to display when the user presses on the target. Defaults to "down". * @property downLabel * @type String | Number **/ this.downLabel = downLabel == null ? "down" : downLabel; /** * If true, then ButtonHelper will call gotoAndPlay, if false, it will use gotoAndStop. Default is false. * @property play * @default false * @type Boolean **/ this.play = play; // private properties /** * @property _isPressed * @type Boolean * @protected **/ this._isPressed = false; /** * @property _isOver * @type Boolean * @protected **/ this._isOver = false; /** * @property _enabled * @type Boolean * @protected **/ this._enabled = false; // setup: target.mouseChildren = false; // prevents issues when children are removed from the display list when state changes. this.enabled = true; this.handleEvent({}); if (hitArea) { if (hitLabel) { hitArea.actionsEnabled = false; hitArea.gotoAndStop&&hitArea.gotoAndStop(hitLabel); } target.hitArea = hitArea; } } var p = ButtonHelper.prototype; // getter / setters: /** * Use the {{#crossLink "ButtonHelper/enabled:property"}}{{/crossLink}} property instead. * @method setEnabled * @param {Boolean} value The enabled property to set the instance to. * @[rptected * @protected **/ p._setEnabled = function(value) { if (value == this._enabled) { return; } var o = this.target; this._enabled = value; if (value) { o.cursor = "pointer"; o.addEventListener("rollover", this); o.addEventListener("rollout", this); o.addEventListener("mousedown", this); o.addEventListener("pressup", this); if (o._reset) { o.__reset = o._reset; o._reset = this._reset;} } else { o.cursor = null; o.removeEventListener("rollover", this); o.removeEventListener("rollout", this); o.removeEventListener("mousedown", this); o.removeEventListener("pressup", this); if (o.__reset) { o._reset = o.__reset; delete(o.__reset); } } }; // ButtonHelper.setEnabled is @deprecated. Remove for 1.1+ p.setEnabled = createjs.deprecate(p._setEnabled, "ButtonHelper.setEnabled"); /** * Use the {{#crossLink "ButtonHelper/enabled:property"}}{{/crossLink}} property instead. * @method getEnabled * @protected * @return {Boolean} **/ p._getEnabled = function() { return this._enabled; }; // ButtonHelper.getEnabled is @deprecated. Remove for 1.1+ p.getEnabled = createjs.deprecate(p._getEnabled, "ButtonHelper.getEnabled"); /** * Enables or disables the button functionality on the target. * @property enabled * @type {Boolean} **/ try { Object.defineProperties(p, { enabled: { get: p._getEnabled, set: p._setEnabled } }); } catch (e) {} // TODO: use Log // public methods: /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[ButtonHelper]"; }; // private methods: /** * @method handleEvent * @param {Object} evt The mouse event to handle. * @protected **/ p.handleEvent = function(evt) { var label, t = this.target, type = evt.type; if (type == "mousedown") { this._isPressed = true; label = this.downLabel; } else if (type == "pressup") { this._isPressed = false; label = this._isOver ? this.overLabel : this.outLabel; } else if (type == "rollover") { this._isOver = true; label = this._isPressed ? this.downLabel : this.overLabel; } else { // rollout and default this._isOver = false; label = this._isPressed ? this.overLabel : this.outLabel; } if (this.play) { t.gotoAndPlay&&t.gotoAndPlay(label); } else { t.gotoAndStop&&t.gotoAndStop(label); } }; /** * Injected into target. Preserves the paused state through a reset. * @method _reset * @protected **/ p._reset = function() { // TODO: explore better ways to handle this issue. This is hacky & disrupts object signatures. var p = this.paused; this.__reset(); this.paused = p; }; createjs.ButtonHelper = ButtonHelper; }()); //############################################################################## // Shadow.js //############################################################################## (function() { "use strict"; // constructor: /** * This class encapsulates the properties required to define a shadow to apply to a {{#crossLink "DisplayObject"}}{{/crossLink}} * via its shadow property. * *

Example

* * myImage.shadow = new createjs.Shadow("#000000", 5, 5, 10); * * @class Shadow * @constructor * @param {String} color The color of the shadow. This can be any valid CSS color value. * @param {Number} offsetX The x offset of the shadow in pixels. * @param {Number} offsetY The y offset of the shadow in pixels. * @param {Number} blur The size of the blurring effect. **/ function Shadow(color, offsetX, offsetY, blur) { // public properties: /** * The color of the shadow. This can be any valid CSS color value. * @property color * @type String * @default null */ this.color = color||"black"; /** The x offset of the shadow. * @property offsetX * @type Number * @default 0 */ this.offsetX = offsetX||0; /** The y offset of the shadow. * @property offsetY * @type Number * @default 0 */ this.offsetY = offsetY||0; /** The blur of the shadow. * @property blur * @type Number * @default 0 */ this.blur = blur||0; } var p = Shadow.prototype; // static public properties: /** * An identity shadow object (all properties are set to 0). * @property identity * @type Shadow * @static * @final * @readonly **/ Shadow.identity = new Shadow("transparent", 0, 0, 0); // public methods: /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Shadow]"; }; /** * Returns a clone of this Shadow instance. * @method clone * @return {Shadow} A clone of the current Shadow instance. **/ p.clone = function() { return new Shadow(this.color, this.offsetX, this.offsetY, this.blur); }; createjs.Shadow = Shadow; }()); //############################################################################## // SpriteSheet.js //############################################################################## (function() { "use strict"; // constructor: /** * Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually * animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100 * images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high). * * The data passed to the SpriteSheet constructor defines: *
    *
  1. The source image or images to use.
  2. *
  3. The positions of individual image frames.
  4. *
  5. Sequences of frames that form named animations. Optional.
  6. *
  7. The target playback framerate. Optional.
  8. *
*

SpriteSheet Format

* SpriteSheets are an object with two required properties (`images` and `frames`), and two optional properties * (`framerate` and `animations`). This makes them easy to define in javascript code, or in JSON. * *

images

* An array of source images. Images can be either an HTMlimage * instance, or a uri to an image. The former is recommended to control preloading. * * images: [image1, "path/to/image2.png"], * *

frames

* Defines the individual frames. There are two supported formats for frame data: * When all of the frames are the same size (in a grid), use an object with `width`, `height`, `regX`, `regY`, * and `count` properties. * *
    *
  • `width` & `height` are required and specify the dimensions of the frames
  • *
  • `regX` & `regY` indicate the registration point or "origin" of the frames
  • *
  • `spacing` indicate the spacing between frames
  • *
  • `margin` specify the margin around the image(s)
  • *
  • `count` allows you to specify the total number of frames in the spritesheet; if omitted, this will * be calculated based on the dimensions of the source images and the frames. Frames will be assigned * indexes based on their position in the source images (left to right, top to bottom).
  • *
* * frames: {width:64, height:64, count:20, regX: 32, regY:64, spacing:0, margin:0} * * If the frames are of different sizes, use an array of frame definitions. Each definition is itself an array * with 4 required and 3 optional entries, in the order: * *
    *
  • The first four, `x`, `y`, `width`, and `height` are required and define the frame rectangle.
  • *
  • The fifth, `imageIndex`, specifies the index of the source image (defaults to 0)
  • *
  • The last two, `regX` and `regY` specify the registration point of the frame
  • *
* * frames: [ * // x, y, width, height, imageIndex*, regX*, regY* * [64, 0, 96, 64], * [0, 0, 64, 64, 1, 32, 32] * // etc. * ] * *

animations

* Optional. An object defining sequences of frames to play as named animations. Each property corresponds to an * animation of the same name. Each animation must specify the frames to play, and may * also include a relative playback `speed` (ex. 2 would playback at double speed, 0.5 at half), and * the name of the `next` animation to sequence to after it completes. * * There are three formats supported for defining the frames in an animation, which can be mixed and matched as appropriate: *
    *
  1. for a single frame animation, you can simply specify the frame index * * animations: { * sit: 7 * } * *
  2. *
  3. * for an animation of consecutive frames, you can use an array with two required, and two optional entries * in the order: `start`, `end`, `next`, and `speed`. This will play the frames from start to end inclusive. * * animations: { * // start, end, next*, speed* * run: [0, 8], * jump: [9, 12, "run", 2] * } * *
  4. *
  5. * for non-consecutive frames, you can use an object with a `frames` property defining an array of frame * indexes to play in order. The object can also specify `next` and `speed` properties. * * animations: { * walk: { * frames: [1,2,3,3,2,1] * }, * shoot: { * frames: [1,4,5,6], * next: "walk", * speed: 0.5 * } * } * *
  6. *
* Note: the `speed` property was added in EaselJS 0.7.0. Earlier versions had a `frequency` * property instead, which was the inverse of `speed`. For example, a value of "4" would be 1/4 normal speed in * earlier versions, but is 4x normal speed in EaselJS 0.7.0+. * *

framerate

* Optional. Indicates the default framerate to play this spritesheet at in frames per second. See * {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} for more information. * * framerate: 20 * * Note that the Sprite framerate will only work if the stage update method is provided with the {{#crossLink "Ticker/tick:event"}}{{/crossLink}} * event generated by the {{#crossLink "Ticker"}}{{/crossLink}}. * * createjs.Ticker.on("tick", handleTick); * function handleTick(event) { * stage.update(event); * } * *

Example

* To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with three * animations: "stand" showing the first frame, "run" looping frame 1-5 inclusive, and "jump" playing frame 6-8 and * sequencing back to run. * * var data = { * images: ["sprites.jpg"], * frames: {width:50, height:50}, * animations: { * stand:0, * run:[1,5], * jump:[6,8,"run"] * } * }; * var spriteSheet = new createjs.SpriteSheet(data); * var animation = new createjs.Sprite(spriteSheet, "run"); * *

Generating SpriteSheet Images

* Spritesheets can be created manually by combining images in PhotoShop, and specifying the frame size or * coordinates manually, however there are a number of tools that facilitate this. *
    *
  • Exporting SpriteSheets or HTML5 content from Adobe Flash/Animate supports the EaselJS SpriteSheet format.
  • *
  • The popular Texture Packer has * EaselJS support. *
  • SWF animations in Adobe Flash/Animate can be exported to SpriteSheets using Zoë
  • *
* *

Cross Origin Issues

* Warning: Images loaded cross-origin will throw cross-origin security errors when interacted with * using: *
    *
  • a mouse
  • *
  • methods such as {{#crossLink "Container/getObjectUnderPoint"}}{{/crossLink}}
  • *
  • Filters (see {{#crossLink "Filter"}}{{/crossLink}})
  • *
  • caching (see {{#crossLink "DisplayObject/cache"}}{{/crossLink}})
  • *
* You can get around this by setting `crossOrigin` property on your images before passing them to EaselJS, or * setting the `crossOrigin` property on PreloadJS' LoadQueue or LoadItems. * * var image = new Image(); * img.crossOrigin="Anonymous"; * img.src = "http://server-with-CORS-support.com/path/to/image.jpg"; * * If you pass string paths to SpriteSheets, they will not work cross-origin. The server that stores the image must * support cross-origin requests, or this will not work. For more information, check out * CORS overview on MDN. * * @class SpriteSheet * @constructor * @param {Object} data An object describing the SpriteSheet data. * @extends EventDispatcher **/ function SpriteSheet(data) { this.EventDispatcher_constructor(); // public properties: /** * Indicates whether all images are finished loading. * @property complete * @type Boolean * @readonly **/ this.complete = true; /** * Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See the Sprite class * {{#crossLink "Sprite/framerate:property"}}{{/crossLink}} for more information. * @property framerate * @type Number **/ this.framerate = 0; // private properties: /** * @property _animations * @protected * @type Array **/ this._animations = null; /** * @property _frames * @protected * @type Array **/ this._frames = null; /** * @property _images * @protected * @type Array **/ this._images = null; /** * @property _data * @protected * @type Object **/ this._data = null; /** * @property _loadCount * @protected * @type Number **/ this._loadCount = 0; // only used for simple frame defs: /** * @property _frameHeight * @protected * @type Number **/ this._frameHeight = 0; /** * @property _frameWidth * @protected * @type Number **/ this._frameWidth = 0; /** * @property _numFrames * @protected * @type Number **/ this._numFrames = 0; /** * @property _regX * @protected * @type Number **/ this._regX = 0; /** * @property _regY * @protected * @type Number **/ this._regY = 0; /** * @property _spacing * @protected * @type Number **/ this._spacing = 0; /** * @property _margin * @protected * @type Number **/ this._margin = 0; // setup: this._parseData(data); } var p = createjs.extend(SpriteSheet, createjs.EventDispatcher); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. // events: /** * Dispatched when all images are loaded. Note that this only fires if the images * were not fully loaded when the sprite sheet was initialized. You should check the complete property * to prior to adding a listener. Ex. * * var sheet = new createjs.SpriteSheet(data); * if (!sheet.complete) { * // not preloaded, listen for the complete event: * sheet.addEventListener("complete", handler); * } * * @event complete * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.6.0 */ /** * Dispatched when getFrame is called with a valid frame index. This is primarily intended for use by {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}} * when doing on-demand rendering. * @event getframe * @param {Number} index The frame index. * @param {Object} frame The frame object that getFrame will return. */ /** * Dispatched when an image encounters an error. A SpriteSheet will dispatch an error event for each image that * encounters an error, and will still dispatch a {{#crossLink "SpriteSheet/complete:event"}}{{/crossLink}} * event once all images are finished processing, even if an error is encountered. * @event error * @param {String} src The source of the image that failed to load. * @since 0.8.2 */ // getter / setters: /** * Use the {{#crossLink "SpriteSheet/animations:property"}}{{/crossLink}} property instead. * @method _getAnimations * @protected * @return {Array} **/ p._getAnimations = function() { return this._animations.slice(); }; // SpriteSheet.getAnimations is @deprecated. Remove for 1.1+ p.getAnimations = createjs.deprecate(p._getAnimations, "SpriteSheet.getAnimations"); /** * Returns an array of all available animation names available on this sprite sheet as strings. * @property animations * @type {Array} * @readonly **/ try { Object.defineProperties(p, { animations: { get: p._getAnimations } }); } catch (e) {} // public methods: /** * Returns the total number of frames in the specified animation, or in the whole sprite * sheet if the animation param is omitted. Returns 0 if the spritesheet relies on calculated frame counts, and * the images have not been fully loaded. * @method getNumFrames * @param {String} animation The name of the animation to get a frame count for. * @return {Number} The number of frames in the animation, or in the entire sprite sheet if the animation param is omitted. */ p.getNumFrames = function(animation) { if (animation == null) { return this._frames ? this._frames.length : this._numFrames || 0; } else { var data = this._data[animation]; if (data == null) { return 0; } else { return data.frames.length; } } }; /** * Returns an object defining the specified animation. The returned object contains:
    *
  • frames: an array of the frame ids in the animation
  • *
  • speed: the playback speed for this animation
  • *
  • name: the name of the animation
  • *
  • next: the default animation to play next. If the animation loops, the name and next property will be the * same.
  • *
* @method getAnimation * @param {String} name The name of the animation to get. * @return {Object} a generic object with frames, speed, name, and next properties. **/ p.getAnimation = function(name) { return this._data[name]; }; /** * Returns an object specifying the image and source rect of the specified frame. The returned object has:
    *
  • an image property holding a reference to the image object in which the frame is found
  • *
  • a rect property containing a Rectangle instance which defines the boundaries for the frame within that * image.
  • *
  • A regX and regY property corresponding to the regX/Y values for the frame. *
* @method getFrame * @param {Number} frameIndex The index of the frame. * @return {Object} a generic object with image and rect properties. Returns null if the frame does not exist. **/ p.getFrame = function(frameIndex) { var frame; if (this._frames && (frame=this._frames[frameIndex])) { return frame; } return null; }; /** * Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the specified frame relative * to the origin. For example, a 90 x 70 frame with a regX of 50 and a regY of 40 would return: * * [x=-50, y=-40, width=90, height=70] * * @method getFrameBounds * @param {Number} frameIndex The index of the frame. * @param {Rectangle} [rectangle] A Rectangle instance to copy the values into. By default a new instance is created. * @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully loaded. **/ p.getFrameBounds = function(frameIndex, rectangle) { var frame = this.getFrame(frameIndex); return frame ? (rectangle||new createjs.Rectangle()).setValues(-frame.regX, -frame.regY, frame.rect.width, frame.rect.height) : null; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[SpriteSheet]"; }; /** * SpriteSheet cannot be cloned. A SpriteSheet can be shared by multiple Sprite instances without cloning it. * @method clone **/ p.clone = function() { throw("SpriteSheet cannot be cloned.") }; // private methods: /** * @method _parseData * @param {Object} data An object describing the SpriteSheet data. * @protected **/ p._parseData = function(data) { var i,l,o,a; if (data == null) { return; } this.framerate = data.framerate||0; // parse images: if (data.images && (l=data.images.length) > 0) { a = this._images = []; for (i=0; i= maxFrames) { break imgLoop; } frameCount++; this._frames.push({ image: img, rect: new createjs.Rectangle(x, y, frameWidth, frameHeight), regX: this._regX, regY: this._regY }); x += frameWidth+spacing; } y += frameHeight+spacing; } } this._numFrames = frameCount; }; createjs.SpriteSheet = createjs.promote(SpriteSheet, "EventDispatcher"); }()); //############################################################################## // Graphics.js //############################################################################## (function() { "use strict"; // constructor: /** * The Graphics class exposes an easy to use API for generating vector drawing instructions and drawing them to a * specified context. Note that you can use Graphics without any dependency on the EaselJS framework by calling {{#crossLink "Graphics/draw"}}{{/crossLink}} * directly, or it can be used with the {{#crossLink "Shape"}}{{/crossLink}} object to draw vector graphics within the * context of an EaselJS display list. * * There are two approaches to working with Graphics object: calling methods on a Graphics instance (the "Graphics API"), or * instantiating Graphics command objects and adding them to the graphics queue via {{#crossLink "Graphics/append"}}{{/crossLink}}. * The former abstracts the latter, simplifying beginning and ending paths, fills, and strokes. * * var g = new createjs.Graphics(); * g.setStrokeStyle(1); * g.beginStroke("#000000"); * g.beginFill("red"); * g.drawCircle(0,0,30); * * All drawing methods in Graphics return the Graphics instance, so they can be chained together. For example, * the following line of code would generate the instructions to draw a rectangle with a red stroke and blue fill: * * myGraphics.beginStroke("red").beginFill("blue").drawRect(20, 20, 100, 50); * * Each graphics API call generates a command object (see below). The last command to be created can be accessed via * {{#crossLink "Graphics/command:property"}}{{/crossLink}}: * * var fillCommand = myGraphics.beginFill("red").command; * // ... later, update the fill style/color: * fillCommand.style = "blue"; * // or change it to a bitmap fill: * fillCommand.bitmap(myImage); * * For more direct control of rendering, you can instantiate and append command objects to the graphics queue directly. In this case, you * need to manage path creation manually, and ensure that fill/stroke is applied to a defined path: * * // start a new path. Graphics.beginCmd is a reusable BeginPath instance: * myGraphics.append(createjs.Graphics.beginCmd); * // we need to define the path before applying the fill: * var circle = new createjs.Graphics.Circle(0,0,30); * myGraphics.append(circle); * // fill the path we just defined: * var fill = new createjs.Graphics.Fill("red"); * myGraphics.append(fill); * * These approaches can be used together, for example to insert a custom command: * * myGraphics.beginFill("red"); * var customCommand = new CustomSpiralCommand(etc); * myGraphics.append(customCommand); * myGraphics.beginFill("blue"); * myGraphics.drawCircle(0, 0, 30); * * See {{#crossLink "Graphics/append"}}{{/crossLink}} for more info on creating custom commands. * *

Tiny API

* The Graphics class also includes a "tiny API", which is one or two-letter methods that are shortcuts for all of the * Graphics methods. These methods are great for creating compact instructions, and is used by the Toolkit for CreateJS * to generate readable code. All tiny methods are marked as protected, so you can view them by enabling protected * descriptions in the docs. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TinyMethodTinyMethod
mt{{#crossLink "Graphics/moveTo"}}{{/crossLink}} lt {{#crossLink "Graphics/lineTo"}}{{/crossLink}}
a/at{{#crossLink "Graphics/arc"}}{{/crossLink}} / {{#crossLink "Graphics/arcTo"}}{{/crossLink}} bt{{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}}
qt{{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} (also curveTo)r{{#crossLink "Graphics/rect"}}{{/crossLink}}
cp{{#crossLink "Graphics/closePath"}}{{/crossLink}} c{{#crossLink "Graphics/clear"}}{{/crossLink}}
f{{#crossLink "Graphics/beginFill"}}{{/crossLink}} lf{{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}}
rf{{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} bf{{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}}
ef{{#crossLink "Graphics/endFill"}}{{/crossLink}} ss / sd{{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} / {{#crossLink "Graphics/setStrokeDash"}}{{/crossLink}}
s{{#crossLink "Graphics/beginStroke"}}{{/crossLink}} ls{{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}}
rs{{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} bs{{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}}
es{{#crossLink "Graphics/endStroke"}}{{/crossLink}} dr{{#crossLink "Graphics/drawRect"}}{{/crossLink}}
rr{{#crossLink "Graphics/drawRoundRect"}}{{/crossLink}} rc{{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}}
dc{{#crossLink "Graphics/drawCircle"}}{{/crossLink}} de{{#crossLink "Graphics/drawEllipse"}}{{/crossLink}}
dp{{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} p{{#crossLink "Graphics/decodePath"}}{{/crossLink}}
* * Here is the above example, using the tiny API instead. * * myGraphics.s("red").f("blue").r(20, 20, 100, 50); * * @class Graphics * @constructor **/ function Graphics() { // public properties /** * Holds a reference to the last command that was created or appended. For example, you could retain a reference * to a Fill command in order to dynamically update the color later by using: * * var myFill = myGraphics.beginFill("red").command; * // update color later: * myFill.style = "yellow"; * * @property command * @type Object **/ this.command = null; // private properties /** * @property _stroke * @protected * @type {Stroke} **/ this._stroke = null; /** * @property _strokeStyle * @protected * @type {StrokeStyle} **/ this._strokeStyle = null; /** * @property _oldStrokeStyle * @protected * @type {StrokeStyle} **/ this._oldStrokeStyle = null; /** * @property _strokeDash * @protected * @type {StrokeDash} **/ this._strokeDash = null; /** * @property _oldStrokeDash * @protected * @type {StrokeDash} **/ this._oldStrokeDash = null; /** * @property _strokeIgnoreScale * @protected * @type Boolean **/ this._strokeIgnoreScale = false; /** * @property _fill * @protected * @type {Fill} **/ this._fill = null; /** * @property _instructions * @protected * @type {Array} **/ this._instructions = []; /** * Indicates the last instruction index that was committed. * @property _commitIndex * @protected * @type {Number} **/ this._commitIndex = 0; /** * Uncommitted instructions. * @property _activeInstructions * @protected * @type {Array} **/ this._activeInstructions = []; /** * This indicates that there have been changes to the activeInstruction list since the last updateInstructions call. * @property _dirty * @protected * @type {Boolean} * @default false **/ this._dirty = false; /** * Index to draw from if a store operation has happened. * @property _storeIndex * @protected * @type {Number} * @default 0 **/ this._storeIndex = 0; // setup: this.clear(); } var p = Graphics.prototype; var G = Graphics; // shortcut // static public methods: /** * Returns a CSS compatible color string based on the specified RGB numeric color values in the format * "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)". For example, * * createjs.Graphics.getRGB(50, 100, 150, 0.5); * // Returns "rgba(50,100,150,0.5)" * * It also supports passing a single hex color value as the first param, and an optional alpha value as the second * param. For example, * * createjs.Graphics.getRGB(0xFF00FF, 0.2); * // Returns "rgba(255,0,255,0.2)" * * @method getRGB * @static * @param {Number} r The red component for the color, between 0 and 0xFF (255). * @param {Number} g The green component for the color, between 0 and 0xFF (255). * @param {Number} b The blue component for the color, between 0 and 0xFF (255). * @param {Number} [alpha] The alpha component for the color where 0 is fully transparent and 1 is fully opaque. * @return {String} A CSS compatible color string based on the specified RGB numeric color values in the format * "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)". **/ Graphics.getRGB = function(r, g, b, alpha) { if (r != null && b == null) { alpha = g; b = r&0xFF; g = r>>8&0xFF; r = r>>16&0xFF; } if (alpha == null) { return "rgb("+r+","+g+","+b+")"; } else { return "rgba("+r+","+g+","+b+","+alpha+")"; } }; /** * Returns a CSS compatible color string based on the specified HSL numeric color values in the format "hsla(360,100,100,1.0)", * or if alpha is null then in the format "hsl(360,100,100)". * * createjs.Graphics.getHSL(150, 100, 70); * // Returns "hsl(150,100,70)" * * @method getHSL * @static * @param {Number} hue The hue component for the color, between 0 and 360. * @param {Number} saturation The saturation component for the color, between 0 and 100. * @param {Number} lightness The lightness component for the color, between 0 and 100. * @param {Number} [alpha] The alpha component for the color where 0 is fully transparent and 1 is fully opaque. * @return {String} A CSS compatible color string based on the specified HSL numeric color values in the format * "hsla(360,100,100,1.0)", or if alpha is null then in the format "hsl(360,100,100)". **/ Graphics.getHSL = function(hue, saturation, lightness, alpha) { if (alpha == null) { return "hsl("+(hue%360)+","+saturation+"%,"+lightness+"%)"; } else { return "hsla("+(hue%360)+","+saturation+"%,"+lightness+"%,"+alpha+")"; } }; // static properties: /** * A reusable instance of {{#crossLink "Graphics/BeginPath"}}{{/crossLink}} to avoid * unnecessary instantiation. * @property beginCmd * @type {Graphics.BeginPath} * @static **/ // defined at the bottom of this file. /** * Map of Base64 characters to values. Used by {{#crossLink "Graphics/decodePath"}}{{/crossLink}}. * @property BASE_64 * @static * @final * @readonly * @type {Object} **/ Graphics.BASE_64 = {"A":0,"B":1,"C":2,"D":3,"E":4,"F":5,"G":6,"H":7,"I":8,"J":9,"K":10,"L":11,"M":12,"N":13,"O":14,"P":15,"Q":16,"R":17,"S":18,"T":19,"U":20,"V":21,"W":22,"X":23,"Y":24,"Z":25,"a":26,"b":27,"c":28,"d":29,"e":30,"f":31,"g":32,"h":33,"i":34,"j":35,"k":36,"l":37,"m":38,"n":39,"o":40,"p":41,"q":42,"r":43,"s":44,"t":45,"u":46,"v":47,"w":48,"x":49,"y":50,"z":51,"0":52,"1":53,"2":54,"3":55,"4":56,"5":57,"6":58,"7":59,"8":60,"9":61,"+":62,"/":63}; /** * Maps numeric values for the caps parameter of {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} to * corresponding string values. This is primarily for use with the tiny API. The mappings are as follows: 0 to * "butt", 1 to "round", and 2 to "square". * For example, to set the line caps to "square": * * myGraphics.ss(16, 2); * * @property STROKE_CAPS_MAP * @static * @final * @readonly * @type {Array} **/ Graphics.STROKE_CAPS_MAP = ["butt", "round", "square"]; /** * Maps numeric values for the joints parameter of {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} to * corresponding string values. This is primarily for use with the tiny API. The mappings are as follows: 0 to * "miter", 1 to "round", and 2 to "bevel". * For example, to set the line joints to "bevel": * * myGraphics.ss(16, 0, 2); * * @property STROKE_JOINTS_MAP * @static * @final * @readonly * @type {Array} **/ Graphics.STROKE_JOINTS_MAP = ["miter", "round", "bevel"]; /** * @property _ctx * @static * @protected * @type {CanvasRenderingContext2D} **/ var canvas = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")); if (canvas.getContext) { Graphics._ctx = canvas.getContext("2d"); canvas.width = canvas.height = 1; } // getter / setters: /** * Use the {{#crossLink "Graphics/instructions:property"}}{{/crossLink}} property instead. * @method _getInstructions * @protected * @return {Array} The instructions array, useful for chaining **/ p._getInstructions = function() { this._updateInstructions(); return this._instructions; }; // Graphics.getInstructions is @deprecated. Remove for 1.1+ p.getInstructions = createjs.deprecate(p._getInstructions, "Graphics.getInstructions"); /** * Returns the graphics instructions array. Each entry is a graphics command object (ex. Graphics.Fill, Graphics.Rect) * Modifying the returned array directly is not recommended, and is likely to result in unexpected behaviour. * * This property is mainly intended for introspection of the instructions (ex. for graphics export). * @property instructions * @type {Array} * @readonly **/ try { Object.defineProperties(p, { instructions: { get: p._getInstructions } }); } catch (e) {} // public methods: /** * Returns true if this Graphics instance has no drawing commands. * @method isEmpty * @return {Boolean} Returns true if this Graphics instance has no drawing commands. **/ p.isEmpty = function() { return !(this._instructions.length || this._activeInstructions.length); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Object} data Optional data that is passed to graphics command exec methods. When called from a Shape instance, the shape passes itself as the data parameter. This can be used by custom graphic commands to insert contextual data. **/ p.draw = function(ctx, data) { this._updateInstructions(); var instr = this._instructions; for (var i=this._storeIndex, l=instr.length; iDisplayObject.mask to draw the clipping path, for example. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method drawAsPath * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. **/ p.drawAsPath = function(ctx) { this._updateInstructions(); var instr, instrs = this._instructions; for (var i=this._storeIndex, l=instrs.length; i * whatwg spec. * @method lineTo * @param {Number} x The x coordinate the drawing point should draw to. * @param {Number} y The y coordinate the drawing point should draw to. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.lineTo = function(x, y) { return this.append(new G.LineTo(x,y)); }; /** * Draws an arc with the specified control points and radius. For detailed information, read the * * whatwg spec. A tiny API method "at" also exists. * @method arcTo * @param {Number} x1 * @param {Number} y1 * @param {Number} x2 * @param {Number} y2 * @param {Number} radius * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.arcTo = function(x1, y1, x2, y2, radius) { return this.append(new G.ArcTo(x1, y1, x2, y2, radius)); }; /** * Draws an arc defined by the radius, startAngle and endAngle arguments, centered at the position (x, y). For * example, to draw a full circle with a radius of 20 centered at (100, 100): * * arc(100, 100, 20, 0, Math.PI*2); * * For detailed information, read the * whatwg spec. * A tiny API method "a" also exists. * @method arc * @param {Number} x * @param {Number} y * @param {Number} radius * @param {Number} startAngle Measured in radians. * @param {Number} endAngle Measured in radians. * @param {Boolean} anticlockwise * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.arc = function(x, y, radius, startAngle, endAngle, anticlockwise) { return this.append(new G.Arc(x, y, radius, startAngle, endAngle, anticlockwise)); }; /** * Draws a quadratic curve from the current drawing point to (x, y) using the control point (cpx, cpy). For detailed * information, read the * whatwg spec. A tiny API method "qt" also exists. * @method quadraticCurveTo * @param {Number} cpx * @param {Number} cpy * @param {Number} x * @param {Number} y * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.quadraticCurveTo = function(cpx, cpy, x, y) { return this.append(new G.QuadraticCurveTo(cpx, cpy, x, y)); }; /** * Draws a bezier curve from the current drawing point to (x, y) using the control points (cp1x, cp1y) and (cp2x, * cp2y). For detailed information, read the * * whatwg spec. A tiny API method "bt" also exists. * @method bezierCurveTo * @param {Number} cp1x * @param {Number} cp1y * @param {Number} cp2x * @param {Number} cp2y * @param {Number} x * @param {Number} y * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { return this.append(new G.BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)); }; /** * Draws a rectangle at (x, y) with the specified width and height using the current fill and/or stroke. * For detailed information, read the * * whatwg spec. A tiny API method "r" also exists. * @method rect * @param {Number} x * @param {Number} y * @param {Number} w Width of the rectangle * @param {Number} h Height of the rectangle * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.rect = function(x, y, w, h) { return this.append(new G.Rect(x, y, w, h)); }; /** * Closes the current path, effectively drawing a line from the current drawing point to the first drawing point specified * since the fill or stroke was last set. A tiny API method "cp" also exists. * @method closePath * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.closePath = function() { return this._activeInstructions.length ? this.append(new G.ClosePath()) : this; }; // public methods that roughly map to Adobe Flash/Animate graphics APIs: /** * Clears all drawing instructions, effectively resetting this Graphics instance. Any line and fill styles will need * to be redefined to draw shapes following a clear call. A tiny API method "c" also exists. * @method clear * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.clear = function() { this._instructions.length = this._activeInstructions.length = this._commitIndex = 0; this._strokeStyle = this._oldStrokeStyle = this._stroke = this._fill = this._strokeDash = this._oldStrokeDash = null; this._dirty = this._strokeIgnoreScale = false; return this; }; /** * Begins a fill with the specified color. This ends the current sub-path. A tiny API method "f" also exists. * @method beginFill * @param {String} color A CSS compatible color value (ex. "red", "#FF0000", or "rgba(255,0,0,0.5)"). Setting to * null will result in no fill. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginFill = function(color) { return this._setFill(color ? new G.Fill(color) : null); }; /** * Begins a linear gradient fill defined by the line (x0, y0) to (x1, y1). This ends the current sub-path. For * example, the following code defines a black to white vertical gradient ranging from 20px to 120px, and draws a * square to display it: * * myGraphics.beginLinearGradientFill(["#000","#FFF"], [0, 1], 0, 20, 0, 120).drawRect(20, 20, 120, 120); * * A tiny API method "lf" also exists. * @method beginLinearGradientFill * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define a gradient * drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, 0.9] would draw * the first color to 10% then interpolating to the second color at 90%. * @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. * @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginLinearGradientFill = function(colors, ratios, x0, y0, x1, y1) { return this._setFill(new G.Fill().linearGradient(colors, ratios, x0, y0, x1, y1)); }; /** * Begins a radial gradient fill. This ends the current sub-path. For example, the following code defines a red to * blue radial gradient centered at (100, 100), with a radius of 50, and draws a circle to display it: * * myGraphics.beginRadialGradientFill(["#F00","#00F"], [0, 1], 100, 100, 0, 100, 100, 50).drawCircle(100, 100, 50); * * A tiny API method "rf" also exists. * @method beginRadialGradientFill * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define * a gradient drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, * 0.9] would draw the first color to 10% then interpolating to the second color at 90%. * @param {Number} x0 Center position of the inner circle that defines the gradient. * @param {Number} y0 Center position of the inner circle that defines the gradient. * @param {Number} r0 Radius of the inner circle that defines the gradient. * @param {Number} x1 Center position of the outer circle that defines the gradient. * @param {Number} y1 Center position of the outer circle that defines the gradient. * @param {Number} r1 Radius of the outer circle that defines the gradient. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginRadialGradientFill = function(colors, ratios, x0, y0, r0, x1, y1, r1) { return this._setFill(new G.Fill().radialGradient(colors, ratios, x0, y0, r0, x1, y1, r1)); }; /** * Begins a pattern fill using the specified image. This ends the current sub-path. A tiny API method "bf" also * exists. * @method beginBitmapFill * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use * as the pattern. Must be loaded prior to creating a bitmap fill, or the fill will be empty. * @param {String} repetition Optional. Indicates whether to repeat the image in the fill area. One of "repeat", * "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". Note that Firefox does not support "repeat-x" or * "repeat-y" (latest tests were in FF 20.0), and will default to "repeat". * @param {Matrix2D} matrix Optional. Specifies a transformation matrix for the bitmap fill. This transformation * will be applied relative to the parent transform. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginBitmapFill = function(image, repetition, matrix) { return this._setFill(new G.Fill(null,matrix).bitmap(image, repetition)); }; /** * Ends the current sub-path, and begins a new one with no fill. Functionally identical to beginFill(null). * A tiny API method "ef" also exists. * @method endFill * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.endFill = function() { return this.beginFill(); }; /** * Sets the stroke style. Like all drawing methods, this can be chained, so you can define * the stroke style and color in a single line of code like so: * * myGraphics.setStrokeStyle(8,"round").beginStroke("#F00"); * * A tiny API method "ss" also exists. * @method setStrokeStyle * @param {Number} thickness The width of the stroke. * @param {String | Number} [caps=0] Indicates the type of caps to use at the end of lines. One of butt, * round, or square. Defaults to "butt". Also accepts the values 0 (butt), 1 (round), and 2 (square) for use with * the tiny API. * @param {String | Number} [joints=0] Specifies the type of joints that should be used where two lines meet. * One of bevel, round, or miter. Defaults to "miter". Also accepts the values 0 (miter), 1 (round), and 2 (bevel) * for use with the tiny API. * @param {Number} [miterLimit=10] If joints is set to "miter", then you can specify a miter limit ratio which * controls at what point a mitered joint will be clipped. * @param {Boolean} [ignoreScale=false] If true, the stroke will be drawn at the specified thickness regardless * of active transformations. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.setStrokeStyle = function(thickness, caps, joints, miterLimit, ignoreScale) { this._updateInstructions(true); this._strokeStyle = this.command = new G.StrokeStyle(thickness, caps, joints, miterLimit, ignoreScale); // ignoreScale lives on Stroke, not StrokeStyle, so we do a little trickery: if (this._stroke) { this._stroke.ignoreScale = ignoreScale; } this._strokeIgnoreScale = ignoreScale; return this; }; /** * Sets or clears the stroke dash pattern. * * myGraphics.setStrokeDash([20, 10], 0); * * A tiny API method `sd` also exists. * @method setStrokeDash * @param {Array} [segments] An array specifying the dash pattern, alternating between line and gap. * For example, `[20,10]` would create a pattern of 20 pixel lines with 10 pixel gaps between them. * Passing null or an empty array will clear the existing stroke dash. * @param {Number} [offset=0] The offset of the dash pattern. For example, you could increment this value to create a "marching ants" effect. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.setStrokeDash = function(segments, offset) { this._updateInstructions(true); this._strokeDash = this.command = new G.StrokeDash(segments, offset); return this; }; /** * Begins a stroke with the specified color. This ends the current sub-path. A tiny API method "s" also exists. * @method beginStroke * @param {String} color A CSS compatible color value (ex. "#FF0000", "red", or "rgba(255,0,0,0.5)"). Setting to * null will result in no stroke. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginStroke = function(color) { return this._setStroke(color ? new G.Stroke(color) : null); }; /** * Begins a linear gradient stroke defined by the line (x0, y0) to (x1, y1). This ends the current sub-path. For * example, the following code defines a black to white vertical gradient ranging from 20px to 120px, and draws a * square to display it: * * myGraphics.setStrokeStyle(10). * beginLinearGradientStroke(["#000","#FFF"], [0, 1], 0, 20, 0, 120).drawRect(20, 20, 120, 120); * * A tiny API method "ls" also exists. * @method beginLinearGradientStroke * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define * a gradient drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, * 0.9] would draw the first color to 10% then interpolating to the second color at 90%. * @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. * @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginLinearGradientStroke = function(colors, ratios, x0, y0, x1, y1) { return this._setStroke(new G.Stroke().linearGradient(colors, ratios, x0, y0, x1, y1)); }; /** * Begins a radial gradient stroke. This ends the current sub-path. For example, the following code defines a red to * blue radial gradient centered at (100, 100), with a radius of 50, and draws a rectangle to display it: * * myGraphics.setStrokeStyle(10) * .beginRadialGradientStroke(["#F00","#00F"], [0, 1], 100, 100, 0, 100, 100, 50) * .drawRect(50, 90, 150, 110); * * A tiny API method "rs" also exists. * @method beginRadialGradientStroke * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define * a gradient drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, * 0.9] would draw the first color to 10% then interpolating to the second color at 90%, then draw the second color * to 100%. * @param {Number} x0 Center position of the inner circle that defines the gradient. * @param {Number} y0 Center position of the inner circle that defines the gradient. * @param {Number} r0 Radius of the inner circle that defines the gradient. * @param {Number} x1 Center position of the outer circle that defines the gradient. * @param {Number} y1 Center position of the outer circle that defines the gradient. * @param {Number} r1 Radius of the outer circle that defines the gradient. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginRadialGradientStroke = function(colors, ratios, x0, y0, r0, x1, y1, r1) { return this._setStroke(new G.Stroke().radialGradient(colors, ratios, x0, y0, r0, x1, y1, r1)); }; /** * Begins a pattern fill using the specified image. This ends the current sub-path. Note that unlike bitmap fills, * strokes do not currently support a matrix parameter due to limitations in the canvas API. A tiny API method "bs" * also exists. * @method beginBitmapStroke * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use * as the pattern. Must be loaded prior to creating a bitmap fill, or the fill will be empty. * @param {String} [repetition=repeat] Optional. Indicates whether to repeat the image in the fill area. One of * "repeat", "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.beginBitmapStroke = function(image, repetition) { // NOTE: matrix is not supported for stroke because transforms on strokes also affect the drawn stroke width. return this._setStroke(new G.Stroke().bitmap(image, repetition)); }; /** * Ends the current sub-path, and begins a new one with no stroke. Functionally identical to beginStroke(null). * A tiny API method "es" also exists. * @method endStroke * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.endStroke = function() { return this.beginStroke(); }; /** * Maps the familiar ActionScript curveTo() method to the functionally similar {{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} * method. * @method curveTo * @param {Number} cpx * @param {Number} cpy * @param {Number} x * @param {Number} y * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.curveTo = p.quadraticCurveTo; /** * * Maps the familiar ActionScript drawRect() method to the functionally similar {{#crossLink "Graphics/rect"}}{{/crossLink}} * method. * @method drawRect * @param {Number} x * @param {Number} y * @param {Number} w Width of the rectangle * @param {Number} h Height of the rectangle * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.drawRect = p.rect; /** * Draws a rounded rectangle with all corners with the specified radius. * @method drawRoundRect * @param {Number} x * @param {Number} y * @param {Number} w * @param {Number} h * @param {Number} radius Corner radius. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.drawRoundRect = function(x, y, w, h, radius) { return this.drawRoundRectComplex(x, y, w, h, radius, radius, radius, radius); }; /** * Draws a rounded rectangle with different corner radii. Supports positive and negative corner radii. A tiny API * method "rc" also exists. * @method drawRoundRectComplex * @param {Number} x The horizontal coordinate to draw the round rect. * @param {Number} y The vertical coordinate to draw the round rect. * @param {Number} w The width of the round rect. * @param {Number} h The height of the round rect. * @param {Number} radiusTL Top left corner radius. * @param {Number} radiusTR Top right corner radius. * @param {Number} radiusBR Bottom right corner radius. * @param {Number} radiusBL Bottom left corner radius. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.drawRoundRectComplex = function(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL) { return this.append(new G.RoundRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL)); }; /** * Draws a circle with the specified radius at (x, y). * * var g = new createjs.Graphics(); * g.setStrokeStyle(1); * g.beginStroke(createjs.Graphics.getRGB(0,0,0)); * g.beginFill(createjs.Graphics.getRGB(255,0,0)); * g.drawCircle(0,0,3); * * var s = new createjs.Shape(g); * s.x = 100; * s.y = 100; * * stage.addChild(s); * stage.update(); * * A tiny API method "dc" also exists. * @method drawCircle * @param {Number} x x coordinate center point of circle. * @param {Number} y y coordinate center point of circle. * @param {Number} radius Radius of circle. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.drawCircle = function(x, y, radius) { return this.append(new G.Circle(x, y, radius)); }; /** * Draws an ellipse (oval) with a specified width (w) and height (h). Similar to {{#crossLink "Graphics/drawCircle"}}{{/crossLink}}, * except the width and height can be different. A tiny API method "de" also exists. * @method drawEllipse * @param {Number} x The left coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} * which draws from center. * @param {Number} y The top coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} * which draws from the center. * @param {Number} w The height (horizontal diameter) of the ellipse. The horizontal radius will be half of this * number. * @param {Number} h The width (vertical diameter) of the ellipse. The vertical radius will be half of this number. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.drawEllipse = function(x, y, w, h) { return this.append(new G.Ellipse(x, y, w, h)); }; /** * Draws a star if pointSize is greater than 0, or a regular polygon if pointSize is 0 with the specified number of * points. For example, the following code will draw a familiar 5 pointed star shape centered at 100, 100 and with a * radius of 50: * * myGraphics.beginFill("#FF0").drawPolyStar(100, 100, 50, 5, 0.6, -90); * // Note: -90 makes the first point vertical * * A tiny API method "dp" also exists. * * @method drawPolyStar * @param {Number} x Position of the center of the shape. * @param {Number} y Position of the center of the shape. * @param {Number} radius The outer radius of the shape. * @param {Number} sides The number of points on the star or sides on the polygon. * @param {Number} pointSize The depth or "pointy-ness" of the star points. A pointSize of 0 will draw a regular * polygon (no points), a pointSize of 1 will draw nothing because the points are infinitely pointy. * @param {Number} angle The angle of the first point / corner. For example a value of 0 will draw the first point * directly to the right of the center. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.drawPolyStar = function(x, y, radius, sides, pointSize, angle) { return this.append(new G.PolyStar(x, y, radius, sides, pointSize, angle)); }; /** * Appends a graphics command object to the graphics queue. Command objects expose an "exec" method * that accepts two parameters: the Context2D to operate on, and an arbitrary data object passed into * {{#crossLink "Graphics/draw"}}{{/crossLink}}. The latter will usually be the Shape instance that called draw. * * This method is used internally by Graphics methods, such as drawCircle, but can also be used directly to insert * built-in or custom graphics commands. For example: * * // attach data to our shape, so we can access it during the draw: * myShape.color = "red"; * * // append a Circle command object: * myShape.graphics.append(new createjs.Graphics.Circle(50, 50, 30)); * * // append a custom command object with an exec method that sets the fill style * // based on the shape's data, and then fills the circle. * myShape.graphics.append({exec:function(ctx, shape) { * ctx.fillStyle = shape.color; * ctx.fill(); * }}); * * @method append * @param {Object} command A graphics command object exposing an "exec" method. * @param {boolean} clean The clean param is primarily for internal use. A value of true indicates that a command does not generate a path that should be stroked or filled. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.append = function(command, clean) { this._activeInstructions.push(command); this.command = command; if (!clean) { this._dirty = true; } return this; }; /** * Decodes a compact encoded path string into a series of draw instructions. * This format is not intended to be human readable, and is meant for use by authoring tools. * The format uses a base64 character set, with each character representing 6 bits, to define a series of draw * commands. * * Each command is comprised of a single "header" character followed by a variable number of alternating x and y * position values. Reading the header bits from left to right (most to least significant): bits 1 to 3 specify the * type of operation (0-moveTo, 1-lineTo, 2-quadraticCurveTo, 3-bezierCurveTo, 4-closePath, 5-7 unused). Bit 4 * indicates whether position values use 12 bits (2 characters) or 18 bits (3 characters), with a one indicating the * latter. Bits 5 and 6 are currently unused. * * Following the header is a series of 0 (closePath), 2 (moveTo, lineTo), 4 (quadraticCurveTo), or 6 (bezierCurveTo) * parameters. These parameters are alternating x/y positions represented by 2 or 3 characters (as indicated by the * 4th bit in the command char). These characters consist of a 1 bit sign (1 is negative, 0 is positive), followed * by an 11 (2 char) or 17 (3 char) bit integer value. All position values are in tenths of a pixel. Except in the * case of move operations which are absolute, this value is a delta from the previous x or y position (as * appropriate). * * For example, the string "A3cAAMAu4AAA" represents a line starting at -150,0 and ending at 150,0. *
A - bits 000000. First 3 bits (000) indicate a moveTo operation. 4th bit (0) indicates 2 chars per * parameter. *
n0 - 110111011100. Absolute x position of -150.0px. First bit indicates a negative value, remaining bits * indicate 1500 tenths of a pixel. *
AA - 000000000000. Absolute y position of 0. *
I - 001100. First 3 bits (001) indicate a lineTo operation. 4th bit (1) indicates 3 chars per parameter. *
Au4 - 000000101110111000. An x delta of 300.0px, which is added to the previous x value of -150.0px to * provide an absolute position of +150.0px. *
AAA - 000000000000000000. A y delta value of 0. * * A tiny API method "p" also exists. * @method decodePath * @param {String} str The path string to decode. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.decodePath = function(str) { var instructions = [this.moveTo, this.lineTo, this.quadraticCurveTo, this.bezierCurveTo, this.closePath]; var paramCount = [2, 2, 4, 6, 0]; var i=0, l=str.length; var params = []; var x=0, y=0; var base64 = Graphics.BASE_64; while (i>3; // highest order bits 1-3 code for operation. var f = instructions[fi]; // check that we have a valid instruction & that the unused bits are empty: if (!f || (n&3)) { throw("bad path data (@"+i+"): "+c); } var pl = paramCount[fi]; if (!fi) { x=y=0; } // move operations reset the position. params.length = 0; i++; var charCount = (n>>2&1)+2; // 4th header bit indicates number size for this operation. for (var p=0; p>5) ? -1 : 1; num = ((num&31)<<6)|(base64[str.charAt(i+1)]); if (charCount == 3) { num = (num<<6)|(base64[str.charAt(i+2)]); } num = sign*num/10; if (p%2) { x = (num += x); } else { y = (num += y); } params[p] = num; i += charCount; } f.apply(this,params); } return this; }; /** * Stores all graphics commands so they won't be executed in future draws. Calling store() a second time adds to * the existing store. This also affects `drawAsPath()`. * * This is useful in cases where you are creating vector graphics in an iterative manner (ex. generative art), so * that only new graphics need to be drawn (which can provide huge performance benefits), but you wish to retain all * of the vector instructions for later use (ex. scaling, modifying, or exporting). * * Note that calling store() will force the active path (if any) to be ended in a manner similar to changing * the fill or stroke. * * For example, consider a application where the user draws lines with the mouse. As each line segment (or collection of * segments) are added to a Shape, it can be rasterized using {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}, * and then stored, so that it can be redrawn at a different scale when the application is resized, or exported to SVG. * * // set up cache: * myShape.cache(0,0,500,500,scale); * * // when the user drags, draw a new line: * myShape.graphics.moveTo(oldX,oldY).lineTo(newX,newY); * // then draw it into the existing cache: * myShape.updateCache("source-over"); * // store the new line, so it isn't redrawn next time: * myShape.store(); * * // then, when the window resizes, we can re-render at a different scale: * // first, unstore all our lines: * myShape.unstore(); * // then cache using the new scale: * myShape.cache(0,0,500,500,newScale); * // finally, store the existing commands again: * myShape.store(); * * @method store * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.store = function() { this._updateInstructions(true); this._storeIndex = this._instructions.length; return this; }; /** * Unstores any graphics commands that were previously stored using {{#crossLink "Graphics/store"}}{{/crossLink}} * so that they will be executed in subsequent draw calls. * * @method unstore * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/ p.unstore = function() { this._storeIndex = 0; return this; }; /** * Returns a clone of this Graphics instance. Note that the individual command objects are not cloned. * @method clone * @return {Graphics} A clone of the current Graphics instance. **/ p.clone = function() { var o = new Graphics(); o.command = this.command; o._stroke = this._stroke; o._strokeStyle = this._strokeStyle; o._strokeDash = this._strokeDash; o._strokeIgnoreScale = this._strokeIgnoreScale; o._fill = this._fill; o._instructions = this._instructions.slice(); o._commitIndex = this._commitIndex; o._activeInstructions = this._activeInstructions.slice(); o._dirty = this._dirty; o._storeIndex = this._storeIndex; return o; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Graphics]"; }; // tiny API: /** * Shortcut to moveTo. * @method mt * @param {Number} x The x coordinate the drawing point should move to. * @param {Number} y The y coordinate the drawing point should move to. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls). * @chainable * @protected **/ p.mt = p.moveTo; /** * Shortcut to lineTo. * @method lt * @param {Number} x The x coordinate the drawing point should draw to. * @param {Number} y The y coordinate the drawing point should draw to. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.lt = p.lineTo; /** * Shortcut to arcTo. * @method at * @param {Number} x1 * @param {Number} y1 * @param {Number} x2 * @param {Number} y2 * @param {Number} radius * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.at = p.arcTo; /** * Shortcut to bezierCurveTo. * @method bt * @param {Number} cp1x * @param {Number} cp1y * @param {Number} cp2x * @param {Number} cp2y * @param {Number} x * @param {Number} y * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.bt = p.bezierCurveTo; /** * Shortcut to quadraticCurveTo / curveTo. * @method qt * @param {Number} cpx * @param {Number} cpy * @param {Number} x * @param {Number} y * @protected * @chainable **/ p.qt = p.quadraticCurveTo; /** * Shortcut to arc. * @method a * @param {Number} x * @param {Number} y * @param {Number} radius * @param {Number} startAngle Measured in radians. * @param {Number} endAngle Measured in radians. * @param {Boolean} anticlockwise * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @protected * @chainable **/ p.a = p.arc; /** * Shortcut to rect. * @method r * @param {Number} x * @param {Number} y * @param {Number} w Width of the rectangle * @param {Number} h Height of the rectangle * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.r = p.rect; /** * Shortcut to closePath. * @method cp * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.cp = p.closePath; /** * Shortcut to clear. * @method c * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.c = p.clear; /** * Shortcut to beginFill. * @method f * @param {String} color A CSS compatible color value (ex. "red", "#FF0000", or "rgba(255,0,0,0.5)"). Setting to * null will result in no fill. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.f = p.beginFill; /** * Shortcut to beginLinearGradientFill. * @method lf * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define a gradient * drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, 0.9] would draw * the first color to 10% then interpolating to the second color at 90%. * @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. * @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.lf = p.beginLinearGradientFill; /** * Shortcut to beginRadialGradientFill. * @method rf * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define * a gradient drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, * 0.9] would draw the first color to 10% then interpolating to the second color at 90%. * @param {Number} x0 Center position of the inner circle that defines the gradient. * @param {Number} y0 Center position of the inner circle that defines the gradient. * @param {Number} r0 Radius of the inner circle that defines the gradient. * @param {Number} x1 Center position of the outer circle that defines the gradient. * @param {Number} y1 Center position of the outer circle that defines the gradient. * @param {Number} r1 Radius of the outer circle that defines the gradient. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.rf = p.beginRadialGradientFill; /** * Shortcut to beginBitmapFill. * @method bf * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use * as the pattern. * @param {String} repetition Optional. Indicates whether to repeat the image in the fill area. One of "repeat", * "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". Note that Firefox does not support "repeat-x" or * "repeat-y" (latest tests were in FF 20.0), and will default to "repeat". * @param {Matrix2D} matrix Optional. Specifies a transformation matrix for the bitmap fill. This transformation * will be applied relative to the parent transform. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.bf = p.beginBitmapFill; /** * Shortcut to endFill. * @method ef * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.ef = p.endFill; /** * Shortcut to setStrokeStyle. * @method ss * @param {Number} thickness The width of the stroke. * @param {String | Number} [caps=0] Indicates the type of caps to use at the end of lines. One of butt, * round, or square. Defaults to "butt". Also accepts the values 0 (butt), 1 (round), and 2 (square) for use with * the tiny API. * @param {String | Number} [joints=0] Specifies the type of joints that should be used where two lines meet. * One of bevel, round, or miter. Defaults to "miter". Also accepts the values 0 (miter), 1 (round), and 2 (bevel) * for use with the tiny API. * @param {Number} [miterLimit=10] If joints is set to "miter", then you can specify a miter limit ratio which * controls at what point a mitered joint will be clipped. * @param {Boolean} [ignoreScale=false] If true, the stroke will be drawn at the specified thickness regardless * of active transformations. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.ss = p.setStrokeStyle; /** * Shortcut to setStrokeDash. * @method sd * @param {Array} [segments] An array specifying the dash pattern, alternating between line and gap. * For example, [20,10] would create a pattern of 20 pixel lines with 10 pixel gaps between them. * Passing null or an empty array will clear any existing dash. * @param {Number} [offset=0] The offset of the dash pattern. For example, you could increment this value to create a "marching ants" effect. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.sd = p.setStrokeDash; /** * Shortcut to beginStroke. * @method s * @param {String} color A CSS compatible color value (ex. "#FF0000", "red", or "rgba(255,0,0,0.5)"). Setting to * null will result in no stroke. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.s = p.beginStroke; /** * Shortcut to beginLinearGradientStroke. * @method ls * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define * a gradient drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, * 0.9] would draw the first color to 10% then interpolating to the second color at 90%. * @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. * @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. * @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.ls = p.beginLinearGradientStroke; /** * Shortcut to beginRadialGradientStroke. * @method rs * @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define * a gradient drawing from red to blue. * @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, * 0.9] would draw the first color to 10% then interpolating to the second color at 90%, then draw the second color * to 100%. * @param {Number} x0 Center position of the inner circle that defines the gradient. * @param {Number} y0 Center position of the inner circle that defines the gradient. * @param {Number} r0 Radius of the inner circle that defines the gradient. * @param {Number} x1 Center position of the outer circle that defines the gradient. * @param {Number} y1 Center position of the outer circle that defines the gradient. * @param {Number} r1 Radius of the outer circle that defines the gradient. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.rs = p.beginRadialGradientStroke; /** * Shortcut to beginBitmapStroke. * @method bs * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use * as the pattern. * @param {String} [repetition=repeat] Optional. Indicates whether to repeat the image in the fill area. One of * "repeat", "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.bs = p.beginBitmapStroke; /** * Shortcut to endStroke. * @method es * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.es = p.endStroke; /** * Shortcut to drawRect. * @method dr * @param {Number} x * @param {Number} y * @param {Number} w Width of the rectangle * @param {Number} h Height of the rectangle * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.dr = p.drawRect; /** * Shortcut to drawRoundRect. * @method rr * @param {Number} x * @param {Number} y * @param {Number} w * @param {Number} h * @param {Number} radius Corner radius. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.rr = p.drawRoundRect; /** * Shortcut to drawRoundRectComplex. * @method rc * @param {Number} x The horizontal coordinate to draw the round rect. * @param {Number} y The vertical coordinate to draw the round rect. * @param {Number} w The width of the round rect. * @param {Number} h The height of the round rect. * @param {Number} radiusTL Top left corner radius. * @param {Number} radiusTR Top right corner radius. * @param {Number} radiusBR Bottom right corner radius. * @param {Number} radiusBL Bottom left corner radius. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.rc = p.drawRoundRectComplex; /** * Shortcut to drawCircle. * @method dc * @param {Number} x x coordinate center point of circle. * @param {Number} y y coordinate center point of circle. * @param {Number} radius Radius of circle. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.dc = p.drawCircle; /** * Shortcut to drawEllipse. * @method de * @param {Number} x The left coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} * which draws from center. * @param {Number} y The top coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} * which draws from the center. * @param {Number} w The height (horizontal diameter) of the ellipse. The horizontal radius will be half of this * number. * @param {Number} h The width (vertical diameter) of the ellipse. The vertical radius will be half of this number. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.de = p.drawEllipse; /** * Shortcut to drawPolyStar. * @method dp * @param {Number} x Position of the center of the shape. * @param {Number} y Position of the center of the shape. * @param {Number} radius The outer radius of the shape. * @param {Number} sides The number of points on the star or sides on the polygon. * @param {Number} pointSize The depth or "pointy-ness" of the star points. A pointSize of 0 will draw a regular * polygon (no points), a pointSize of 1 will draw nothing because the points are infinitely pointy. * @param {Number} angle The angle of the first point / corner. For example a value of 0 will draw the first point * directly to the right of the center. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.dp = p.drawPolyStar; /** * Shortcut to decodePath. * @method p * @param {String} str The path string to decode. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable * @protected **/ p.p = p.decodePath; // private methods: /** * @method _updateInstructions * @param commit * @protected **/ p._updateInstructions = function(commit) { var instr = this._instructions, active = this._activeInstructions, commitIndex = this._commitIndex; if (this._dirty && active.length) { instr.length = commitIndex; // remove old, uncommitted commands instr.push(Graphics.beginCmd); var l = active.length, ll = instr.length; instr.length = ll+l; for (var i=0; i= 2) { var o = this.style = Graphics._ctx.createPattern(image, repetition || ""); o.props = {image: image, repetition: repetition, type: "bitmap"}; } return this; }; p.path = false; /** * Graphics command object. See {{#crossLink "Graphics/beginStroke"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class Stroke * @constructor * @param {Object} style A valid Context2D fillStyle. * @param {Boolean} ignoreScale **/ /** * A valid Context2D strokeStyle. * @property style * @type Object */ /** * @property ignoreScale * @type Boolean */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ p = (G.Stroke = function(style, ignoreScale) { this.style = style; this.ignoreScale = ignoreScale; }).prototype; p.exec = function(ctx) { if (!this.style) { return; } ctx.strokeStyle = this.style; if (this.ignoreScale) { ctx.save(); ctx.setTransform(1,0,0,1,0,0); } ctx.stroke(); if (this.ignoreScale) { ctx.restore(); } }; /** * Creates a linear gradient style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}. * See {{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}} for more information. * @method linearGradient * @param {Array} colors * @param {Array} ratios * @param {Number} x0 * @param {Number} y0 * @param {Number} x1 * @param {Number} y1 * @return {Fill} Returns this Stroke object for chaining or assignment. */ p.linearGradient = G.Fill.prototype.linearGradient; /** * Creates a radial gradient style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}. * See {{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} for more information. * @method radialGradient * @param {Array} colors * @param {Array} ratios * @param {Number} x0 * @param {Number} y0 * @param {Number} r0 * @param {Number} x1 * @param {Number} y1 * @param {Number} r1 * @return {Fill} Returns this Stroke object for chaining or assignment. */ p.radialGradient = G.Fill.prototype.radialGradient; /** * Creates a bitmap fill style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}. * See {{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}} for more information. * @method bitmap * @param {HTMLImageElement} image * @param {String} [repetition] One of: repeat, repeat-x, repeat-y, or no-repeat. * @return {Fill} Returns this Stroke object for chaining or assignment. */ p.bitmap = G.Fill.prototype.bitmap; p.path = false; /** * Graphics command object. See {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class StrokeStyle * @constructor * @param {Number} width * @param {String} [caps=butt] * @param {String} [joints=miter] * @param {Number} [miterLimit=10] * @param {Boolean} [ignoreScale=false] **/ /** * @property width * @type Number */ /** * One of: butt, round, square * @property caps * @type String */ /** * One of: round, bevel, miter * @property joints * @type String */ /** * @property miterLimit * @type Number */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ p = (G.StrokeStyle = function(width, caps, joints, miterLimit, ignoreScale) { this.width = width; this.caps = caps; this.joints = joints; this.miterLimit = miterLimit; this.ignoreScale = ignoreScale; }).prototype; p.exec = function(ctx) { ctx.lineWidth = (this.width == null ? "1" : this.width); ctx.lineCap = (this.caps == null ? "butt" : (isNaN(this.caps) ? this.caps : Graphics.STROKE_CAPS_MAP[this.caps])); ctx.lineJoin = (this.joints == null ? "miter" : (isNaN(this.joints) ? this.joints : Graphics.STROKE_JOINTS_MAP[this.joints])); ctx.miterLimit = (this.miterLimit == null ? "10" : this.miterLimit); ctx.ignoreScale = (this.ignoreScale == null ? false : this.ignoreScale); }; p.path = false; /** * Graphics command object. See {{#crossLink "Graphics/setStrokeDash"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class StrokeDash * @constructor * @param {Array} [segments] * @param {Number} [offset=0] **/ /** * @property segments * @type Array */ /** * @property offset * @type Number */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ (G.StrokeDash = function(segments, offset) { this.segments = segments; this.offset = offset||0; }).prototype.exec = function(ctx) { if (ctx.setLineDash) { // feature detection. ctx.setLineDash(this.segments|| G.StrokeDash.EMPTY_SEGMENTS); // instead of [] to reduce churn. ctx.lineDashOffset = this.offset||0; } }; /** * The default value for segments (ie. no dash). * @property EMPTY_SEGMENTS * @static * @final * @readonly * @protected * @type {Array} **/ G.StrokeDash.EMPTY_SEGMENTS = []; /** * Graphics command object. See {{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class RoundRect * @constructor * @param {Number} x * @param {Number} y * @param {Number} w * @param {Number} h * @param {Number} radiusTL * @param {Number} radiusTR * @param {Number} radiusBR * @param {Number} radiusBL **/ /** * @property x * @type Number */ /** * @property y * @type Number */ /** * @property w * @type Number */ /** * @property h * @type Number */ /** * @property radiusTL * @type Number */ /** * @property radiusTR * @type Number */ /** * @property radiusBR * @type Number */ /** * @property radiusBL * @type Number */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ (G.RoundRect = function(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL) { this.x = x; this.y = y; this.w = w; this.h = h; this.radiusTL = radiusTL; this.radiusTR = radiusTR; this.radiusBR = radiusBR; this.radiusBL = radiusBL; }).prototype.exec = function(ctx) { var max = (w max) { rTL = max; } if (rTR < 0) { rTR *= (mTR=-1); } if (rTR > max) { rTR = max; } if (rBR < 0) { rBR *= (mBR=-1); } if (rBR > max) { rBR = max; } if (rBL < 0) { rBL *= (mBL=-1); } if (rBL > max) { rBL = max; } ctx.moveTo(x+w-rTR, y); ctx.arcTo(x+w+rTR*mTR, y-rTR*mTR, x+w, y+rTR, rTR); ctx.lineTo(x+w, y+h-rBR); ctx.arcTo(x+w+rBR*mBR, y+h+rBR*mBR, x+w-rBR, y+h, rBR); ctx.lineTo(x+rBL, y+h); ctx.arcTo(x-rBL*mBL, y+h+rBL*mBL, x, y+h-rBL, rBL); ctx.lineTo(x, y+rTL); ctx.arcTo(x-rTL*mTL, y-rTL*mTL, x+rTL, y, rTL); ctx.closePath(); }; /** * Graphics command object. See {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class Circle * @constructor * @param {Number} x * @param {Number} y * @param {Number} radius **/ /** * @property x * @type Number */ /** * @property y * @type Number */ /** * @property radius * @type Number */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ (G.Circle = function(x, y, radius) { this.x = x; this.y = y; this.radius = radius; }).prototype.exec = function(ctx) { ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); }; /** * Graphics command object. See {{#crossLink "Graphics/drawEllipse"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class Ellipse * @constructor * @param {Number} x * @param {Number} y * @param {Number} w * @param {Number} h **/ /** * @property x * @type Number */ /** * @property y * @type Number */ /** * @property w * @type Number */ /** * @property h * @type Number */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ (G.Ellipse = function(x, y, w, h) { this.x = x; this.y = y; this.w = w; this.h = h; }).prototype.exec = function(ctx) { var x = this.x, y = this.y; var w = this.w, h = this.h; var k = 0.5522848; var ox = (w / 2) * k; var oy = (h / 2) * k; var xe = x + w; var ye = y + h; var xm = x + w / 2; var ym = y + h / 2; ctx.moveTo(x, ym); ctx.bezierCurveTo(x, ym-oy, xm-ox, y, xm, y); ctx.bezierCurveTo(xm+ox, y, xe, ym-oy, xe, ym); ctx.bezierCurveTo(xe, ym+oy, xm+ox, ye, xm, ye); ctx.bezierCurveTo(xm-ox, ye, x, ym+oy, x, ym); }; /** * Graphics command object. See {{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. * @class PolyStar * @constructor * @param {Number} x * @param {Number} y * @param {Number} radius * @param {Number} sides * @param {Number} pointSize * @param {Number} angle **/ /** * @property x * @type Number */ /** * @property y * @type Number */ /** * @property radius * @type Number */ /** * @property sides * @type Number */ /** * @property pointSize * @type Number */ /** * @property angle * @type Number */ /** * Execute the Graphics command in the provided Canvas context. * @method exec * @param {CanvasRenderingContext2D} ctx The canvas rendering context */ (G.PolyStar = function(x, y, radius, sides, pointSize, angle) { this.x = x; this.y = y; this.radius = radius; this.sides = sides; this.pointSize = pointSize; this.angle = angle; }).prototype.exec = function(ctx) { var x = this.x, y = this.y; var radius = this.radius; var angle = (this.angle||0)/180*Math.PI; var sides = this.sides; var ps = 1-(this.pointSize||0); var a = Math.PI/sides; ctx.moveTo(x+Math.cos(angle)*radius, y+Math.sin(angle)*radius); for (var i=0; iNote: In EaselJS 0.7.0, the mouseEnabled property will not work properly with nested Containers. Please * check out the latest NEXT version in GitHub for an updated version with this issue resolved. The fix will be * provided in the next release of EaselJS. * @property mouseEnabled * @type {Boolean} * @default true **/ this.mouseEnabled = true; /** * If false, the tick will not run on this display object (or its children). This can provide some performance benefits. * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates * on some display objects (ex. Sprite & MovieClip frame advancing, and DOMElement display properties). * @property tickEnabled * @type Boolean * @default true **/ this.tickEnabled = true; /** * An optional name for this display object. Included in {{#crossLink "DisplayObject/toString"}}{{/crossLink}} . Useful for * debugging. * @property name * @type {String} * @default null **/ this.name = null; /** * A reference to the {{#crossLink "Container"}}{{/crossLink}} or {{#crossLink "Stage"}}{{/crossLink}} object that * contains this display object, or null if it has not been added * to one. * @property parent * @final * @type {Container} * @default null * @readonly **/ this.parent = null; /** * The left offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate * around its center, you would set regX and {{#crossLink "DisplayObject/regY:property"}}{{/crossLink}} to 50. * Cached object's registration points should be set based on pre-cache conditions, not cached size. * @property regX * @type {Number} * @default 0 **/ this.regX = 0; /** * The y offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around * its center, you would set {{#crossLink "DisplayObject/regX:property"}}{{/crossLink}} and regY to 50. * Cached object's registration points should be set based on pre-cache conditions, not cached size. * @property regY * @type {Number} * @default 0 **/ this.regY = 0; /** * The rotation in degrees for this display object. * @property rotation * @type {Number} * @default 0 **/ this.rotation = 0; /** * The factor to stretch this display object horizontally. For example, setting scaleX to 2 will stretch the display * object to twice its nominal width. To horizontally flip an object, set the scale to a negative number. * @property scaleX * @type {Number} * @default 1 **/ this.scaleX = 1; /** * The factor to stretch this display object vertically. For example, setting scaleY to 0.5 will stretch the display * object to half its nominal height. To vertically flip an object, set the scale to a negative number. * @property scaleY * @type {Number} * @default 1 **/ this.scaleY = 1; /** * The factor to skew this display object horizontally. * @property skewX * @type {Number} * @default 0 **/ this.skewX = 0; /** * The factor to skew this display object vertically. * @property skewY * @type {Number} * @default 0 **/ this.skewY = 0; /** * A shadow object that defines the shadow to render on this display object. Set to `null` to remove a shadow. If * null, this property is inherited from the parent container. * @property shadow * @type {Shadow} * @default null **/ this.shadow = null; /** * Indicates whether this display object should be rendered to the canvas and included when running the Stage * {{#crossLink "Stage/getObjectsUnderPoint"}}{{/crossLink}} method. * @property visible * @type {Boolean} * @default true **/ this.visible = true; /** * The x (horizontal) position of the display object, relative to its parent. * @property x * @type {Number} * @default 0 **/ this.x = 0; /** The y (vertical) position of the display object, relative to its parent. * @property y * @type {Number} * @default 0 **/ this.y = 0; /** * If set, defines the transformation for this display object, overriding all other transformation properties * (x, y, rotation, scale, skew). * @property transformMatrix * @type {Matrix2D} * @default null **/ this.transformMatrix = null; /** * The composite operation indicates how the pixels of this display object will be composited with the elements * behind it. If `null`, this property is inherited from the parent container. For more information, read the * * whatwg spec on compositing. For a list of supported compositeOperation value, visit * the W3C draft on Compositing and Blending. * @property compositeOperation * @type {String} * @default null **/ this.compositeOperation = null; /** * Indicates whether the display object should be drawn to a whole pixel when * {{#crossLink "Stage/snapToPixelEnabled"}}{{/crossLink}} is true. To enable/disable snapping on whole * categories of display objects, set this value on the prototype (Ex. Text.prototype.snapToPixel = true). * @property snapToPixel * @type {Boolean} * @default true **/ this.snapToPixel = true; /** * An array of Filter objects to apply to this display object. Filters are only applied / updated when {{#crossLink "cache"}}{{/crossLink}} * or {{#crossLink "updateCache"}}{{/crossLink}} is called on the display object, and only apply to the area that is * cached. * @property filters * @type {Array} * @default null **/ this.filters = null; /** * A Shape instance that defines a vector mask (clipping path) for this display object. The shape's transformation * will be applied relative to the display object's parent coordinates (as if it were a child of the parent). * @property mask * @type {Shape} * @default null */ this.mask = null; /** * A display object that will be tested when checking mouse interactions or testing {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}. * The hit area will have its transformation applied relative to this display object's coordinate space (as though * the hit test object were a child of this display object and relative to its regX/Y). The hitArea will be tested * using only its own `alpha` value regardless of the alpha value on the target display object, or the target's * ancestors (parents). * * If set on a {{#crossLink "Container"}}{{/crossLink}}, children of the Container will not receive mouse events. * This is similar to setting {{#crossLink "mouseChildren"}}{{/crossLink}} to false. * * Note that hitArea is NOT currently used by the `hitTest()` method, nor is it supported for {{#crossLink "Stage"}}{{/crossLink}}. * @property hitArea * @type {DisplayObject} * @default null */ this.hitArea = null; /** * A CSS cursor (ex. "pointer", "help", "text", etc) that will be displayed when the user hovers over this display * object. You must enable mouseover events using the {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}} method to * use this property. Setting a non-null cursor on a Container will override the cursor set on its descendants. * @property cursor * @type {String} * @default null */ this.cursor = null; // private properties: /** * Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} * @property _cacheScale * @protected * @type {Number} * @default 1 * @deprecated **/ /** * Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} * @property _cacheDataURLID * @protected * @type {Number} * @default 0 * @deprecated */ /** * Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} * @property _cacheDataURL * @protected * @type {String} * @default null * @deprecated */ /** * @property _props * @protected * @type {DisplayObject} * @default null **/ this._props = new createjs.DisplayProps(); /** * @property _rectangle * @protected * @type {Rectangle} * @default null **/ this._rectangle = new createjs.Rectangle(); /** * @property _bounds * @protected * @type {Rectangle} * @default null **/ this._bounds = null; /** * Where StageGL should look for required display properties, matters only for leaf display objects. Containers * or cached objects won't use this property, it's for native display of terminal elements. * @property _webGLRenderStyle * @protected * @type {number} * @default 0 */ this._webGLRenderStyle = DisplayObject._StageGL_NONE; } var p = createjs.extend(DisplayObject, createjs.EventDispatcher); // static properties: /** * Listing of mouse event names. Used in _hasMouseEventListener. * @property _MOUSE_EVENTS * @protected * @static * @type {Array} **/ DisplayObject._MOUSE_EVENTS = ["click","dblclick","mousedown","mouseout","mouseover","pressmove","pressup","rollout","rollover"]; /** * Suppresses errors generated when using features like hitTest, mouse events, and {{#crossLink "getObjectsUnderPoint"}}{{/crossLink}} * with cross domain content. * @property suppressCrossDomainErrors * @static * @type {Boolean} * @default false **/ DisplayObject.suppressCrossDomainErrors = false; /** * @property _snapToPixelEnabled * @protected * @static * @type {Boolean} * @default false **/ DisplayObject._snapToPixelEnabled = false; // stage.snapToPixelEnabled is temporarily copied here during a draw to provide global access. /** * Enum like property for determining StageGL render lookup, i.e. where to expect properties. * @property _StageGL_NONE * @protected * @static * @type {number} */ DisplayObject._StageGL_NONE = 0; /** * Enum like property for determining StageGL render lookup, i.e. where to expect properties. * @property _StageGL_SPRITE * @protected * @static * @type {number} */ DisplayObject._StageGL_SPRITE = 1; /** * Enum like property for determining StageGL render lookup, i.e. where to expect properties. * @property _StageGL_BITMAP * @protected * @static * @type {number} */ DisplayObject._StageGL_BITMAP = 2; /** * @property _hitTestCanvas * @type {HTMLCanvasElement | Object} * @static * @protected **/ /** * @property _hitTestContext * @type {CanvasRenderingContext2D} * @static * @protected **/ var canvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"); // prevent errors on load in browsers without canvas. if (canvas.getContext) { DisplayObject._hitTestCanvas = canvas; DisplayObject._hitTestContext = canvas.getContext("2d"); canvas.width = canvas.height = 1; } // events: /** * Dispatched when the user presses their left mouse button over the display object. See the * {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event mousedown * @since 0.6.0 */ /** * Dispatched when the user presses their left mouse button and then releases it while over the display object. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event click * @since 0.6.0 */ /** * Dispatched when the user double clicks their left mouse button over this display object. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event dblclick * @since 0.6.0 */ /** * Dispatched when the user's mouse enters this display object. This event must be enabled using * {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. See also {{#crossLink "DisplayObject/rollover:event"}}{{/crossLink}}. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event mouseover * @since 0.6.0 */ /** * Dispatched when the user's mouse leaves this display object. This event must be enabled using * {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. See also {{#crossLink "DisplayObject/rollout:event"}}{{/crossLink}}. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event mouseout * @since 0.6.0 */ /** * This event is similar to {{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}}, with the following * differences: it does not bubble, and it considers {{#crossLink "Container"}}{{/crossLink}} instances as an * aggregate of their content. * * For example, myContainer contains two overlapping children: shapeA and shapeB. The user moves their mouse over * shapeA and then directly on to shapeB. With a listener for {{#crossLink "mouseover:event"}}{{/crossLink}} on * myContainer, two events would be received, each targeting a child element:
    *
  1. when the mouse enters shapeA (target=shapeA)
  2. *
  3. when the mouse enters shapeB (target=shapeB)
  4. *
* However, with a listener for "rollover" instead, only a single event is received when the mouse first enters * the aggregate myContainer content (target=myContainer). * * This event must be enabled using {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event rollover * @since 0.7.0 */ /** * This event is similar to {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}}, with the following * differences: it does not bubble, and it considers {{#crossLink "Container"}}{{/crossLink}} instances as an * aggregate of their content. * * For example, myContainer contains two overlapping children: shapeA and shapeB. The user moves their mouse over * shapeA, then directly on to shapeB, then off both. With a listener for {{#crossLink "mouseout:event"}}{{/crossLink}} * on myContainer, two events would be received, each targeting a child element:
    *
  1. when the mouse leaves shapeA (target=shapeA)
  2. *
  3. when the mouse leaves shapeB (target=shapeB)
  4. *
* However, with a listener for "rollout" instead, only a single event is received when the mouse leaves * the aggregate myContainer content (target=myContainer). * * This event must be enabled using {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event rollout * @since 0.7.0 */ /** * After a {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} occurs on a display object, a pressmove * event will be generated on that object whenever the mouse moves until the mouse press is released. This can be * useful for dragging and similar operations. * * **Please note** that if the initial mouse target from a `mousedown` event is removed from the stage after being pressed * (e.g. during a `pressmove` event), a `pressmove` event is still generated. However since it is no longer in the * display list, the event can not bubble. This means that previous ancestors (parent containers) will not receive * the event, and therefore can not re-dispatch it. If you intend to listen for `{{#crossLink "DisplayObject/pressup:event"}}{{/crossLink}}` * or `pressmove` on a dynamic object (such as a {{#crossLink "MovieClip"}}{{/crossLink}} or {{#crossLink "Container"}}{{/crossLink}}), * then ensure you set {{#crossLink "Container/mouseChildren:property"}}{{/crossLink}} to `false`. * @event pressmove * @since 0.7.0 */ /** * After a {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} occurs on a display object, a pressup event * will be generated on that object when that mouse press is released. This can be useful for dragging and similar * operations. * * **Please note** that if the initial mouse target from a `mousedown` event is removed from the stage after being pressed * (e.g. during a `pressmove` event), a `pressup` event is still generated. However since it is no longer in the * display list, the event can not bubble. This means that previous ancestors (parent containers) will not receive * the event, and therefore can not re-dispatch it. If you intend to listen for `{{#crossLink "DisplayObject/pressmove:event"}}{{/crossLink}}` * or `pressup` on a dynamic object (such as a {{#crossLink "MovieClip"}}{{/crossLink}} or {{#crossLink "Container"}}{{/crossLink}}), * then ensure you set {{#crossLink "Container/mouseChildren:property"}}{{/crossLink}} to `false`. * @event pressup * @since 0.7.0 */ /** * Dispatched when the display object is added to a parent container. * @event added */ /** * Dispatched when the display object is removed from its parent container. * @event removed */ /** * Dispatched on each display object on a stage whenever the stage updates. This occurs immediately before the * rendering (draw) pass. When {{#crossLink "Stage/update"}}{{/crossLink}} is called, first all display objects on * the stage dispatch the tick event, then all of the display objects are drawn to stage. Children will have their * {{#crossLink "tick:event"}}{{/crossLink}} event dispatched in order of their depth prior to the event being * dispatched on their parent. * @event tick * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Array} params An array containing any arguments that were passed to the Stage.update() method. For * example if you called stage.update("hello"), then the params would be ["hello"]. * @since 0.6.0 */ // getter / setters: /** * Use the {{#crossLink "DisplayObject/stage:property"}}{{/crossLink}} property instead. * @method _getStage * @protected * @return {Stage} **/ p._getStage = function() { // uses dynamic access to avoid circular dependencies; var o = this, _Stage = createjs["Stage"]; while (o.parent) { o = o.parent; } if (o instanceof _Stage) { return o; } return null; }; // DisplayObject.getStage is @deprecated. Remove for 1.1+ p.getStage = createjs.deprecate(p._getStage, "DisplayObject.getStage"); /** * Returns the Stage instance that this display object will be rendered on, or null if it has not been added to one. * @property stage * @type {Stage} * @readonly **/ /** * Returns an ID number that uniquely identifies the current cache for this display object. This can be used to * determine if the cache has changed since a previous check. * Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} * @property cacheID * @deprecated * @type {Number} * @default 0 */ /** * Set both the {{#crossLink "DisplayObject/scaleX:property"}}{{/crossLink}} and the {{#crossLink "DisplayObject/scaleY"}}{{/crossLink}} * property to the same value. Note that when you get the value, if the `scaleX` and `scaleY` are different values, * it will return only the `scaleX`. * @property scale * @type {Number} * @default 1 */ try { Object.defineProperties(p, { stage: { get: p._getStage }, cacheID: { get: function(){ return this.bitmapCache && this.bitmapCache.cacheID }, set: function(a){ this.bitmapCache && (this.bitmapCache.cacheID = a) } }, scale: { get: function() { return this.scaleX; }, set: function(scale) { this.scaleX = this.scaleY = scale; }, } }); } catch (e) {} // public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For example, * used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). * @return {Boolean} **/ p.draw = function(ctx, ignoreCache) { var cache = this.bitmapCache; if(cache && !ignoreCache) { return cache.draw(ctx); } return false; }; /** * Applies this display object's transformation, alpha, globalCompositeOperation, clipping path (mask), and shadow * to the specified context. This is typically called prior to {{#crossLink "DisplayObject/draw"}}{{/crossLink}}. * @method updateContext * @param {CanvasRenderingContext2D} ctx The canvas 2D to update. **/ p.updateContext = function(ctx) { var o=this, mask=o.mask, mtx= o._props.matrix; if (mask && mask.graphics && !mask.graphics.isEmpty()) { mask.getMatrix(mtx); ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); mask.graphics.drawAsPath(ctx); ctx.clip(); mtx.invert(); ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); } this.getMatrix(mtx); var tx = mtx.tx, ty = mtx.ty; if (DisplayObject._snapToPixelEnabled && o.snapToPixel) { tx = tx + (tx < 0 ? -0.5 : 0.5) | 0; ty = ty + (ty < 0 ? -0.5 : 0.5) | 0; } ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, tx, ty); ctx.globalAlpha *= o.alpha; if (o.compositeOperation) { ctx.globalCompositeOperation = o.compositeOperation; } if (o.shadow) { this._applyShadow(ctx, o.shadow); } }; /** * Draws the display object into a new element, which is then used for subsequent draws. Intended for complex content * that does not change frequently (ex. a Container with many children that do not move, or a complex vector Shape), * this can provide for much faster rendering because the content does not need to be re-rendered each tick. The * cached display object can be moved, rotated, faded, etc freely, however if its content changes, you must manually * update the cache by calling updateCache() again. You must specify the cached area via the x, y, w, * and h parameters. This defines the rectangle that will be rendered and cached using this display object's coordinates. * *

Example

* For example if you defined a Shape that drew a circle at 0, 0 with a radius of 25: * * var shape = new createjs.Shape(); * shape.graphics.beginFill("#ff0000").drawCircle(0, 0, 25); * shape.cache(-25, -25, 50, 50); * * Note that filters need to be defined before the cache is applied or you will have to call updateCache after * application. Check out the {{#crossLink "Filter"}}{{/crossLink}} class for more information. Some filters * (ex. BlurFilter) may not work as expected in conjunction with the scale param. * * Usually, the resulting cacheCanvas will have the dimensions width * scale, height * scale, however some filters (ex. BlurFilter) * will add padding to the canvas dimensions. * * In previous versions caching was handled on DisplayObject but has since been moved to {{#crossLink "BitmapCache"}}{{/crossLink}}. * This allows for easier interaction and alternate cache methods like WebGL with {{#crossLink "StageGL"}}{{/crossLink}}. * For more information on the options object, see the BitmapCache {{#crossLink "BitmapCache/define"}}{{/crossLink}}. * * @method cache * @param {Number} x The x coordinate origin for the cache region. * @param {Number} y The y coordinate origin for the cache region. * @param {Number} width The width of the cache region. * @param {Number} height The height of the cache region. * @param {Number} [scale=1] The scale at which the cache will be created. For example, if you cache a vector shape using * myShape.cache(0,0,100,100,2) then the resulting cacheCanvas will be 200x200 px. This lets you scale and rotate * cached elements with greater fidelity. Default is 1. * @param {Object} [options=undefined] Specify additional parameters for the cache logic **/ p.cache = function(x, y, width, height, scale, options) { if(!this.bitmapCache){ this.bitmapCache = new createjs.BitmapCache(); } this.bitmapCache.define(this, x, y, width, height, scale, options); }; /** * Redraws the display object to its cache. Calling updateCache without an active cache will throw an error. * If compositeOperation is null the current cache will be cleared prior to drawing. Otherwise the display object * will be drawn over the existing cache using the specified compositeOperation. * *

Example

* Clear the current graphics of a cached shape, draw some new instructions, and then update the cache. The new line * will be drawn on top of the old one. * * // Not shown: Creating the shape, and caching it. * shapeInstance.clear(); * shapeInstance.setStrokeStyle(3).beginStroke("#ff0000").moveTo(100, 100).lineTo(200,200); * shapeInstance.updateCache(); * * In previous versions caching was handled on DisplayObject but has since been moved to {{#crossLink "BitmapCache"}}{{/crossLink}}. * This allows for easier interaction and alternate cache methods like WebGL and {{#crossLink "StageGL"}}{{/crossLink}}. * * @method updateCache * @param {String} compositeOperation The compositeOperation to use, or null to clear the cache and redraw it. * * whatwg spec on compositing. **/ p.updateCache = function(compositeOperation) { if(!this.bitmapCache) { throw "cache() must be called before updateCache()"; } this.bitmapCache.update(compositeOperation); }; /** * Clears the current cache. See {{#crossLink "DisplayObject/cache"}}{{/crossLink}} for more information. * @method uncache **/ p.uncache = function() { if(this.bitmapCache) { this.bitmapCache.release(); this.bitmapCache = undefined; } }; /** * Returns a data URL for the cache, or null if this display object is not cached. * Only generated if the cache has changed, otherwise returns last result. * @method getCacheDataURL * @return {String} The image data url for the cache. **/ p.getCacheDataURL = function() { return this.bitmapCache?this.bitmapCache.getDataURL():null; }; /** * Transforms the specified x and y position from the coordinate space of the display object * to the global (stage) coordinate space. For example, this could be used to position an HTML label * over a specific point on a nested display object. Returns a Point instance with x and y properties * correlating to the transformed coordinates on the stage. * *

Example

* * displayObject.x = 300; * displayObject.y = 200; * stage.addChild(displayObject); * var point = displayObject.localToGlobal(100, 100); * // Results in x=400, y=300 * * @method localToGlobal * @param {Number} x The x position in the source display object to transform. * @param {Number} y The y position in the source display object to transform. * @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned. * @return {Point} A Point instance with x and y properties correlating to the transformed coordinates * on the stage. **/ p.localToGlobal = function(x, y, pt) { return this.getConcatenatedMatrix(this._props.matrix).transformPoint(x,y, pt||new createjs.Point()); }; /** * Transforms the specified x and y position from the global (stage) coordinate space to the * coordinate space of the display object. For example, this could be used to determine * the current mouse position within the display object. Returns a Point instance with x and y properties * correlating to the transformed position in the display object's coordinate space. * *

Example

* * displayObject.x = 300; * displayObject.y = 200; * stage.addChild(displayObject); * var point = displayObject.globalToLocal(100, 100); * // Results in x=-200, y=-100 * * @method globalToLocal * @param {Number} x The x position on the stage to transform. * @param {Number} y The y position on the stage to transform. * @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned. * @return {Point} A Point instance with x and y properties correlating to the transformed position in the * display object's coordinate space. **/ p.globalToLocal = function(x, y, pt) { return this.getConcatenatedMatrix(this._props.matrix).invert().transformPoint(x,y, pt||new createjs.Point()); }; /** * Transforms the specified x and y position from the coordinate space of this display object to the coordinate * space of the target display object. Returns a Point instance with x and y properties correlating to the * transformed position in the target's coordinate space. Effectively the same as using the following code with * {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}. * * var pt = this.localToGlobal(x, y); * pt = target.globalToLocal(pt.x, pt.y); * * @method localToLocal * @param {Number} x The x position in the source display object to transform. * @param {Number} y The y position on the source display object to transform. * @param {DisplayObject} target The target display object to which the coordinates will be transformed. * @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned. * @return {Point} Returns a Point instance with x and y properties correlating to the transformed position * in the target's coordinate space. **/ p.localToLocal = function(x, y, target, pt) { pt = this.localToGlobal(x, y, pt); return target.globalToLocal(pt.x, pt.y, pt); }; /** * Shortcut method to quickly set the transform properties on the display object. All parameters are optional. * Omitted parameters will have the default value set. * *

Example

* * displayObject.setTransform(100, 100, 2, 2); * * @method setTransform * @param {Number} [x=0] The horizontal translation (x position) in pixels * @param {Number} [y=0] The vertical translation (y position) in pixels * @param {Number} [scaleX=1] The horizontal scale, as a percentage of 1 * @param {Number} [scaleY=1] the vertical scale, as a percentage of 1 * @param {Number} [rotation=0] The rotation, in degrees * @param {Number} [skewX=0] The horizontal skew factor * @param {Number} [skewY=0] The vertical skew factor * @param {Number} [regX=0] The horizontal registration point in pixels * @param {Number} [regY=0] The vertical registration point in pixels * @return {DisplayObject} Returns this instance. Useful for chaining commands. * @chainable */ p.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { this.x = x || 0; this.y = y || 0; this.scaleX = scaleX == null ? 1 : scaleX; this.scaleY = scaleY == null ? 1 : scaleY; this.rotation = rotation || 0; this.skewX = skewX || 0; this.skewY = skewY || 0; this.regX = regX || 0; this.regY = regY || 0; return this; }; /** * Returns a matrix based on this object's current transform. * @method getMatrix * @param {Matrix2D} matrix Optional. A Matrix2D object to populate with the calculated values. If null, a new * Matrix object is returned. * @return {Matrix2D} A matrix representing this display object's transform. **/ p.getMatrix = function(matrix) { var o = this, mtx = matrix&&matrix.identity() || new createjs.Matrix2D(); return o.transformMatrix ? mtx.copy(o.transformMatrix) : mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); }; /** * Generates a Matrix2D object representing the combined transform of the display object and all of its * parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). This can * be used to transform positions between coordinate spaces, such as with {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} * and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}. * @method getConcatenatedMatrix * @param {Matrix2D} [matrix] A {{#crossLink "Matrix2D"}}{{/crossLink}} object to populate with the calculated values. * If null, a new Matrix2D object is returned. * @return {Matrix2D} The combined matrix. **/ p.getConcatenatedMatrix = function(matrix) { var o = this, mtx = this.getMatrix(matrix); while (o = o.parent) { mtx.prependMatrix(o.getMatrix(o._props.matrix)); } return mtx; }; /** * Generates a DisplayProps object representing the combined display properties of the object and all of its * parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). * @method getConcatenatedDisplayProps * @param {DisplayProps} [props] A {{#crossLink "DisplayProps"}}{{/crossLink}} object to populate with the calculated values. * If null, a new DisplayProps object is returned. * @return {DisplayProps} The combined display properties. **/ p.getConcatenatedDisplayProps = function(props) { props = props ? props.identity() : new createjs.DisplayProps(); var o = this, mtx = o.getMatrix(props.matrix); do { props.prepend(o.visible, o.alpha, o.shadow, o.compositeOperation); // we do this to avoid problems with the matrix being used for both operations when o._props.matrix is passed in as the props param. // this could be simplified (ie. just done as part of the prepend above) if we switched to using a pool. if (o != this) { mtx.prependMatrix(o.getMatrix(o._props.matrix)); } } while (o = o.parent); return props; }; /** * Tests whether the display object intersects the specified point in local coordinates (ie. draws a pixel with alpha > 0 at * the specified position). This ignores the alpha, shadow, hitArea, mask, and compositeOperation of the display object. * *

Example

* * stage.addEventListener("stagemousedown", handleMouseDown); * function handleMouseDown(event) { * var hit = myShape.hitTest(event.stageX, event.stageY); * } * * Please note that shape-to-shape collision is not currently supported by EaselJS. * @method hitTest * @param {Number} x The x position to check in the display object's local coordinates. * @param {Number} y The y position to check in the display object's local coordinates. * @return {Boolean} A Boolean indicating whether a visible portion of the DisplayObject intersect the specified * local Point. */ p.hitTest = function(x, y) { var ctx = DisplayObject._hitTestContext; ctx.setTransform(1, 0, 0, 1, -x, -y); this.draw(ctx); var hit = this._testHit(ctx); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, 2, 2); return hit; }; /** * Provides a chainable shortcut method for setting a number of properties on the instance. * *

Example

* * var myGraphics = new createjs.Graphics().beginFill("#ff0000").drawCircle(0, 0, 25); * var shape = stage.addChild(new Shape()).set({graphics:myGraphics, x:100, y:100, alpha:0.5}); * * @method set * @param {Object} props A generic object containing properties to copy to the DisplayObject instance. * @return {DisplayObject} Returns the instance the method is called on (useful for chaining calls.) * @chainable */ p.set = function(props) { for (var n in props) { this[n] = props[n]; } return this; }; /** * Returns a rectangle representing this object's bounds in its local coordinate system (ie. with no transformation). * Objects that have been cached will return the bounds of the cache. * * Not all display objects can calculate their own bounds (ex. Shape). For these objects, you can use * {{#crossLink "DisplayObject/setBounds"}}{{/crossLink}} so that they are included when calculating Container * bounds. * * * * * * * * * *
All * All display objects support setting bounds manually using setBounds(). Likewise, display objects that * have been cached using cache() will return the bounds of their cache. Manual and cache bounds will override * the automatic calculations listed below. *
Bitmap * Returns the width and height of the sourceRect (if specified) or image, extending from (x=0,y=0). *
Sprite * Returns the bounds of the current frame. May have non-zero x/y if a frame registration point was specified * in the spritesheet data. See also {{#crossLink "SpriteSheet/getFrameBounds"}}{{/crossLink}} *
Container * Returns the aggregate (combined) bounds of all children that return a non-null value from getBounds(). *
Shape * Does not currently support automatic bounds calculations. Use setBounds() to manually define bounds. *
Text * Returns approximate bounds. Horizontal values (x/width) are quite accurate, but vertical values (y/height) are * not, especially when using textBaseline values other than "top". *
BitmapText * Returns approximate bounds. Values will be more accurate if spritesheet frame registration points are close * to (x=0,y=0). *
* * Bounds can be expensive to calculate for some objects (ex. text, or containers with many children), and * are recalculated each time you call getBounds(). You can prevent recalculation on static objects by setting the * bounds explicitly: * * var bounds = obj.getBounds(); * obj.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); * // getBounds will now use the set values, instead of recalculating * * To reduce memory impact, the returned Rectangle instance may be reused internally; clone the instance or copy its * values if you need to retain it. * * var myBounds = obj.getBounds().clone(); * // OR: * myRect.copy(obj.getBounds()); * * @method getBounds * @return {Rectangle} A Rectangle instance representing the bounds, or null if bounds are not available for this * object. **/ p.getBounds = function() { if (this._bounds) { return this._rectangle.copy(this._bounds); } var cacheCanvas = this.cacheCanvas; if (cacheCanvas) { var scale = this._cacheScale; return this._rectangle.setValues(this._cacheOffsetX, this._cacheOffsetY, cacheCanvas.width/scale, cacheCanvas.height/scale); } return null; }; /** * Returns a rectangle representing this object's bounds in its parent's coordinate system (ie. with transformations applied). * Objects that have been cached will return the transformed bounds of the cache. * * Not all display objects can calculate their own bounds (ex. Shape). For these objects, you can use * {{#crossLink "DisplayObject/setBounds"}}{{/crossLink}} so that they are included when calculating Container * bounds. * * To reduce memory impact, the returned Rectangle instance may be reused internally; clone the instance or copy its * values if you need to retain it. * * Container instances calculate aggregate bounds for all children that return bounds via getBounds. * @method getTransformedBounds * @return {Rectangle} A Rectangle instance representing the bounds, or null if bounds are not available for this object. **/ p.getTransformedBounds = function() { return this._getBounds(); }; /** * Allows you to manually specify the bounds of an object that either cannot calculate their own bounds (ex. Shape & * Text) for future reference, or so the object can be included in Container bounds. Manually set bounds will always * override calculated bounds. * * The bounds should be specified in the object's local (untransformed) coordinates. For example, a Shape instance * with a 25px radius circle centered at 0,0 would have bounds of (-25, -25, 50, 50). * @method setBounds * @param {Number} x The x origin of the bounds. Pass null to remove the manual bounds. * @param {Number} y The y origin of the bounds. * @param {Number} width The width of the bounds. * @param {Number} height The height of the bounds. **/ p.setBounds = function(x, y, width, height) { if (x == null) { this._bounds = x; return; } this._bounds = (this._bounds || new createjs.Rectangle()).setValues(x, y, width, height); }; /** * Returns a clone of this DisplayObject. Some properties that are specific to this instance's current context are * reverted to their defaults (for example .parent). Caches are not maintained across clones, and some elements * are copied by reference (masks, individual filter instances, hit area) * @method clone * @return {DisplayObject} A clone of the current DisplayObject instance. **/ p.clone = function() { return this._cloneProps(new DisplayObject()); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[DisplayObject (name="+ this.name +")]"; }; // private methods: /** * Called before the object gets drawn and is a chance to ensure the display state of the object is correct. * Mostly used by {{#crossLink "MovieClip"}}{{/crossLink}} and {{#crossLink "BitmapText"}}{{/crossLink}} to * correct their internal state and children prior to being drawn. * * Is manually called via draw in a {{#crossLink "Stage"}}{{/crossLink}} but is automatically called when * present in a {{#crossLink "StageGL"}}{{/crossLink}} instance. * * @method _updateState * @default null */ p._updateState = null; // separated so it can be used more easily in subclasses: /** * @method _cloneProps * @param {DisplayObject} o The DisplayObject instance which will have properties from the current DisplayObject * instance copied into. * @return {DisplayObject} o * @protected **/ p._cloneProps = function(o) { o.alpha = this.alpha; o.mouseEnabled = this.mouseEnabled; o.tickEnabled = this.tickEnabled; o.name = this.name; o.regX = this.regX; o.regY = this.regY; o.rotation = this.rotation; o.scaleX = this.scaleX; o.scaleY = this.scaleY; o.shadow = this.shadow; o.skewX = this.skewX; o.skewY = this.skewY; o.visible = this.visible; o.x = this.x; o.y = this.y; o.compositeOperation = this.compositeOperation; o.snapToPixel = this.snapToPixel; o.filters = this.filters==null?null:this.filters.slice(0); o.mask = this.mask; o.hitArea = this.hitArea; o.cursor = this.cursor; o._bounds = this._bounds; return o; }; /** * @method _applyShadow * @protected * @param {CanvasRenderingContext2D} ctx * @param {Shadow} shadow **/ p._applyShadow = function(ctx, shadow) { shadow = shadow || Shadow.identity; ctx.shadowColor = shadow.color; ctx.shadowOffsetX = shadow.offsetX; ctx.shadowOffsetY = shadow.offsetY; ctx.shadowBlur = shadow.blur; }; /** * @method _tick * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * @protected **/ p._tick = function(evtObj) { // because tick can be really performance sensitive, check for listeners before calling dispatchEvent. var ls = this._listeners; if (ls && ls["tick"]) { // reset & reuse the event object to avoid construction / GC costs: evtObj.target = null; evtObj.propagationStopped = evtObj.immediatePropagationStopped = false; this.dispatchEvent(evtObj); } }; /** * @method _testHit * @protected * @param {CanvasRenderingContext2D} ctx * @return {Boolean} **/ p._testHit = function(ctx) { try { var hit = ctx.getImageData(0, 0, 1, 1).data[3] > 1; } catch (e) { if (!DisplayObject.suppressCrossDomainErrors) { throw "An error has occurred. This is most likely due to security restrictions on reading canvas pixel data with local or cross-domain images."; } } return hit; }; /** * @method _getBounds * @param {Matrix2D} matrix * @param {Boolean} ignoreTransform If true, does not apply this object's transform. * @return {Rectangle} * @protected **/ p._getBounds = function(matrix, ignoreTransform){ return this._transformBounds(this.getBounds(), matrix, ignoreTransform); }; /** * @method _transformBounds * @param {Rectangle} bounds * @param {Matrix2D} matrix * @param {Boolean} ignoreTransform * @return {Rectangle} * @protected **/ p._transformBounds = function(bounds, matrix, ignoreTransform) { if (!bounds) { return bounds; } var x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height, mtx = this._props.matrix; mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); if (x || y) { mtx.appendTransform(0,0,1,1,0,0,0,-x,-y); } // TODO: simplify this. if (matrix) { mtx.prependMatrix(matrix); } var x_a = width*mtx.a, x_b = width*mtx.b; var y_c = height*mtx.c, y_d = height*mtx.d; var tx = mtx.tx, ty = mtx.ty; var minX = tx, maxX = tx, minY = ty, maxY = ty; if ((x = x_a + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; } if ((x = x_a + y_c + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; } if ((x = y_c + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; } if ((y = x_b + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; } if ((y = x_b + y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; } if ((y = y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; } return bounds.setValues(minX, minY, maxX-minX, maxY-minY); }; /** * Indicates whether the display object has any mouse event listeners or a cursor. * @method _isMouseOpaque * @return {Boolean} * @protected **/ p._hasMouseEventListener = function() { var evts = DisplayObject._MOUSE_EVENTS; for (var i= 0, l=evts.length; itransform and alpha properties concatenated with their parent * Container. * * For example, a {{#crossLink "Shape"}}{{/crossLink}} with x=100 and alpha=0.5, placed in a Container with x=50 * and alpha=0.7 will be rendered to the canvas at x=150 and alpha=0.35. * Containers have some overhead, so you generally shouldn't create a Container to hold a single child. * *

Example

* * var container = new createjs.Container(); * container.addChild(bitmapInstance, shapeInstance); * container.x = 100; * * @class Container * @extends DisplayObject * @constructor **/ function Container() { this.DisplayObject_constructor(); // public properties: /** * The array of children in the display list. You should usually use the child management methods such as * {{#crossLink "Container/addChild"}}{{/crossLink}}, {{#crossLink "Container/removeChild"}}{{/crossLink}}, * {{#crossLink "Container/swapChildren"}}{{/crossLink}}, etc, rather than accessing this directly, but it is * included for advanced uses. * @property children * @type Array * @default null **/ this.children = []; /** * Indicates whether the children of this container are independently enabled for mouse/pointer interaction. * If false, the children will be aggregated under the container - for example, a click on a child shape would * trigger a click event on the container. * @property mouseChildren * @type Boolean * @default true **/ this.mouseChildren = true; /** * If false, the tick will not be propagated to children of this Container. This can provide some performance benefits. * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates * on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). * @property tickChildren * @type Boolean * @default true **/ this.tickChildren = true; } var p = createjs.extend(Container, createjs.DisplayObject); // getter / setters: /** * Use the {{#crossLink "Container/numChildren:property"}}{{/crossLink}} property instead. * @method _getNumChildren * @protected * @return {Number} **/ p._getNumChildren = function() { return this.children.length; }; // Container.getNumChildren is @deprecated. Remove for 1.1+ p.getNumChildren = createjs.deprecate(p._getNumChildren, "Container.getNumChildren"); /** * Returns the number of children in the container. * @property numChildren * @type {Number} * @readonly **/ try { Object.defineProperties(p, { numChildren: { get: p._getNumChildren } }); } catch (e) {} // public methods: /** * Constructor alias for backwards compatibility. This method will be removed in future versions. * Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. * @method initialize * @deprecated in favour of `createjs.promote()` **/ p.initialize = Container; // TODO: deprecated. /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { var hasContent = this.cacheCanvas || this.children.length; return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } // this ensures we don't have issues with display list changes that occur during a draw: var list = this.children.slice(); for (var i=0,l=list.length; iExample * * container.addChild(bitmapInstance); * * You can also add multiple children at once: * * container.addChild(bitmapInstance, shapeInstance, textInstance); * * @method addChild * @param {DisplayObject} child The display object to add. * @return {DisplayObject} The child that was added, or the last child if multiple children were added. **/ p.addChild = function(child) { if (child == null) { return child; } var l = arguments.length; if (l > 1) { for (var i=0; iExample * * addChildAt(child1, index); * * You can also add multiple children, such as: * * addChildAt(child1, child2, ..., index); * * The index must be between 0 and numChildren. For example, to add myShape under otherShape in the display list, * you could use: * * container.addChildAt(myShape, container.getChildIndex(otherShape)); * * This would also bump otherShape's index up by one. Fails silently if the index is out of range. * * @method addChildAt * @param {DisplayObject} child The display object to add. * @param {Number} index The index to add the child at. * @return {DisplayObject} Returns the last child that was added, or the last child if multiple children were added. **/ p.addChildAt = function(child, index) { var l = arguments.length; var indx = arguments[l-1]; // can't use the same name as the index param or it replaces arguments[1] if (indx < 0 || indx > this.children.length) { return arguments[l-2]; } if (l > 2) { for (var i=0; iExample * * container.removeChild(child); * * You can also remove multiple children: * * removeChild(child1, child2, ...); * * Returns true if the child (or children) was removed, or false if it was not in the display list. * @method removeChild * @param {DisplayObject} child The child to remove. * @return {Boolean} true if the child (or children) was removed, or false if it was not in the display list. **/ p.removeChild = function(child) { var l = arguments.length; if (l > 1) { var good = true; for (var i=0; iExample * * container.removeChildAt(2); * * You can also remove multiple children: * * container.removeChild(2, 7, ...) * * Returns true if the child (or children) was removed, or false if any index was out of range. * @method removeChildAt * @param {Number} index The index of the child to remove. * @return {Boolean} true if the child (or children) was removed, or false if any index was out of range. **/ p.removeChildAt = function(index) { var l = arguments.length; if (l > 1) { var a = []; for (var i=0; iExample * * container.removeAllChildren(); * * @method removeAllChildren **/ p.removeAllChildren = function() { var kids = this.children; while (kids.length) { this._removeChildAt(0); } }; /** * Returns the child at the specified index. * *

Example

* * container.getChildAt(2); * * @method getChildAt * @param {Number} index The index of the child to return. * @return {DisplayObject} The child at the specified index. Returns null if there is no child at the index. **/ p.getChildAt = function(index) { return this.children[index]; }; /** * Returns the child with the specified name. * @method getChildByName * @param {String} name The name of the child to return. * @return {DisplayObject} The child with the specified name. **/ p.getChildByName = function(name) { var kids = this.children; for (var i=0,l=kids.length;iExample: Display children with a higher y in front. * * var sortFunction = function(obj1, obj2, options) { * if (obj1.y > obj2.y) { return 1; } * if (obj1.y < obj2.y) { return -1; } * return 0; * } * container.sortChildren(sortFunction); * * @method sortChildren * @param {Function} sortFunction the function to use to sort the child list. See JavaScript's Array.sort * documentation for details. **/ p.sortChildren = function(sortFunction) { this.children.sort(sortFunction); }; /** * Returns the index of the specified child in the display list, or -1 if it is not in the display list. * *

Example

* * var index = container.getChildIndex(child); * * @method getChildIndex * @param {DisplayObject} child The child to return the index of. * @return {Number} The index of the specified child. -1 if the child is not found. **/ p.getChildIndex = function(child) { return createjs.indexOf(this.children, child); }; /** * Swaps the children at the specified indexes. Fails silently if either index is out of range. * @method swapChildrenAt * @param {Number} index1 * @param {Number} index2 **/ p.swapChildrenAt = function(index1, index2) { var kids = this.children; var o1 = kids[index1]; var o2 = kids[index2]; if (!o1 || !o2) { return; } kids[index1] = o2; kids[index2] = o1; }; /** * Swaps the specified children's depth in the display list. Fails silently if either child is not a child of this * Container. * @method swapChildren * @param {DisplayObject} child1 * @param {DisplayObject} child2 **/ p.swapChildren = function(child1, child2) { var kids = this.children; var index1,index2; for (var i=0,l=kids.length;i= l) { return; } for (var i=0;i 0 at the * specified position). This ignores the alpha, shadow and compositeOperation of the display object, and all * transform properties including regX/Y. * @method hitTest * @param {Number} x The x position to check in the display object's local coordinates. * @param {Number} y The y position to check in the display object's local coordinates. * @return {Boolean} A Boolean indicating whether there is a visible section of a DisplayObject that overlaps the specified * coordinates. **/ p.hitTest = function(x, y) { // TODO: optimize to use the fast cache check where possible. return (this.getObjectUnderPoint(x, y) != null); }; /** * Returns an array of all display objects under the specified coordinates that are in this container's display * list. This routine ignores any display objects with {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}} * set to `false`. The array will be sorted in order of visual depth, with the top-most display object at index 0. * This uses shape based hit detection, and can be an expensive operation to run, so it is best to use it carefully. * For example, if testing for objects under the mouse, test on tick (instead of on {{#crossLink "DisplayObject/mousemove:event"}}{{/crossLink}}), * and only if the mouse's position has changed. * *
    *
  • By default (mode=0) this method evaluates all display objects.
  • *
  • By setting the `mode` parameter to `1`, the {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}} * and {{#crossLink "mouseChildren:property"}}{{/crossLink}} properties will be respected.
  • *
  • Setting the `mode` to `2` additionally excludes display objects that do not have active mouse event * listeners or a {{#crossLink "DisplayObject:cursor:property"}}{{/crossLink}} property. That is, only objects * that would normally intercept mouse interaction will be included. This can significantly improve performance * in some cases by reducing the number of display objects that need to be tested.
  • * * * This method accounts for both {{#crossLink "DisplayObject/hitArea:property"}}{{/crossLink}} and {{#crossLink "DisplayObject/mask:property"}}{{/crossLink}}. * @method getObjectsUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. * @param {Number} [mode=0] The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. * @return {Array} An Array of DisplayObjects under the specified coordinates. **/ p.getObjectsUnderPoint = function(x, y, mode) { var arr = []; var pt = this.localToGlobal(x, y); this._getObjectsUnderPoint(pt.x, pt.y, arr, mode>0, mode==1); return arr; }; /** * Similar to {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}, but returns only the top-most display * object. This runs significantly faster than getObjectsUnderPoint(), but is still potentially an expensive * operation. See {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} for more information. * @method getObjectUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. * @param {Number} mode The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. * @return {DisplayObject} The top-most display object under the specified coordinates. **/ p.getObjectUnderPoint = function(x, y, mode) { var pt = this.localToGlobal(x, y); return this._getObjectsUnderPoint(pt.x, pt.y, null, mode>0, mode==1); }; /** * Docced in superclass. */ p.getBounds = function() { return this._getBounds(null, true); }; /** * Docced in superclass. */ p.getTransformedBounds = function() { return this._getBounds(); }; /** * Returns a clone of this Container. Some properties that are specific to this instance's current context are * reverted to their defaults (for example .parent). * @method clone * @param {Boolean} [recursive=false] If true, all of the descendants of this container will be cloned recursively. If false, the * properties of the container will be cloned, but the new instance will not have any children. * @return {Container} A clone of the current Container instance. **/ p.clone = function(recursive) { var o = this._cloneProps(new Container()); if (recursive) { this._cloneChildren(o); } return o; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Container (name="+ this.name +")]"; }; // private methods: /** * @method _tick * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * @protected **/ p._tick = function(evtObj) { if (this.tickChildren) { for (var i=this.children.length-1; i>=0; i--) { var child = this.children[i]; if (child.tickEnabled && child._tick) { child._tick(evtObj); } } } this.DisplayObject__tick(evtObj); }; /** * Recursively clones all children of this container, and adds them to the target container. * @method cloneChildren * @protected * @param {Container} o The target container. **/ p._cloneChildren = function(o) { if (o.children.length) { o.removeAllChildren(); } var arr = o.children; for (var i=0, l=this.children.length; i this.children.length-1) { return false; } var child = this.children[index]; if (child) { child.parent = null; } this.children.splice(index, 1); if (!silent) { child.dispatchEvent("removed"); } return true; }; /** * @method _getObjectsUnderPoint * @param {Number} x * @param {Number} y * @param {Array} arr * @param {Boolean} mouse If true, it will respect mouse interaction properties like mouseEnabled, mouseChildren, and active listeners. * @param {Boolean} activeListener If true, there is an active mouse event listener on a parent object. * @param {Number} currentDepth Indicates the current depth of the search. * @return {DisplayObject} * @protected **/ p._getObjectsUnderPoint = function(x, y, arr, mouse, activeListener, currentDepth) { currentDepth = currentDepth || 0; if (!currentDepth && !this._testMask(this, x, y)) { return null; } var mtx, ctx = createjs.DisplayObject._hitTestContext; activeListener = activeListener || (mouse&&this._hasMouseEventListener()); // draw children one at a time, and check if we get a hit: var children = this.children, l = children.length; for (var i=l-1; i>=0; i--) { var child = children[i]; var hitArea = child.hitArea; if (!child.visible || (!hitArea && !child.isVisible()) || (mouse && !child.mouseEnabled)) { continue; } if (!hitArea && !this._testMask(child, x, y)) { continue; } // if a child container has a hitArea then we only need to check its hitAre2a, so we can treat it as a normal DO: if (!hitArea && child instanceof Container) { var result = child._getObjectsUnderPoint(x, y, arr, mouse, activeListener, currentDepth+1); if (!arr && result) { return (mouse && !this.mouseChildren) ? this : result; } } else { if (mouse && !activeListener && !child._hasMouseEventListener()) { continue; } // TODO: can we pass displayProps forward, to avoid having to calculate this backwards every time? It's kind of a mixed bag. When we're only hunting for DOs with event listeners, it may not make sense. var props = child.getConcatenatedDisplayProps(child._props); mtx = props.matrix; if (hitArea) { mtx.appendMatrix(hitArea.getMatrix(hitArea._props.matrix)); props.alpha = hitArea.alpha; } ctx.globalAlpha = props.alpha; ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx-x, mtx.ty-y); (hitArea||child).draw(ctx); if (!this._testHit(ctx)) { continue; } ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, 2, 2); if (arr) { arr.push(child); } else { return (mouse && !this.mouseChildren) ? this : child; } } } return null; }; /** * @method _testMask * @param {DisplayObject} target * @param {Number} x * @param {Number} y * @return {Boolean} Indicates whether the x/y is within the masked region. * @protected **/ p._testMask = function(target, x, y) { var mask = target.mask; if (!mask || !mask.graphics || mask.graphics.isEmpty()) { return true; } var mtx = this._props.matrix, parent = target.parent; mtx = parent ? parent.getConcatenatedMatrix(mtx) : mtx.identity(); mtx = mask.getMatrix(mask._props.matrix).prependMatrix(mtx); var ctx = createjs.DisplayObject._hitTestContext; ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx-x, mtx.ty-y); // draw the mask as a solid fill: mask.graphics.drawAsPath(ctx); ctx.fillStyle = "#000"; ctx.fill(); if (!this._testHit(ctx)) { return false; } ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, 2, 2); return true; }; /** * @method _getBounds * @param {Matrix2D} matrix * @param {Boolean} ignoreTransform If true, does not apply this object's transform. * @return {Rectangle} * @protected **/ p._getBounds = function(matrix, ignoreTransform) { var bounds = this.DisplayObject_getBounds(); if (bounds) { return this._transformBounds(bounds, matrix, ignoreTransform); } var mtx = this._props.matrix; mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); if (matrix) { mtx.prependMatrix(matrix); } var l = this.children.length, rect=null; for (var i=0; iExample * This example creates a stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child * and redraw the stage using {{#crossLink "Stage/update"}}{{/crossLink}}. * * var stage = new createjs.Stage("canvasElementId"); * var image = new createjs.Bitmap("imagePath.png"); * stage.addChild(image); * createjs.Ticker.addEventListener("tick", handleTick); * function handleTick(event) { * image.x += 10; * stage.update(); * } * * @class Stage * @extends Container * @constructor * @param {HTMLCanvasElement | String | Object} canvas A canvas object that the Stage will render to, or the string id * of a canvas object in the current document. **/ function Stage(canvas) { this.Container_constructor(); // public properties: /** * Indicates whether the stage should automatically clear the canvas before each render. You can set this to false * to manually control clearing (for generative art, or when pointing multiple stages at the same canvas for * example). * *

    Example

    * * var stage = new createjs.Stage("canvasId"); * stage.autoClear = false; * * @property autoClear * @type Boolean * @default true **/ this.autoClear = true; /** * The canvas the stage will render to. Multiple stages can share a single canvas, but you must disable autoClear for all but the * first stage that will be ticked (or they will clear each other's render). * * When changing the canvas property you must disable the events on the old canvas, and enable events on the * new canvas or mouse events will not work as expected. For example: * * myStage.enableDOMEvents(false); * myStage.canvas = anotherCanvas; * myStage.enableDOMEvents(true); * * @property canvas * @type HTMLCanvasElement | Object **/ this.canvas = (typeof canvas == "string") ? document.getElementById(canvas) : canvas; /** * The current mouse X position on the canvas. If the mouse leaves the canvas, this will indicate the most recent * position over the canvas, and mouseInBounds will be set to false. * @property mouseX * @type Number * @readonly **/ this.mouseX = 0; /** * The current mouse Y position on the canvas. If the mouse leaves the canvas, this will indicate the most recent * position over the canvas, and mouseInBounds will be set to false. * @property mouseY * @type Number * @readonly **/ this.mouseY = 0; /** * Specifies the area of the stage to affect when calling update. This can be use to selectively * re-draw specific regions of the canvas. If null, the whole canvas area is drawn. * @property drawRect * @type {Rectangle} */ this.drawRect = null; /** * Indicates whether display objects should be rendered on whole pixels. You can set the * {{#crossLink "DisplayObject/snapToPixel"}}{{/crossLink}} property of * display objects to false to enable/disable this behaviour on a per instance basis. * @property snapToPixelEnabled * @type Boolean * @default false **/ this.snapToPixelEnabled = false; /** * Indicates whether the mouse is currently within the bounds of the canvas. * @property mouseInBounds * @type Boolean * @default false **/ this.mouseInBounds = false; /** * If true, tick callbacks will be called on all display objects on the stage prior to rendering to the canvas. * @property tickOnUpdate * @type Boolean * @default true **/ this.tickOnUpdate = true; /** * If true, mouse move events will continue to be called when the mouse leaves the target canvas. See * {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}}, and {{#crossLink "MouseEvent"}}{{/crossLink}} * x/y/rawX/rawY. * @property mouseMoveOutside * @type Boolean * @default false **/ this.mouseMoveOutside = false; /** * Prevents selection of other elements in the html page if the user clicks and drags, or double clicks on the canvas. * This works by calling `preventDefault()` on any mousedown events (or touch equivalent) originating on the canvas. * @property preventSelection * @type Boolean * @default true **/ this.preventSelection = true; /** * The hitArea property is not supported for Stage. * @property hitArea * @type {DisplayObject} * @default null */ // private properties: /** * Holds objects with data for each active pointer id. Each object has the following properties: * x, y, event, target, overTarget, overX, overY, inBounds, posEvtObj (native event that last updated position) * @property _pointerData * @type {Object} * @private */ this._pointerData = {}; /** * Number of active pointers. * @property _pointerCount * @type {Object} * @private */ this._pointerCount = 0; /** * The ID of the primary pointer. * @property _primaryPointerID * @type {Object} * @private */ this._primaryPointerID = null; /** * @property _mouseOverIntervalID * @protected * @type Number **/ this._mouseOverIntervalID = null; /** * @property _nextStage * @protected * @type Stage **/ this._nextStage = null; /** * @property _prevStage * @protected * @type Stage **/ this._prevStage = null; // initialize: this.enableDOMEvents(true); } var p = createjs.extend(Stage, createjs.Container); // events: /** * Dispatched when the user moves the mouse over the canvas. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event stagemousemove * @since 0.6.0 */ /** * Dispatched when the user presses their left mouse button on the canvas. See the {{#crossLink "MouseEvent"}}{{/crossLink}} * class for a listing of event properties. * @event stagemousedown * @since 0.6.0 */ /** * Dispatched when the user the user presses somewhere on the stage, then releases the mouse button anywhere that the page can detect it (this varies slightly between browsers). * You can use {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}} to check whether the mouse is currently within the stage bounds. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event stagemouseup * @since 0.6.0 */ /** * Dispatched when the mouse moves from within the canvas area (mouseInBounds == true) to outside it (mouseInBounds == false). * This is currently only dispatched for mouse input (not touch). See the {{#crossLink "MouseEvent"}}{{/crossLink}} * class for a listing of event properties. * @event mouseleave * @since 0.7.0 */ /** * Dispatched when the mouse moves into the canvas area (mouseInBounds == false) from outside it (mouseInBounds == true). * This is currently only dispatched for mouse input (not touch). See the {{#crossLink "MouseEvent"}}{{/crossLink}} * class for a listing of event properties. * @event mouseenter * @since 0.7.0 */ /** * Dispatched each update immediately before the tick event is propagated through the display list. * You can call preventDefault on the event object to cancel propagating the tick event. * @event tickstart * @since 0.7.0 */ /** * Dispatched each update immediately after the tick event is propagated through the display list. Does not fire if * tickOnUpdate is false. Precedes the "drawstart" event. * @event tickend * @since 0.7.0 */ /** * Dispatched each update immediately before the canvas is cleared and the display list is drawn to it. * You can call preventDefault on the event object to cancel the draw. * @event drawstart * @since 0.7.0 */ /** * Dispatched each update immediately after the display list is drawn to the canvas and the canvas context is restored. * @event drawend * @since 0.7.0 */ // getter / setters: /** * Specifies a target stage that will have mouse / touch interactions relayed to it after this stage handles them. * This can be useful in cases where you have multiple layered canvases and want user interactions * events to pass through. For example, this would relay mouse events from topStage to bottomStage: * * topStage.nextStage = bottomStage; * * To disable relaying, set nextStage to null. * * MouseOver, MouseOut, RollOver, and RollOut interactions are also passed through using the mouse over settings * of the top-most stage, but are only processed if the target stage has mouse over interactions enabled. * Considerations when using roll over in relay targets:
      *
    1. The top-most (first) stage must have mouse over interactions enabled (via enableMouseOver)
    2. *
    3. All stages that wish to participate in mouse over interaction must enable them via enableMouseOver
    4. *
    5. All relay targets will share the frequency value of the top-most stage
    6. *
    * To illustrate, in this example the targetStage would process mouse over interactions at 10hz (despite passing * 30 as it's desired frequency): * topStage.nextStage = targetStage; * topStage.enableMouseOver(10); * targetStage.enableMouseOver(30); * * If the target stage's canvas is completely covered by this stage's canvas, you may also want to disable its * DOM events using: * * targetStage.enableDOMEvents(false); * * @property nextStage * @type {Stage} **/ p._get_nextStage = function() { return this._nextStage; }; p._set_nextStage = function(value) { if (this._nextStage) { this._nextStage._prevStage = null; } if (value) { value._prevStage = this; } this._nextStage = value; }; try { Object.defineProperties(p, { nextStage: { get: p._get_nextStage, set: p._set_nextStage } }); } catch (e) {} // TODO: use Log // public methods: /** * Each time the update method is called, the stage will call {{#crossLink "Stage/tick"}}{{/crossLink}} * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false, * and then render the display list to the canvas. * * @method update * @param {Object} [props] Props object to pass to `tick()`. Should usually be a {{#crossLink "Ticker"}}{{/crossLink}} event object, or similar object with a delta property. **/ p.update = function(props) { if (!this.canvas) { return; } if (this.tickOnUpdate) { this.tick(props); } if (this.dispatchEvent("drawstart", false, true) === false) { return; } createjs.DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled; var r = this.drawRect, ctx = this.canvas.getContext("2d"); ctx.setTransform(1, 0, 0, 1, 0, 0); if (this.autoClear) { if (r) { ctx.clearRect(r.x, r.y, r.width, r.height); } else { ctx.clearRect(0, 0, this.canvas.width+1, this.canvas.height+1); } } ctx.save(); if (this.drawRect) { ctx.beginPath(); ctx.rect(r.x, r.y, r.width, r.height); ctx.clip(); } this.updateContext(ctx); this.draw(ctx, false); ctx.restore(); this.dispatchEvent("drawend"); }; /** * Propagates a tick event through the display list. This is automatically called by {{#crossLink "Stage/update"}}{{/crossLink}} * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false. * * If a props object is passed to `tick()`, then all of its properties will be copied to the event object that is * propagated to listeners. * * Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that * a {{#crossLink "Ticker/tick:event"}}{{/crossLink}} event object (or equivalent object with a delta property) be * passed as the `props` parameter to `tick()`. For example: * * Ticker.on("tick", handleTick); * function handleTick(evtObj) { * // clone the event object from Ticker, and add some custom data to it: * var evt = evtObj.clone().set({greeting:"hello", name:"world"}); * * // pass it to stage.update(): * myStage.update(evt); // subsequently calls tick() with the same param * } * * // ... * myDisplayObject.on("tick", handleDisplayObjectTick); * function handleDisplayObjectTick(evt) { * console.log(evt.delta); // the delta property from the Ticker tick event object * console.log(evt.greeting, evt.name); // custom data: "hello world" * } * * @method tick * @param {Object} [props] An object with properties that should be copied to the event object. Should usually be a Ticker event object, or similar object with a delta property. **/ p.tick = function(props) { if (!this.tickEnabled || this.dispatchEvent("tickstart", false, true) === false) { return; } var evtObj = new createjs.Event("tick"); if (props) { for (var n in props) { if (props.hasOwnProperty(n)) { evtObj[n] = props[n]; } } } this._tick(evtObj); this.dispatchEvent("tickend"); }; /** * Default event handler that calls the Stage {{#crossLink "Stage/update"}}{{/crossLink}} method when a {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} * event is received. This allows you to register a Stage instance as a event listener on {{#crossLink "Ticker"}}{{/crossLink}} * directly, using: * * Ticker.addEventListener("tick", myStage); * * Note that if you subscribe to ticks using this pattern, then the tick event object will be passed through to * display object tick handlers, instead of delta and paused parameters. * @property handleEvent * @type Function **/ p.handleEvent = function(evt) { if (evt.type == "tick") { this.update(evt); } }; /** * Clears the target canvas. Useful if {{#crossLink "Stage/autoClear:property"}}{{/crossLink}} is set to `false`. * @method clear **/ p.clear = function() { if (!this.canvas) { return; } var ctx = this.canvas.getContext("2d"); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, this.canvas.width+1, this.canvas.height+1); }; /** * Returns a data url that contains a Base64-encoded image of the contents of the stage. The returned data url can * be specified as the src value of an image element. * @method toDataURL * @param {String} [backgroundColor] The background color to be used for the generated image. Any valid CSS color * value is allowed. The default value is a transparent background. * @param {String} [mimeType="image/png"] The MIME type of the image format to be create. The default is "image/png". If an unknown MIME type * is passed in, or if the browser does not support the specified MIME type, the default value will be used. * @return {String} a Base64 encoded image. **/ p.toDataURL = function(backgroundColor, mimeType) { var data, ctx = this.canvas.getContext('2d'), w = this.canvas.width, h = this.canvas.height; if (backgroundColor) { data = ctx.getImageData(0, 0, w, h); var compositeOperation = ctx.globalCompositeOperation; ctx.globalCompositeOperation = "destination-over"; ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, w, h); } var dataURL = this.canvas.toDataURL(mimeType||"image/png"); if(backgroundColor) { ctx.putImageData(data, 0, 0); ctx.globalCompositeOperation = compositeOperation; } return dataURL; }; /** * Enables or disables (by passing a frequency of 0) mouse over ({{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}} * and {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}}) and roll over events ({{#crossLink "DisplayObject/rollover:event"}}{{/crossLink}} * and {{#crossLink "DisplayObject/rollout:event"}}{{/crossLink}}) for this stage's display list. These events can * be expensive to generate, so they are disabled by default. The frequency of the events can be controlled * independently of mouse move events via the optional `frequency` parameter. * *

    Example

    * * var stage = new createjs.Stage("canvasId"); * stage.enableMouseOver(10); // 10 updates per second * * @method enableMouseOver * @param {Number} [frequency=20] Optional param specifying the maximum number of times per second to broadcast * mouse over/out events. Set to 0 to disable mouse over events completely. Maximum is 50. A lower frequency is less * responsive, but uses less CPU. **/ p.enableMouseOver = function(frequency) { if (this._mouseOverIntervalID) { clearInterval(this._mouseOverIntervalID); this._mouseOverIntervalID = null; if (frequency == 0) { this._testMouseOver(true); } } if (frequency == null) { frequency = 20; } else if (frequency <= 0) { return; } var o = this; this._mouseOverIntervalID = setInterval(function(){ o._testMouseOver(); }, 1000/Math.min(50,frequency)); }; /** * Enables or disables the event listeners that stage adds to DOM elements (window, document and canvas). It is good * practice to disable events when disposing of a Stage instance, otherwise the stage will continue to receive * events from the page. * * When changing the canvas property you must disable the events on the old canvas, and enable events on the * new canvas or mouse events will not work as expected. For example: * * myStage.enableDOMEvents(false); * myStage.canvas = anotherCanvas; * myStage.enableDOMEvents(true); * * @method enableDOMEvents * @param {Boolean} [enable=true] Indicates whether to enable or disable the events. Default is true. **/ p.enableDOMEvents = function(enable) { if (enable == null) { enable = true; } var n, o, ls = this._eventListeners; if (!enable && ls) { for (n in ls) { o = ls[n]; o.t.removeEventListener(n, o.f, false); } this._eventListeners = null; } else if (enable && !ls && this.canvas) { var t = window.addEventListener ? window : document; var _this = this; ls = this._eventListeners = {}; ls["mouseup"] = {t:t, f:function(e) { _this._handleMouseUp(e)} }; ls["mousemove"] = {t:t, f:function(e) { _this._handleMouseMove(e)} }; ls["dblclick"] = {t:this.canvas, f:function(e) { _this._handleDoubleClick(e)} }; ls["mousedown"] = {t:this.canvas, f:function(e) { _this._handleMouseDown(e)} }; for (n in ls) { o = ls[n]; o.t.addEventListener(n, o.f, false); } } }; /** * Stage instances cannot be cloned. * @method clone **/ p.clone = function() { throw("Stage cannot be cloned."); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Stage (name="+ this.name +")]"; }; // private methods: /** * @method _getElementRect * @protected * @param {HTMLElement} e **/ p._getElementRect = function(e) { var bounds; try { bounds = e.getBoundingClientRect(); } // this can fail on disconnected DOM elements in IE9 catch (err) { bounds = {top: e.offsetTop, left: e.offsetLeft, width:e.offsetWidth, height:e.offsetHeight}; } var offX = (window.pageXOffset || document.scrollLeft || 0) - (document.clientLeft || document.body.clientLeft || 0); var offY = (window.pageYOffset || document.scrollTop || 0) - (document.clientTop || document.body.clientTop || 0); var styles = window.getComputedStyle ? getComputedStyle(e,null) : e.currentStyle; // IE <9 compatibility. var padL = parseInt(styles.paddingLeft)+parseInt(styles.borderLeftWidth); var padT = parseInt(styles.paddingTop)+parseInt(styles.borderTopWidth); var padR = parseInt(styles.paddingRight)+parseInt(styles.borderRightWidth); var padB = parseInt(styles.paddingBottom)+parseInt(styles.borderBottomWidth); // note: in some browsers bounds properties are read only. return { left: bounds.left+offX+padL, right: bounds.right+offX-padR, top: bounds.top+offY+padT, bottom: bounds.bottom+offY-padB } }; /** * @method _getPointerData * @protected * @param {Number} id **/ p._getPointerData = function(id) { var data = this._pointerData[id]; if (!data) { data = this._pointerData[id] = {x:0,y:0}; } return data; }; /** * @method _handleMouseMove * @protected * @param {MouseEvent} e **/ p._handleMouseMove = function(e) { if(!e){ e = window.event; } this._handlePointerMove(-1, e, e.pageX, e.pageY); }; /** * @method _handlePointerMove * @protected * @param {Number} id * @param {Event} e * @param {Number} pageX * @param {Number} pageY * @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. **/ p._handlePointerMove = function(id, e, pageX, pageY, owner) { if (this._prevStage && owner === undefined) { return; } // redundant listener. if (!this.canvas) { return; } var nextStage=this._nextStage, o=this._getPointerData(id); var inBounds = o.inBounds; this._updatePointerPosition(id, e, pageX, pageY); if (inBounds || o.inBounds || this.mouseMoveOutside) { if (id === -1 && o.inBounds == !inBounds) { this._dispatchMouseEvent(this, (inBounds ? "mouseleave" : "mouseenter"), false, id, o, e); } this._dispatchMouseEvent(this, "stagemousemove", false, id, o, e); this._dispatchMouseEvent(o.target, "pressmove", true, id, o, e); } nextStage&&nextStage._handlePointerMove(id, e, pageX, pageY, null); }; /** * @method _updatePointerPosition * @protected * @param {Number} id * @param {Event} e * @param {Number} pageX * @param {Number} pageY **/ p._updatePointerPosition = function(id, e, pageX, pageY) { var rect = this._getElementRect(this.canvas); pageX -= rect.left; pageY -= rect.top; var w = this.canvas.width; var h = this.canvas.height; pageX /= (rect.right-rect.left)/w; pageY /= (rect.bottom-rect.top)/h; var o = this._getPointerData(id); if (o.inBounds = (pageX >= 0 && pageY >= 0 && pageX <= w-1 && pageY <= h-1)) { o.x = pageX; o.y = pageY; } else if (this.mouseMoveOutside) { o.x = pageX < 0 ? 0 : (pageX > w-1 ? w-1 : pageX); o.y = pageY < 0 ? 0 : (pageY > h-1 ? h-1 : pageY); } o.posEvtObj = e; o.rawX = pageX; o.rawY = pageY; if (id === this._primaryPointerID || id === -1) { this.mouseX = o.x; this.mouseY = o.y; this.mouseInBounds = o.inBounds; } }; /** * @method _handleMouseUp * @protected * @param {MouseEvent} e **/ p._handleMouseUp = function(e) { this._handlePointerUp(-1, e, false); }; /** * @method _handlePointerUp * @protected * @param {Number} id * @param {Event} e * @param {Boolean} clear * @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. **/ p._handlePointerUp = function(id, e, clear, owner) { var nextStage = this._nextStage, o = this._getPointerData(id); if (this._prevStage && owner === undefined) { return; } // redundant listener. var target=null, oTarget = o.target; if (!owner && (oTarget || nextStage)) { target = this._getObjectsUnderPoint(o.x, o.y, null, true); } if (o.down) { this._dispatchMouseEvent(this, "stagemouseup", false, id, o, e, target); o.down = false; } if (target == oTarget) { this._dispatchMouseEvent(oTarget, "click", true, id, o, e); } this._dispatchMouseEvent(oTarget, "pressup", true, id, o, e); if (clear) { if (id==this._primaryPointerID) { this._primaryPointerID = null; } delete(this._pointerData[id]); } else { o.target = null; } nextStage&&nextStage._handlePointerUp(id, e, clear, owner || target && this); }; /** * @method _handleMouseDown * @protected * @param {MouseEvent} e **/ p._handleMouseDown = function(e) { this._handlePointerDown(-1, e, e.pageX, e.pageY); }; /** * @method _handlePointerDown * @protected * @param {Number} id * @param {Event} e * @param {Number} pageX * @param {Number} pageY * @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. **/ p._handlePointerDown = function(id, e, pageX, pageY, owner) { if (this.preventSelection) { e.preventDefault(); } if (this._primaryPointerID == null || id === -1) { this._primaryPointerID = id; } // mouse always takes over. if (pageY != null) { this._updatePointerPosition(id, e, pageX, pageY); } var target = null, nextStage = this._nextStage, o = this._getPointerData(id); if (!owner) { target = o.target = this._getObjectsUnderPoint(o.x, o.y, null, true); } if (o.inBounds) { this._dispatchMouseEvent(this, "stagemousedown", false, id, o, e, target); o.down = true; } this._dispatchMouseEvent(target, "mousedown", true, id, o, e); nextStage&&nextStage._handlePointerDown(id, e, pageX, pageY, owner || target && this); }; /** * @method _testMouseOver * @param {Boolean} clear If true, clears the mouseover / rollover (ie. no target) * @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. * @param {Stage} eventTarget The stage that the cursor is actively over. * @protected **/ p._testMouseOver = function(clear, owner, eventTarget) { if (this._prevStage && owner === undefined) { return; } // redundant listener. var nextStage = this._nextStage; if (!this._mouseOverIntervalID) { // not enabled for mouseover, but should still relay the event. nextStage&&nextStage._testMouseOver(clear, owner, eventTarget); return; } var o = this._getPointerData(-1); // only update if the mouse position has changed. This provides a lot of optimization, but has some trade-offs. if (!o || (!clear && this.mouseX == this._mouseOverX && this.mouseY == this._mouseOverY && this.mouseInBounds)) { return; } var e = o.posEvtObj; var isEventTarget = eventTarget || e&&(e.target == this.canvas); var target=null, common = -1, cursor="", t, i, l; if (!owner && (clear || this.mouseInBounds && isEventTarget)) { target = this._getObjectsUnderPoint(this.mouseX, this.mouseY, null, true); this._mouseOverX = this.mouseX; this._mouseOverY = this.mouseY; } var oldList = this._mouseOverTarget||[]; var oldTarget = oldList[oldList.length-1]; var list = this._mouseOverTarget = []; // generate ancestor list and check for cursor: t = target; while (t) { list.unshift(t); if (!cursor) { cursor = t.cursor; } t = t.parent; } this.canvas.style.cursor = cursor; if (!owner && eventTarget) { eventTarget.canvas.style.cursor = cursor; } // find common ancestor: for (i=0,l=list.length; icommon; i--) { this._dispatchMouseEvent(oldList[i], "rollout", false, -1, o, e, target); } for (i=list.length-1; i>common; i--) { this._dispatchMouseEvent(list[i], "rollover", false, -1, o, e, oldTarget); } if (oldTarget != target) { this._dispatchMouseEvent(target, "mouseover", true, -1, o, e, oldTarget); } nextStage&&nextStage._testMouseOver(clear, owner || target && this, eventTarget || isEventTarget && this); }; /** * @method _handleDoubleClick * @protected * @param {MouseEvent} e * @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. **/ p._handleDoubleClick = function(e, owner) { var target=null, nextStage=this._nextStage, o=this._getPointerData(-1); if (!owner) { target = this._getObjectsUnderPoint(o.x, o.y, null, true); this._dispatchMouseEvent(target, "dblclick", true, -1, o, e); } nextStage&&nextStage._handleDoubleClick(e, owner || target && this); }; /** * @method _dispatchMouseEvent * @protected * @param {DisplayObject} target * @param {String} type * @param {Boolean} bubbles * @param {Number} pointerId * @param {Object} o * @param {MouseEvent} [nativeEvent] * @param {DisplayObject} [relatedTarget] **/ p._dispatchMouseEvent = function(target, type, bubbles, pointerId, o, nativeEvent, relatedTarget) { // TODO: might be worth either reusing MouseEvent instances, or adding a willTrigger method to avoid GC. if (!target || (!bubbles && !target.hasEventListener(type))) { return; } /* // TODO: account for stage transformations? this._mtx = this.getConcatenatedMatrix(this._mtx).invert(); var pt = this._mtx.transformPoint(o.x, o.y); var evt = new createjs.MouseEvent(type, bubbles, false, pt.x, pt.y, nativeEvent, pointerId, pointerId==this._primaryPointerID || pointerId==-1, o.rawX, o.rawY); */ var evt = new createjs.MouseEvent(type, bubbles, false, o.x, o.y, nativeEvent, pointerId, pointerId === this._primaryPointerID || pointerId === -1, o.rawX, o.rawY, relatedTarget); target.dispatchEvent(evt); }; createjs.Stage = createjs.promote(Stage, "Container"); }()); //############################################################################## // StageGL.js //############################################################################## /* * README IF EDITING: * Terminology for developers: * * Vertex: a point that help defines a shape, 3 per triangle. Usually has an x,y,z but can have more/less info. * Vertex Property: a piece of information attached to the vertex like a vector3 containing x,y,z * Index/Indices: used in groups of 3 to define a triangle, points to vertices by their index in an array (some render * modes do not use these) * Card: a group of 2 triangles used to display a rectangular image * U/V: common names for the [0-1] texture co-ordinates on an image * Batch: a single call to the renderer, best done as little as possible so multiple cards are put into a single batch * Buffer: WebGL array data * Program/Shader: For every vertex we run the Vertex shader. The results are used per pixel by the Fragment shader. When * combined and paired these are a shader "program" * Texture: WebGL representation of image data and associated extra information * Slot: A space on the GPU into which textures can be loaded for use in a batch, using "ActiveTexture" switches texture slot. */ (function () { "use strict"; /** * A StageGL instance is the root level {{#crossLink "Container"}}{{/crossLink}} for an WebGL-optimized display list, * which is used in place of the usual {{#crossLink "Stage"}}{{/crossLink}}. This class should behave identically to * a {{#crossLink "Stage"}}{{/crossLink}} except for WebGL-specific functionality. * * Each time the {{#crossLink "Stage/tick"}}{{/crossLink}} method is called, the display list is rendered to the * target <canvas/> instance, ignoring non-WebGL-compatible display objects. On devices and browsers that don't * support WebGL, content will automatically be rendered to canvas 2D context instead. * *

    Limitations

    * - {{#crossLink "Shape"}}{{/crossLink}}, {{#crossLink "Shadow"}}{{/crossLink}}, and {{#crossLink "Text"}}{{/crossLink}} * are not rendered when added to the display list. * - To display something StageGL cannot render, {{#crossLink "displayObject/cache"}}{{/crossLink}} the object. * Caches can be rendered regardless of source. * - Images are wrapped as a webGL "Texture". Each graphics card has a limit to its concurrent Textures, too many * Textures will noticeably slow performance. * - Each cache counts as an individual Texture. As such {{#crossLink "SpriteSheet"}}{{/crossLink}} and * {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}} are recommended practices to help keep texture counts low. * - To use any image node (DOM Image/Canvas Element) between multiple StageGL instances it must be a * {{#crossLink "Bitmap/clone"}}{{/crossLink}}, otherwise the GPU texture loading and tracking will get confused. * - to avoid an up/down scaled render you must call {{#crossLink "StageGL/updateViewport"}}{{/crossLink}} if you * resize your canvas after making a StageGL instance, this will properly size the WebGL context stored in memory. * - Best performance in demanding scenarios will come from manual management of texture memory, but it is handled * automatically by default. See {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} for details. * *

    Example

    * This example creates a StageGL instance, adds a child to it, then uses the EaselJS {{#crossLink "Ticker"}}{{/crossLink}} * to update the child and redraw the stage. * * var stage = new createjs.StageGL("canvasElementId"); * * var image = new createjs.Bitmap("imagePath.png"); * stage.addChild(image); * * createjs.Ticker.on("tick", handleTick); * * function handleTick(event) { * image.x += 10; * stage.update(); * } * *

    Notes

    * - StageGL is not currently included in the minified version of EaselJS. * - {{#crossLink "SpriteContainer"}}{{/crossLink}} (the previous approach to WebGL with EaselJS) has been deprecated. * - Earlier versions of WebGL support in EaselJS (SpriteStage and SpriteContainer) had hard limitations on images * per container, which have been solved. * * @class StageGL * @extends Stage * @constructor * @param {HTMLCanvasElement | String | Object} canvas A canvas object that StageGL will render to, or the string id * of a canvas object in the current DOM. * @param {Object} options All the option parameters in a reference object, some are not supported by some browsers. * @param {Boolean} [options.preserveBuffer=false] If `true`, the canvas is NOT auto-cleared by WebGL (the spec * discourages setting this to `true`). This is useful if you want persistent draw effects. * @param {Boolean} [options.antialias=false] Specifies whether or not the browser's WebGL implementation should try * to perform anti-aliasing. This will also enable linear pixel sampling on power-of-two textures (smoother images). * @param {Boolean} [options.transparent=false] If `true`, the canvas is transparent. This is very * expensive, and should be used with caution. * @param {Boolean} [options.premultiply=false] Alters color handling. If `true`, this assumes the shader must * account for pre-multiplied alpha. This can help avoid visual halo effects with some assets, but may also cause * problems with other assets. * @param {Integer} [options.autoPurge=1200] How often the system should automatically dump unused textures with * `purgeTextures(autoPurge)` every `autoPurge/2` draws. See {{#crossLink "StageGL/purgeTextures"}}{{/crossLink}} for more * information. */ function StageGL(canvas, options) { this.Stage_constructor(canvas); if (options !== undefined) { if (typeof options !== "object"){ throw("Invalid options object"); } var premultiply = options.premultiply; var transparent = options.transparent; var antialias = options.antialias; var preserveBuffer = options.preserveBuffer; var autoPurge = options.autoPurge; } // public properties: /** * Console log potential issues and problems. This is designed to have minimal performance impact, so * if extensive debugging information is required, this may be inadequate. See {{#crossLink "WebGLInspector"}}{{/crossLink}} * @property vocalDebug * @type {Boolean} * @default false */ this.vocalDebug = false; // private properties: /** * Specifies whether or not the canvas is auto-cleared by WebGL. The WebGL spec discourages `true`. * If true, the canvas is NOT auto-cleared by WebGL. Used when the canvas context is created and requires * context re-creation to update. * @property _preserveBuffer * @protected * @type {Boolean} * @default false */ this._preserveBuffer = preserveBuffer||false; /** * Specifies whether or not the browser's WebGL implementation should try to perform anti-aliasing. * @property _antialias * @protected * @type {Boolean} * @default false */ this._antialias = antialias||false; /** * Specifies whether or not the browser's WebGL implementation should be transparent. * @property _transparent * @protected * @type {Boolean} * @default false */ this._transparent = transparent||false; /** * Specifies whether or not StageGL is handling colours as premultiplied alpha. * @property _premultiply * @protected * @type {Boolean} * @default false */ this._premultiply = premultiply||false; /** * Internal value of {{#crossLink "StageGL/autoPurge"}}{{/crossLink}} * @property _autoPurge * @protected * @type {Integer} * @default null */ this._autoPurge = undefined; this.autoPurge = autoPurge; //getter/setter handles setting the real value and validating /** * The width in px of the drawing surface saved in memory. * @property _viewportWidth * @protected * @type {Number} * @default 0 */ this._viewportWidth = 0; /** * The height in px of the drawing surface saved in memory. * @property _viewportHeight * @protected * @type {Number} * @default 0 */ this._viewportHeight = 0; /** * A 2D projection matrix used to convert WebGL's viewspace into canvas co-ordinates. Regular canvas display * uses Top-Left values of [0,0] where WebGL uses a Center [0,0] Top-Right [1,1] (euclidean) system. * @property _projectionMatrix * @protected * @type {Float32Array} * @default null */ this._projectionMatrix = null; /** * The current WebGL canvas context. Often shorthanded to just "gl" in many parts of the code. * @property _webGLContext * @protected * @type {WebGLRenderingContext} * @default null */ this._webGLContext = null; /** * The color to use when the WebGL canvas has been cleared. May appear as a background color. Defaults to grey. * @property _clearColor * @protected * @type {Object} * @default {r: 0.50, g: 0.50, b: 0.50, a: 0.00} */ this._clearColor = {r: 0.50, g: 0.50, b: 0.50, a: 0.00}; /** * The maximum number of cards (aka a single sprite) that can be drawn in one draw call. Use getter/setters to * modify otherwise internal buffers may be incorrect sizes. * @property _maxCardsPerBatch * @protected * @type {Number} * @default StageGL.DEFAULT_MAX_BATCH_SIZE (10000) */ this._maxCardsPerBatch = StageGL.DEFAULT_MAX_BATCH_SIZE; //TODO: write getter/setters for this /** * The shader program used to draw the current batch. * @property _activeShader * @protected * @type {WebGLProgram} * @default null */ this._activeShader = null; /** * The vertex position data for the current draw call. * @property _vertices * @protected * @type {Float32Array} * @default null */ this._vertices = null; /** * The WebGL buffer attached to {{#crossLink "StageGL/_vertices:property"}}{{/crossLink}}. * @property _vertexPositionBuffer * @protected * @type {WebGLBuffer} * @default null */ this._vertexPositionBuffer = null; /** * The vertex U/V data for the current draw call. * @property _uvs * @protected * @type {Float32Array} * @default null */ this._uvs = null; /** * The WebGL buffer attached to {{#crossLink "StageGL/_uvs:property"}}{{/crossLink}}. * @property _uvPositionBuffer * @protected * @type {WebGLBuffer} * @default null */ this._uvPositionBuffer = null; /** * The vertex indices data for the current draw call. * @property _indices * @protected * @type {Float32Array} * @default null */ this._indices = null; /** * The WebGL buffer attached to {{#crossLink "StageGL/_indices:property"}}{{/crossLink}}. * @property _textureIndexBuffer * @protected * @type {WebGLBuffer} * @default null */ this._textureIndexBuffer = null; /** * The vertices data for the current draw call. * @property _alphas * @protected * @type {Float32Array} * @default null */ this._alphas = null; /** * The WebGL buffer attached to {{#crossLink "StageGL/_alphas:property"}}{{/crossLink}}. * @property _alphaBuffer * @protected * @type {WebGLBuffer} * @default null */ this._alphaBuffer = null; /** * An index based lookup of every WebGL Texture currently in use. * @property _drawTexture * @protected * @type {Array} */ this._textureDictionary = []; /** * A string based lookup hash of which index a texture is stored at in the dictionary. The lookup string is * often the src url. * @property _textureIDs * @protected * @type {Object} */ this._textureIDs = {}; /** * An array of all the textures currently loaded into the GPU. The index in the array matches the GPU index. * @property _batchTextures * @protected * @type {Array} */ this._batchTextures = []; /** * An array of all the simple filler textures used to prevent issues with missing textures in a batch. * @property _baseTextures * @protected * @type {Array} */ this._baseTextures = []; /** * The number of concurrent textures the GPU can handle. This value is dynamically set from WebGL during initialization * via `gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)`. The WebGL spec states that the lowest guaranteed value is 8, * but it could be higher. Do not set this value higher than the value returned by the GPU. Setting it lower will * probably reduce performance, but may be advisable to reserve slots for custom filter work. * NOTE: Can also act as a length for {{#crossLink "StageGL/_batchTextures:property"}}. * @property _batchTextureCount * @protected * @type {Number} * @default 8 */ this._batchTextureCount = 8; /** * The location at which the last texture was inserted into a GPU slot in {{#crossLink "StageGL/_batchTextures:property"}}{{/crossLink}}. * Manual control of this variable can yield improvements in performance by intelligently replacing textures * inside a batch to reduce texture re-load. It is impossible to write automated general use code, as it requires * display list look ahead inspection and/or render foreknowledge. * @property _lastTextureInsert * @protected * @type {Number} * @default -1 */ this._lastTextureInsert = -1; /** * The current batch being drawn, A batch consists of a call to `drawElements` on the GPU. Many of these calls * can occur per draw. * @property _batchId * @protected * @type {Number} * @default 0 */ this._batchID = 0; /** * The current draw being performed, may contain multiple batches. Comparing to {{#crossLink "StageGL/_batchID:property"}}{{/crossLink}} * can reveal batching efficiency. * @property _drawID * @protected * @type {Number} * @default 0 */ this._drawID = 0; /** * Used to prevent textures in certain GPU slots from being replaced by an insert. * @property _slotBlackList * @protected * @type {Array} */ this._slotBlacklist = []; /** * Used to prevent nested draw calls from accidentally overwriting drawing information by tracking depth. * @property _isDrawing * @protected * @type {Number} * @default 0 */ this._isDrawing = 0; /** * Used to ensure every canvas used as a texture source has a unique ID. * @property _lastTrackedCanvas * @protected * @type {Number} * @default 0 */ this._lastTrackedCanvas = 0; /** * Controls whether final rendering output of a {{#crossLink "cacheDraw"}}{{/crossLink}} is the canvas or a render * texture. See the {{#crossLink "cache"}}{{/crossLink}} function modifications for full implications and discussion. * @property isCacheControlled * @protected * @type {Boolean} * @default false * @todo LM: is this supposed to be _isCacheControlled since its private? */ this.isCacheControlled = false; /** * Used to counter-position the object being cached so it aligns with the cache surface. Additionally ensures * that all rendering starts with a top level container. * @property _cacheContainer * @protected * @type {Container} * @default An instance of an EaselJS Container. */ this._cacheContainer = new createjs.Container(); // and begin this._initializeWebGL(); } var p = createjs.extend(StageGL, createjs.Stage); // static methods: /** * Calculate the U/V co-ordinate based info for sprite frames. Instead of pixel count it uses a 0-1 space. Also includes * the ability to get info back for a specific frame, or only calculate that one frame. * * //generate UV rects for all entries * StageGL.buildUVRects( spriteSheetA ); * //generate all, fetch the first * var firstFrame = StageGL.buildUVRects( spriteSheetB, 0 ); * //generate the rect for just a single frame for performance's sake * var newFrame = StageGL.buildUVRects( dynamicSpriteSheet, newFrameIndex, true ); * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method buildUVRects * @param {SpriteSheet} spritesheet The spritesheet to find the frames on * @param {int} [target=-1] The index of the frame to return * @param {Boolean} [onlyTarget=false] Whether "target" is the only frame that gets calculated * @static * @return {Object} the target frame if supplied and present or a generic frame {t, l, b, r} */ StageGL.buildUVRects = function (spritesheet, target, onlyTarget) { if (!spritesheet || !spritesheet._frames) { return null; } if (target === undefined) { target = -1; } if (onlyTarget === undefined) { onlyTarget = false; } var start = (target != -1 && onlyTarget)?(target):(0); var end = (target != -1 && onlyTarget)?(target+1):(spritesheet._frames.length); for (var i=start; i 0.0035) {" + // 1/255 = 0.0039, so ignore any value below 1 because it's probably noise "gl_FragColor = vec4(color.rgb/color.a, color.a * alphaValue);" + "} else {" + "gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);" + "}" ); //TODO: DHG: a real particle shader /** * @property PARTICLE_VERTEX_BODY * @todo * @final * @static * @type {String} * @readonly */ StageGL.PARTICLE_VERTEX_BODY = ( StageGL.REGULAR_VERTEX_BODY ); /** * @property PARTICLE_FRAGMENT_BODY * @todo * @final * @static * @type {String} * @readonly */ StageGL.PARTICLE_FRAGMENT_BODY = ( StageGL.REGULAR_FRAGMENT_BODY ); /** * Portion of the shader that contains the "varying" properties required in both vertex and fragment shaders. The * cover shader is designed to be a simple vertex/uv only texture render that covers the render surface. Shader * code may contain templates that are replaced pre-compile. * @property COVER_VARYING_HEADER * @static * @final * @type {String} * @readonly */ StageGL.COVER_VARYING_HEADER = ( "precision mediump float;" + "varying highp vec2 vRenderCoord;" + "varying highp vec2 vTextureCoord;" ); /** * Actual full header for the vertex shader. Includes the varying header. The cover shader is designed to be a * simple vertex/uv only texture render that covers the render surface. Shader code may contain templates that are * replaced pre-compile. * @property COVER_VERTEX_HEADER * @static * @final * @type {String} * @readonly */ StageGL.COVER_VERTEX_HEADER = ( StageGL.COVER_VARYING_HEADER + "attribute vec2 vertexPosition;" + "attribute vec2 uvPosition;" + "uniform float uUpright;" ); /** * Actual full header for the fragment shader. Includes the varying header. The cover shader is designed to be a * simple vertex/uv only texture render that covers the render surface. Shader code may contain templates that are * replaced pre-compile. * @property COVER_FRAGMENT_HEADER * @static * @final * @type {String} * @readonly */ StageGL.COVER_FRAGMENT_HEADER = ( StageGL.COVER_VARYING_HEADER + "uniform sampler2D uSampler;" ); /** * Body of the vertex shader. The cover shader is designed to be a simple vertex/uv only texture render that covers * the render surface. Shader code may contain templates that are replaced pre-compile. * @property COVER_VERTEX_BODY * @static * @final * @type {String} * @readonly */ StageGL.COVER_VERTEX_BODY = ( "void main(void) {" + "gl_Position = vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" + "vRenderCoord = uvPosition;" + "vTextureCoord = vec2(uvPosition.x, abs(uUpright - uvPosition.y));" + "}" ); /** * Body of the fragment shader. The cover shader is designed to be a simple vertex/uv only texture render that * covers the render surface. Shader code may contain templates that are replaced pre-compile. * @property COVER_FRAGMENT_BODY * @static * @final * @type {String} * @readonly */ StageGL.COVER_FRAGMENT_BODY = ( "void main(void) {" + "vec4 color = texture2D(uSampler, vRenderCoord);" + "gl_FragColor = color;" + "}" ); // events: /** * Dispatched each update immediately before the canvas is cleared and the display list is drawn to it. You can call * {{#crossLink "Event/preventDefault"}}{{/crossLink}} on the event to cancel the draw. * @event drawstart */ /** * Dispatched each update immediately after the display list is drawn to the canvas and the canvas context is restored. * @event drawend */ // getter / setters: p._get_isWebGL = function () { return !!this._webGLContext; }; p._set_autoPurge = function (value) { value = isNaN(value)?1200:value; if (value != -1) { value = value<10?10:value; } this._autoPurge = value; }; p._get_autoPurge = function () { return Number(this._autoPurge); }; try { Object.defineProperties(p, { /** * Indicates whether WebGL is being used for rendering. For example, this would be `false` if WebGL is not * supported in the browser. * @property isWebGL * @type {Boolean} * @readonly */ isWebGL: { get: p._get_isWebGL }, /** * Specifies whether or not StageGL is automatically purging unused textures. Higher numbers purge less * often. Values below 10 are upgraded to 10, and -1 disables this feature. * @property autoPurge * @protected * @type {Integer} * @default 1000 */ autoPurge: { get: p._get_autoPurge, set: p._set_autoPurge } }); } catch (e) {} // TODO: use Log // constructor methods: /** * Create and properly initialize the WebGL instance. * @method _initializeWebGL * @protected * @return {WebGLRenderingContext} */ p._initializeWebGL = function () { if (this.canvas) { if (!this._webGLContext || this._webGLContext.canvas !== this.canvas) { // A context hasn't been defined yet, // OR the defined context belongs to a different canvas, so reinitialize. // defaults and options var options = { depth: false, // Disable the depth buffer as it isn't used. alpha: this._transparent, // Make the canvas background transparent. stencil: true, antialias: this._antialias, premultipliedAlpha: this._premultiply, // Assume the drawing buffer contains colors with premultiplied alpha. preserveDrawingBuffer: this._preserveBuffer }; var gl = this._webGLContext = this._fetchWebGLContext(this.canvas, options); if (!gl) { return null; } this.updateSimultaneousTextureCount(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)); this._maxTextureSlots = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); this._createBuffers(gl); this._initTextures(gl); gl.disable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._premultiply); //gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); this._webGLContext.clearColor(this._clearColor.r, this._clearColor.g, this._clearColor.b, this._clearColor.a); this.updateViewport(this._viewportWidth || this.canvas.width, this._viewportHeight || this.canvas.height); } } else { this._webGLContext = null; } return this._webGLContext; }; // public methods: /** * Docced in superclass */ p.update = function (props) { if (!this.canvas) { return; } if (this.tickOnUpdate) { this.tick(props); } this.dispatchEvent("drawstart"); if (this.autoClear) { this.clear(); } if (this._webGLContext) { // Use WebGL. this._batchDraw(this, this._webGLContext); if (this._autoPurge != -1 && !(this._drawID%((this._autoPurge/2)|0))) { this.purgeTextures(this._autoPurge); } } else { // Use 2D. var ctx = this.canvas.getContext("2d"); ctx.save(); this.updateContext(ctx); this.draw(ctx, false); ctx.restore(); } this.dispatchEvent("drawend"); }; /** * Docced in superclass */ p.clear = function () { if (!this.canvas) { return; } if (StageGL.isWebGLActive(this._webGLContext)) { var gl = this._webGLContext; var cc = this._clearColor; var adjust = this._transparent ? cc.a : 1.0; // Use WebGL settings; adjust for pre multiplied alpha appropriate to scenario this._webGLContext.clearColor(cc.r * adjust, cc.g * adjust, cc.b * adjust, adjust); gl.clear(gl.COLOR_BUFFER_BIT); this._webGLContext.clearColor(cc.r, cc.g, cc.b, cc.a); } else { // Use 2D. this.Stage_clear(); } }; /** * Draws the stage into the supplied context if possible. Many WebGL properties only exist on their context. As such * you cannot share contexts among many StageGLs and each context requires a unique StageGL instance. Contexts that * don't match the context managed by this StageGL will be treated as a 2D context. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D | WebGLRenderingContext} context The context object to draw into. * @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For * example, used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). * @return {Boolean} If the draw was handled by this function */ p.draw = function (context, ignoreCache) { if (context === this._webGLContext && StageGL.isWebGLActive(this._webGLContext)) { var gl = this._webGLContext; this._batchDraw(this, gl, ignoreCache); return true; } else { return this.Stage_draw(context, ignoreCache); } }; /** * Draws the target into the correct context, be it a canvas or Render Texture using WebGL. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method cacheDraw * @param {DisplayObject} target The object we're drawing into cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). * @param {Array} filters The filters we're drawing into cache. * @param {BitmapCache} manager The BitmapCache instance looking after the cache * @return {Boolean} If the draw was handled by this function */ p.cacheDraw = function (target, filters, manager) { if (StageGL.isWebGLActive(this._webGLContext)) { var gl = this._webGLContext; this._cacheDraw(gl, target, filters, manager); return true; } else { return false; } }; /** * Blocks, or frees a texture "slot" on the GPU. Can be useful if you are overflowing textures. When overflowing * textures they are re-uploaded to the GPU every time they're encountered, this can be expensive with large textures. * By blocking the slot you reduce available slots, potentially increasing draw calls, but mostly you prevent a * texture being re-uploaded if it would have moved slots due to overflow. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * For example, block the slot a background image is stored in so there is less re-loading of that image. * @method protectTextureSlot * @param {Number} id The slot to be affected * @param {Boolean} [lock=false] Whether this slot is the one being locked. */ p.protectTextureSlot = function (id, lock) { if (id > this._maxTextureSlots || id < 0) { throw "Slot outside of acceptable range"; } this._slotBlacklist[id] = !!lock; }; /** * Render textures can't draw into themselves so any item being used for renderTextures needs two to alternate between. * This function creates, gets, and toggles the render surface between the two. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method getTargetRenderTexture * @param {DisplayObject} target The object associated with the render textures, usually a cached object. * @param {Number} w The width to create the texture at. * @param {Number} h The height to create the texture at. * @return {Objet} * @todo fill in return type */ p.getTargetRenderTexture = function (target, w, h) { var result, toggle = false; var gl = this._webGLContext; if (target.__lastRT !== undefined && target.__lastRT === target.__rtA) { toggle = true; } if (!toggle) { if (target.__rtA === undefined) { target.__rtA = this.getRenderBufferTexture(w, h); } else { if (w != target.__rtA._width || h != target.__rtA._height) { this.resizeTexture(target.__rtA, w, h); } this.setTextureParams(gl); } result = target.__rtA; } else { if (target.__rtB === undefined) { target.__rtB = this.getRenderBufferTexture(w, h); } else { if (w != target.__rtB._width || h != target.__rtB._height) { this.resizeTexture(target.__rtB, w, h); } this.setTextureParams(gl); } result = target.__rtB; } if (!result) { throw "Problems creating render textures, known causes include using too much VRAM by not releasing WebGL texture instances"; } target.__lastRT = result; return result; }; /** * For every image encountered StageGL registers and tracks it automatically. This tracking can cause memory leaks * if not purged. StageGL, by default, automatically purges them. This does have a cost and may unfortunately find * false positives. This function is for manual management of this memory instead of the automatic system controlled * by the {{#crossLink "StageGL/autoPurge:property"}}{{/crossLink}} property. * * This function will recursively remove all textures found on the object, its children, cache, etc. It will uncache * objects and remove any texture it finds REGARDLESS of whether it is currently in use elsewhere. It is up to the * developer to ensure that a texture in use is not removed. * * Textures in use, or to be used again shortly, should not be removed. This is simply for performance reasons. * Removing a texture in use will cause the texture to have to be re-uploaded slowing rendering. * @method releaseTexture * @param {DisplayObject | Texture | Image | Canvas} item An object that used the texture to be discarded. */ p.releaseTexture = function (item) { var i, l; if (!item) { return; } // this is a container object if (item.children) { for (i = 0, l = item.children.length; i < l; i++) { this.releaseTexture(item.children[i]); } } // this has a cache canvas if (item.cacheCanvas) { item.uncache(); } var foundImage = undefined; if (item._storeID !== undefined) { // this is a texture itself if (item === this._textureDictionary[item._storeID]) { this._killTextureObject(item); item._storeID = undefined; return; } // this is an image or canvas foundImage = item; } else if (item._webGLRenderStyle === 2) { // this is a Bitmap class foundImage = item.image; } else if (item._webGLRenderStyle === 1) { // this is a SpriteSheet, we can't tell which image we used from the list easily so remove them all! for (i = 0, l = item.spriteSheet._images.length; i < l; i++) { this.releaseTexture(item.spriteSheet._images[i]); } return; } // did we find anything if (foundImage === undefined) { if (this.vocalDebug) { console.log("No associated texture found on release"); } return; } // remove it this._killTextureObject(this._textureDictionary[foundImage._storeID]); foundImage._storeID = undefined; }; /** * Similar to {{#crossLink "releaseTexture"}}{{/crossLink}}, but this function differs by searching for textures to * release. It works by assuming that it can purge any texture which was last used more than "count" draw calls ago. * Because this process is unaware of the objects and whether they may be used on your stage, false positives can * occur. It is recommended to manually manage your memory with {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}}, * however, there are many use cases where this is simpler and error-free. This process is also run by default under * the hood to prevent leaks. To disable it see the {{#crossLink "StageGL/autoPurge:property"}}{{/crossLink}} property. * @method purgeTextures * @param {Number} [count=100] How many renders ago the texture was last used */ p.purgeTextures = function (count) { if (count == undefined){ count = 100; } var dict = this._textureDictionary; var l = dict.length; for (var i= 0; inot update the canvas element's width/height, but * the render surface's instead. This is necessary after manually resizing the canvas element on the DOM to avoid a * up/down scaled render. * @method updateViewport * @param {Integer} width The width of the render surface in pixels. * @param {Integer} height The height of the render surface in pixels. */ p.updateViewport = function (width, height) { this._viewportWidth = width|0; this._viewportHeight = height|0; var gl = this._webGLContext; if (gl) { gl.viewport(0, 0, this._viewportWidth, this._viewportHeight); // WebGL works with a -1,1 space on its screen. It also follows Y-Up // we need to flip the y, scale and then translate the co-ordinates to match this // additionally we offset into they Y so the polygons are inside the camera's "clipping" plane this._projectionMatrix = new Float32Array([ 2 / this._viewportWidth, 0, 0, 0, 0, -2 / this._viewportHeight, 1, 0, 0, 0, 1, 0, -1, 1, 0.1, 0 ]); // create the flipped version for use with render texture flipping // DHG: this would be a slice/clone but some platforms don't offer them for Float32Array this._projectionMatrixFlip = new Float32Array([0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]); this._projectionMatrixFlip.set(this._projectionMatrix); this._projectionMatrixFlip[5] *= -1; this._projectionMatrixFlip[13] *= -1; } }; /** * Fetches the shader compiled and set up to work with the provided filter/object. The shader is compiled on first * use and returned on subsequent calls. * @method getFilterShader * @param {Filter|Object} filter The object which will provide the information needed to construct the filter shader. * @return {WebGLProgram} */ p.getFilterShader = function (filter) { if (!filter) { filter = this; } var gl = this._webGLContext; var targetShader = this._activeShader; if (filter._builtShader) { targetShader = filter._builtShader; if (filter.shaderParamSetup) { gl.useProgram(targetShader); filter.shaderParamSetup(gl, this, targetShader); } } else { try { targetShader = this._fetchShaderProgram( gl, "filter", filter.VTX_SHADER_BODY, filter.FRAG_SHADER_BODY, filter.shaderParamSetup && filter.shaderParamSetup.bind(filter) ); filter._builtShader = targetShader; targetShader._name = filter.toString(); } catch (e) { console && console.log("SHADER SWITCH FAILURE", e); } } return targetShader; }; /** * Returns a base texture that has no image or data loaded. Not intended for loading images. It may return `null` * in some error cases, and trying to use a "null" texture can cause renders to fail. * @method getBaseTexture * @param {uint} [w=1] The width of the texture in pixels, defaults to 1 * @param {uint} [h=1] The height of the texture in pixels, defaults to 1 */ p.getBaseTexture = function (w, h) { var width = Math.ceil(w > 0 ? w : 1) || 1; var height = Math.ceil(h > 0 ? h : 1) || 1; var gl = this._webGLContext; var texture = gl.createTexture(); this.resizeTexture(texture, width, height); this.setTextureParams(gl, false); return texture; }; /** * Resizes a supplied texture element. May generate invalid textures in some error cases such as; when the texture * is too large, when an out of texture memory error occurs, or other error scenarios. Trying to use an invalid texture * can cause renders to hard stop on some devices. Check the WebGL bound texture after running this function. * * NOTE: The supplied texture must have been made with the WebGL "texImage2D" function, all default APIs in StageGL * use this, so this note only matters for library developers and plugins. * * @protected * @method resizeTexture * @param {WebGLTexture} texture The GL Texture to be modified. * @param {uint} [width=1] The width of the texture in pixels, defaults to 1 * @param {uint} [height=1] The height of the texture in pixels, defaults to 1 */ p.resizeTexture = function (texture, width,height) { var gl = this._webGLContext; gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D( gl.TEXTURE_2D, // target 0, // level of detail gl.RGBA, // internal format width, height, 0, // width, height, border (only for array/null sourced textures) gl.RGBA, // format (match internal format) gl.UNSIGNED_BYTE, // type of texture(pixel color depth) null // image data, we can do null because we're doing array data ); texture.width = width; texture.height = height; }; /** * Returns a base texture (see {{#crossLink "StageGL/getBaseTexture"}}{{/crossLink}}) for details. Also includes an * attached and linked render buffer in `texture._frameBuffer`. RenderTextures can be thought of as an internal * canvas on the GPU that can be drawn to. * @method getRenderBufferTexture * @param {Number} w The width of the texture in pixels. * @param {Number} h The height of the texture in pixels. * @return {Texture} the basic texture instance with a render buffer property. */ p.getRenderBufferTexture = function (w, h) { var gl = this._webGLContext; // get the texture var renderTexture = this.getBaseTexture(w, h); if (!renderTexture) { return null; } // get the frame buffer var frameBuffer = gl.createFramebuffer(); if (!frameBuffer) { return null; } // set its width and height for spoofing as an image renderTexture.width = w; renderTexture.height = h; // attach frame buffer to texture and provide cross links to look up each other gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, renderTexture, 0); frameBuffer._renderTexture = renderTexture; renderTexture._frameBuffer = frameBuffer; // these keep track of themselves simply to reduce complexity of some lookup code renderTexture._storeID = this._textureDictionary.length; this._textureDictionary[renderTexture._storeID] = renderTexture; gl.bindFramebuffer(gl.FRAMEBUFFER, null); return renderTexture; }; /** * Common utility function used to apply the correct texture processing parameters for the bound texture. * @method setTextureParams * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {Boolean} [isPOT=false] Marks whether the texture is "Power of Two", this may allow better quality AA. */ p.setTextureParams = function (gl, isPOT) { if (isPOT && this._antialias) { //non POT linear works in some devices, but performance is NOT good, investigate gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); } gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); }; /** * Changes the webGL clear, aka "background" color to the provided value. A transparent clear is recommended, as * non-transparent colours may create undesired boxes around some visuals. * * The clear color will also be used for filters and other "render textures". The stage background will ignore the * transparency value and display a solid color normally. For the stage to recognize and use transparency it must be * created with the transparent flag set to `true` (see {{#crossLink "StageGL/constructor"}}{{/crossLink}})). * * Using "transparent white" to demonstrate, the valid data formats are as follows: *
      *
    • "#FFF"
    • *
    • "#FFFFFF"
    • *
    • "#FFFFFF00"
    • *
    • "rgba(255,255,255,0.0)"
    • *
    * @method setClearColor * @param {String|int} [color=0x00000000] The new color to use as the background */ p.setClearColor = function (color) { var r, g, b, a, output; if (typeof color == "string") { if (color.indexOf("#") == 0) { if (color.length == 4) { color = "#" + color.charAt(1)+color.charAt(1) + color.charAt(2)+color.charAt(2) + color.charAt(3)+color.charAt(3) } r = Number("0x"+color.slice(1, 3))/255; g = Number("0x"+color.slice(3, 5))/255; b = Number("0x"+color.slice(5, 7))/255; a = Number("0x"+color.slice(7, 9))/255; } else if (color.indexOf("rgba(") == 0) { output = color.slice(5, -1).split(","); r = Number(output[0])/255; g = Number(output[1])/255; b = Number(output[2])/255; a = Number(output[3]); } } else { // >>> is an unsigned shift which is what we want as 0x80000000 and up are negative values r = ((color & 0xFF000000) >>> 24)/255; g = ((color & 0x00FF0000) >>> 16)/255; b = ((color & 0x0000FF00) >>> 8)/255; a = (color & 0x000000FF)/255; } this._clearColor.r = r || 0; this._clearColor.g = g || 0; this._clearColor.b = b || 0; this._clearColor.a = a || 0; if (!this._webGLContext) { return; } this._webGLContext.clearColor(this._clearColor.r, this._clearColor.g, this._clearColor.b, this._clearColor.a); }; /** * docced in subclass */ p.toString = function () { return "[StageGL (name="+ this.name +")]"; }; // private methods: /** * Sets up and returns the WebGL context for the canvas. May return undefined in error scenarios. These can include * situations where the canvas element already has a context, 2D or GL. * @param {Canvas} canvas The DOM canvas element to attach to * @param {Object} options The options to be handed into the WebGL object, see WebGL spec * @method _fetchWebGLContext * @protected * @return {WebGLRenderingContext} The WebGL context, may return undefined in error scenarios */ p._fetchWebGLContext = function (canvas, options) { var gl; try { gl = canvas.getContext("webgl", options) || canvas.getContext("experimental-webgl", options); } catch (e) { // don't do anything in catch, null check will handle it } if (!gl) { var msg = "Could not initialize WebGL"; console.error?console.error(msg):console.log(msg); } else { gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; } return gl; }; /** * Create the completed Shader Program from the vertex and fragment shaders. Allows building of custom shaders for * filters. Once compiled, shaders are saved so. If the Shader code requires dynamic alterations re-run this function * to generate a new shader. * @method _fetchShaderProgram * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {String} [shaderName="regular"] Working values: "regular", "override", and "filter". Which type of shader to build. * Filter and override both accept the custom params. Regular and override have all features. Filter is a special case reduced feature shader meant to be over-ridden. * @param {String} [customVTX] Extra vertex shader information to replace a regular draw, see * {{#crossLink "StageGL/COVER_VERTEX_BODY"}}{{/crossLink}} for default and {{#crossLink "Filter"}}{{/crossLink}} for examples. * @param {String} [customFRAG] Extra fragment shader information to replace a regular draw, see * {{#crossLink "StageGL/COVER_FRAGMENT_BODY"}}{{/crossLink}} for default and {{#crossLink "Filter"}}{{/crossLink}} for examples. * @param {Function} [shaderParamSetup] Function to run so custom shader parameters can get applied for the render. * @protected * @return {WebGLProgram} The compiled and linked shader */ p._fetchShaderProgram = function (gl, shaderName, customVTX, customFRAG, shaderParamSetup) { gl.useProgram(null); // safety to avoid collisions // build the correct shader string out of the right headers and bodies var targetFrag, targetVtx; switch (shaderName) { case "filter": targetVtx = StageGL.COVER_VERTEX_HEADER + (customVTX || StageGL.COVER_VERTEX_BODY); targetFrag = StageGL.COVER_FRAGMENT_HEADER + (customFRAG || StageGL.COVER_FRAGMENT_BODY); break; case "particle": //TODO targetVtx = StageGL.REGULAR_VERTEX_HEADER + StageGL.PARTICLE_VERTEX_BODY; targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + StageGL.PARTICLE_FRAGMENT_BODY; break; case "override": targetVtx = StageGL.REGULAR_VERTEX_HEADER + (customVTX || StageGL.REGULAR_VERTEX_BODY); targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + (customFRAG || StageGL.REGULAR_FRAGMENT_BODY); break; case "regular": default: targetVtx = StageGL.REGULAR_VERTEX_HEADER + StageGL.REGULAR_VERTEX_BODY; targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + StageGL.REGULAR_FRAGMENT_BODY; break; } // create the separate vars var vertexShader = this._createShader(gl, gl.VERTEX_SHADER, targetVtx); var fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, targetFrag); // link them together var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); shaderProgram._type = shaderName; // check compile status if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { gl.useProgram(this._activeShader); throw gl.getProgramInfoLog(shaderProgram); } // set up the parameters on the shader gl.useProgram(shaderProgram); switch (shaderName) { case "filter": // get the places in memory the shader is stored so we can feed information into them // then save it off on the shader because it's so tied to the shader itself shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.uvPositionAttribute = gl.getAttribLocation(shaderProgram, "uvPosition"); gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute); shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); gl.uniform1i(shaderProgram.samplerUniform, 0); shaderProgram.uprightUniform = gl.getUniformLocation(shaderProgram, "uUpright"); gl.uniform1f(shaderProgram.uprightUniform, 0); // if there's some custom attributes be sure to hook them up if (shaderParamSetup) { shaderParamSetup(gl, this, shaderProgram); } break; case "override": case "particle": case "regular": default: // get the places in memory the shader is stored so we can feed information into them // then save it off on the shader because it's so tied to the shader itself shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.uvPositionAttribute = gl.getAttribLocation(shaderProgram, "uvPosition"); gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute); shaderProgram.textureIndexAttribute = gl.getAttribLocation(shaderProgram, "textureIndex"); gl.enableVertexAttribArray(shaderProgram.textureIndexAttribute); shaderProgram.alphaAttribute = gl.getAttribLocation(shaderProgram, "objectAlpha"); gl.enableVertexAttribArray(shaderProgram.alphaAttribute); var samplers = []; for (var i = 0; i < this._batchTextureCount; i++) { samplers[i] = i; } shaderProgram.samplerData = samplers; shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); gl.uniform1iv(shaderProgram.samplerUniform, samplers); shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "pMatrix"); break; } gl.useProgram(this._activeShader); return shaderProgram; }; /** * Creates a shader from the specified string replacing templates. Template items are defined via `{{` `key` `}}``. * @method _createShader * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {Number} type The type of shader to create. gl.VERTEX_SHADER | gl.FRAGMENT_SHADER * @param {String} str The definition for the shader. * @return {WebGLShader} * @protected */ p._createShader = function (gl, type, str) { // inject the static number str = str.replace(/{{count}}/g, this._batchTextureCount); // resolve issue with no dynamic samplers by creating correct samplers in if else chain // TODO: WebGL 2.0 does not need this support var insert = ""; for (var i = 1; i gl.MAX_TEXTURE_SIZE || image.height > gl.MAX_TEXTURE_SIZE){ console && console.error("Oversized Texture: "+ image.width+"x"+image.height +" vs "+ gl.MAX_TEXTURE_SIZE +"max"); } } }; /** * Adds the texture to a spot in the current batch, forcing a draw if no spots are free. * @method _insertTextureInBatch * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {WebGLTexture} texture The texture to be inserted. * @protected */ p._insertTextureInBatch = function (gl, texture) { // if it wasn't used last batch if (this._batchTextures[texture._activeIndex] !== texture) { // we've got to find it a a spot. var found = -1; var start = (this._lastTextureInsert+1) % this._batchTextureCount; var look = start; do { if (this._batchTextures[look]._batchID != this._batchID && !this._slotBlacklist[look]) { found = look; break; } look = (look+1) % this._batchTextureCount; } while (look !== start); // we couldn't find anywhere for it go, meaning we're maxed out if (found === -1) { this.batchReason = "textureOverflow"; this._drawBuffers(gl); // <------------------------------------------------------------------------ this.batchCardCount = 0; found = start; } // lets put it into that spot this._batchTextures[found] = texture; texture._activeIndex = found; var image = texture._imageData; if (image && image._invalid && texture._drawID !== undefined) { this._updateTextureImageData(gl, image); } else { gl.activeTexture(gl.TEXTURE0 + found); gl.bindTexture(gl.TEXTURE_2D, texture); this.setTextureParams(gl); } this._lastTextureInsert = found; } else { var image = texture._imageData; if (texture._storeID != undefined && image && image._invalid) { this._updateTextureImageData(gl, image); } } texture._drawID = this._drawID; texture._batchID = this._batchID; }; /** * Remove and clean the texture, expects a texture and is inflexible. Mostly for internal use, recommended to call * {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} instead as it will call this with the correct texture object(s). * Note: Testing shows this may not happen immediately, have to wait frames for WebGL to have actually adjust memory. * @method _killTextureObject * @param {Texture} tex The texture to be cleaned out * @protected */ p._killTextureObject = function (tex) { if (!tex) { return; } var gl = this._webGLContext; // remove linkage if (tex._storeID !== undefined && tex._storeID >= 0) { this._textureDictionary[tex._storeID] = undefined; for (var n in this._textureIDs) { if (this._textureIDs[n] == tex._storeID) { delete this._textureIDs[n]; } } if(tex._imageData) { tex._imageData._storeID = undefined; } tex._imageData = tex._storeID = undefined; } // make sure to drop it out of an active slot if (tex._activeIndex !== undefined && this._batchTextures[tex._activeIndex] === tex) { this._batchTextures[tex._activeIndex] = this._baseTextures[tex._activeIndex]; } // remove buffers if present try { if (tex._frameBuffer) { gl.deleteFramebuffer(tex._frameBuffer); } tex._frameBuffer = undefined; } catch(e) { /* suppress delete errors because it's already gone or didn't need deleting probably */ if (this.vocalDebug) { console.log(e); } } // remove entry try { gl.deleteTexture(tex); } catch(e) { /* suppress delete errors because it's already gone or didn't need deleting probably */ if (this.vocalDebug) { console.log(e); } } }; /** * Store or restore current batch textures into a backup array * @method _backupBatchTextures * @param {Boolean} restore Perform a restore instead of a store. * @param {Array} [target=this._backupTextures] Where to perform the backup, defaults to internal backup. * @protected */ p._backupBatchTextures = function (restore, target) { var gl = this._webGLContext; if (!this._backupTextures) { this._backupTextures = []; } if (target === undefined) { target = this._backupTextures; } for (var i=0; i 0) { this._drawBuffers(gl); } this._isDrawing++; this._drawID++; this.batchCardCount = 0; this.depth = 0; this._appendToBatchGroup(sceneGraph, gl, new createjs.Matrix2D(), this.alpha, ignoreCache); this.batchReason = "drawFinish"; this._drawBuffers(gl); // <-------------------------------------------------------- this._isDrawing--; }; /** * Perform the drawing process to fill a specific cache texture, including applying filters. * @method _cacheDraw * @param {DisplayObject} target The object we're drawing into the cache. For example, used for drawing the cache * (to prevent it from simply drawing an existing cache back into itself). * @param {Array} filters The filters we're drawing into cache. * @param {BitmapCache} manager The BitmapCache instance looking after the cache * @protected */ p._cacheDraw = function (gl, target, filters, manager) { /* Implicitly there are 4 modes to this function: filtered-sameContext, filtered-uniqueContext, sameContext, uniqueContext. Each situation must be handled slightly differently as 'sameContext' or 'uniqueContext' define how the output works, one drawing directly into the main context and the other drawing into a stored renderTexture respectively. When the draw is a 'filtered' draw, the filters are applied sequentially and will draw into saved textures repeatedly. Once the final filter is done the final output is treated depending upon whether it is a same or unique context. The internal complexity comes from reducing over-draw, shared code, and issues like textures needing to be flipped sometimes when written to render textures. */ var renderTexture; var shaderBackup = this._activeShader; var blackListBackup = this._slotBlacklist; var lastTextureSlot = this._maxTextureSlots-1; var wBackup = this._viewportWidth, hBackup = this._viewportHeight; // protect the last slot so that we have somewhere to bind the renderTextures so it doesn't get upset this.protectTextureSlot(lastTextureSlot, true); // create offset container for drawing item var mtx = target.getMatrix(); mtx = mtx.clone(); mtx.scale(1/manager.scale, 1/manager.scale); mtx = mtx.invert(); mtx.translate(-manager.offX/manager.scale*target.scaleX, -manager.offY/manager.scale*target.scaleY); var container = this._cacheContainer; container.children = [target]; container.transformMatrix = mtx; this._backupBatchTextures(false); if (filters && filters.length) { this._drawFilters(target, filters, manager); } else { // is this for another stage or mine? if (this.isCacheControlled) { // draw item to canvas I -> C gl.clear(gl.COLOR_BUFFER_BIT); this._batchDraw(container, gl, true); } else { gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); target.cacheCanvas = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); renderTexture = target.cacheCanvas; // draw item to render texture I -> T gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); this.updateViewport(manager._drawWidth, manager._drawHeight); this._projectionMatrix = this._projectionMatrixFlip; gl.clear(gl.COLOR_BUFFER_BIT); this._batchDraw(container, gl, true); gl.bindFramebuffer(gl.FRAMEBUFFER, null); this.updateViewport(wBackup, hBackup); } } this._backupBatchTextures(true); this.protectTextureSlot(lastTextureSlot, false); this._activeShader = shaderBackup; this._slotBlacklist = blackListBackup; }; /** * Sub portion of _cacheDraw, split off for readability. Do not call independently. * @method _drawFilters * @param {DisplayObject} target The object we're drawing with a filter. * @param {Array} filters The filters we're drawing into cache. * @param {BitmapCache} manager The BitmapCache instance looking after the cache */ p._drawFilters = function (target, filters, manager) { var gl = this._webGLContext; var renderTexture; var lastTextureSlot = this._maxTextureSlots-1; var wBackup = this._viewportWidth, hBackup = this._viewportHeight; var container = this._cacheContainer; var filterCount = filters.length; // we don't know which texture slot we're dealing with previously and we need one out of the way // once we're using that slot activate it so when we make and bind our RenderTexture it's safe there gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); renderTexture = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); // draw item to render texture I -> T gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); this.updateViewport(manager._drawWidth, manager._drawHeight); gl.clear(gl.COLOR_BUFFER_BIT); this._batchDraw(container, gl, true); // bind the result texture to slot 0 as all filters and cover draws assume original content is in slot 0 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, renderTexture); this.setTextureParams(gl); var flipY = false; var i = 0, filter = filters[i]; do { // this is safe because we wouldn't be in apply filters without a filter count of at least 1 // swap to correct shader this._activeShader = this.getFilterShader(filter); if (!this._activeShader) { continue; } // now the old result is stored in slot 0, make a new render texture gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); renderTexture = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); // draw result to render texture R -> T gl.viewport(0, 0, manager._drawWidth, manager._drawHeight); gl.clear(gl.COLOR_BUFFER_BIT); this._drawCover(gl, flipY); // bind the result texture to slot 0 as all filters and cover draws assume original content is in slot 0 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, renderTexture); this.setTextureParams(gl); // use flipping to keep things upright, things already cancel out on a single filter // this needs to be here as multiPass is not accurate to _this_ frame until after shader acquisition if (filterCount > 1 || filters[0]._multiPass) { flipY = !flipY; } // work through the multipass if it's there, otherwise move on filter = filter._multiPass !== null ? filter._multiPass : filters[++i]; } while (filter); // is this for another stage or mine if (this.isCacheControlled) { gl.bindFramebuffer(gl.FRAMEBUFFER, null); this.updateViewport(wBackup, hBackup); // draw result to canvas R -> C this._activeShader = this.getFilterShader(this); gl.clear(gl.COLOR_BUFFER_BIT); this._drawCover(gl, flipY); } else { //TODO: DHG: this is less than ideal. A flipped initial render for this circumstance might help. Adjust the perspective matrix? if (flipY) { gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); renderTexture = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); this._activeShader = this.getFilterShader(this); gl.viewport(0, 0, manager._drawWidth, manager._drawHeight); gl.clear(gl.COLOR_BUFFER_BIT); this._drawCover(gl, !flipY); } gl.bindFramebuffer(gl.FRAMEBUFFER, null); this.updateViewport(wBackup, hBackup); // make sure the last texture is the active thing to draw target.cacheCanvas = renderTexture; } }; /** * Add all the contents of a container to the pending buffers, called recursively on each container. This may * trigger a draw if a buffer runs out of space. This is the main workforce of the render loop. * @method _appendToBatchGroup * @param {Container} container The {{#crossLink "Container"}}{{/crossLink}} that contains everything to be drawn. * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {Matrix2D} concatMtx The effective (concatenated) transformation matrix when beginning this container * @param {Number} concatAlpha The effective (concatenated) alpha when beginning this container * @param {Boolean} ignoreCache Don't use an element's cache during this draw * @protected */ p._appendToBatchGroup = function (container, gl, concatMtx, concatAlpha, ignoreCache) { // sort out shared properties if (!container._glMtx) { container._glMtx = new createjs.Matrix2D(); } var cMtx = container._glMtx; cMtx.copy(concatMtx); if (container.transformMatrix) { cMtx.appendMatrix(container.transformMatrix); } else { cMtx.appendTransform( container.x, container.y, container.scaleX, container.scaleY, container.rotation, container.skewX, container.skewY, container.regX, container.regY ); } // sub components of figuring out the position an object holds var subL, subT, subR, subB; // actually apply its data to the buffers var l = container.children.length; for (var i = 0; i < l; i++) { var item = container.children[i]; if (!(item.visible && concatAlpha)) { continue; } if (!item.cacheCanvas || ignoreCache) { if (item._updateState){ item._updateState(); } if (item.children) { this._appendToBatchGroup(item, gl, cMtx, item.alpha * concatAlpha); continue; } } // check for overflowing batch, if yes then force a render // TODO: DHG: consider making this polygon count dependant for things like vector draws if (this.batchCardCount+1 > this._maxCardsPerBatch) { this.batchReason = "vertexOverflow"; this._drawBuffers(gl); // <------------------------------------------------------------ this.batchCardCount = 0; } // keep track of concatenated position if (!item._glMtx) { item._glMtx = new createjs.Matrix2D(); } var iMtx = item._glMtx; iMtx.copy(cMtx); if (item.transformMatrix) { iMtx.appendMatrix(item.transformMatrix); } else { iMtx.appendTransform( item.x, item.y, item.scaleX, item.scaleY, item.rotation, item.skewX, item.skewY, item.regX, item.regY ); } var uvRect, texIndex, image, frame, texture, src; var useCache = item.cacheCanvas && !ignoreCache; if (item._webGLRenderStyle === 2 || useCache) { // BITMAP / Cached Canvas image = (ignoreCache?false:item.cacheCanvas) || item.image; } else if (item._webGLRenderStyle === 1) { // SPRITE frame = item.spriteSheet.getFrame(item.currentFrame); //TODO: Faster way? if (frame === null) { continue; } image = frame.image; } else { // MISC (DOM objects render themselves later) continue; } var uvs = this._uvs; var vertices = this._vertices; var texI = this._indices; var alphas = this._alphas; // calculate texture if (!image) { continue; } if (image._storeID === undefined) { // this texture is new to us so load it and add it to the batch texture = this._loadTextureImage(gl, image); this._insertTextureInBatch(gl, texture); } else { // fetch the texture (render textures know how to look themselves up to simplify this logic) texture = this._textureDictionary[image._storeID]; if (!texture){ if (this.vocalDebug){ console.log("Texture should not be looked up while not being stored."); } continue; } // put it in the batch if needed if (texture._batchID !== this._batchID) { this._insertTextureInBatch(gl, texture); } } texIndex = texture._activeIndex; if (item._webGLRenderStyle === 2 || useCache) { // BITMAP / Cached Canvas if (!useCache && item.sourceRect) { // calculate uvs if (!item._uvRect) { item._uvRect = {}; } src = item.sourceRect; uvRect = item._uvRect; uvRect.t = (src.y)/image.height; uvRect.l = (src.x)/image.width; uvRect.b = (src.y + src.height)/image.height; uvRect.r = (src.x + src.width)/image.width; // calculate vertices subL = 0; subT = 0; subR = src.width+subL; subB = src.height+subT; } else { // calculate uvs uvRect = StageGL.UV_RECT; // calculate vertices if (useCache) { src = item.bitmapCache; subL = src.x+(src._filterOffX/src.scale); subT = src.y+(src._filterOffY/src.scale); subR = (src._drawWidth/src.scale)+subL; subB = (src._drawHeight/src.scale)+subT; } else { subL = 0; subT = 0; subR = image.width+subL; subB = image.height+subT; } } } else if (item._webGLRenderStyle === 1) { // SPRITE var rect = frame.rect; // calculate uvs uvRect = frame.uvRect; if (!uvRect) { uvRect = StageGL.buildUVRects(item.spriteSheet, item.currentFrame, false); } // calculate vertices subL = -frame.regX; subT = -frame.regY; subR = rect.width-frame.regX; subB = rect.height-frame.regY; } // These must be calculated here else a forced draw might happen after they're set var offV1 = this.batchCardCount*StageGL.INDICIES_PER_CARD; // offset for 1 component vectors var offV2 = offV1*2; // offset for 2 component vectors //DHG: See Matrix2D.transformPoint for why this math specifically // apply vertices vertices[offV2] = subL *iMtx.a + subT *iMtx.c +iMtx.tx; vertices[offV2+1] = subL *iMtx.b + subT *iMtx.d +iMtx.ty; vertices[offV2+2] = subL *iMtx.a + subB *iMtx.c +iMtx.tx; vertices[offV2+3] = subL *iMtx.b + subB *iMtx.d +iMtx.ty; vertices[offV2+4] = subR *iMtx.a + subT *iMtx.c +iMtx.tx; vertices[offV2+5] = subR *iMtx.b + subT *iMtx.d +iMtx.ty; vertices[offV2+6] = vertices[offV2+2]; vertices[offV2+7] = vertices[offV2+3]; vertices[offV2+8] = vertices[offV2+4]; vertices[offV2+9] = vertices[offV2+5]; vertices[offV2+10] = subR *iMtx.a + subB *iMtx.c +iMtx.tx; vertices[offV2+11] = subR *iMtx.b + subB *iMtx.d +iMtx.ty; // apply uvs uvs[offV2] = uvRect.l; uvs[offV2+1] = uvRect.t; uvs[offV2+2] = uvRect.l; uvs[offV2+3] = uvRect.b; uvs[offV2+4] = uvRect.r; uvs[offV2+5] = uvRect.t; uvs[offV2+6] = uvRect.l; uvs[offV2+7] = uvRect.b; uvs[offV2+8] = uvRect.r; uvs[offV2+9] = uvRect.t; uvs[offV2+10] = uvRect.r; uvs[offV2+11] = uvRect.b; // apply texture texI[offV1] = texI[offV1+1] = texI[offV1+2] = texI[offV1+3] = texI[offV1+4] = texI[offV1+5] = texIndex; // apply alpha alphas[offV1] = alphas[offV1+1] = alphas[offV1+2] = alphas[offV1+3] = alphas[offV1+4] = alphas[offV1+5] = item.alpha * concatAlpha; this.batchCardCount++; } }; /** * Draws all the currently defined cards in the buffer to the render surface. * @method _drawBuffers * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @protected */ p._drawBuffers = function (gl) { if (this.batchCardCount <= 0) { return; } // prevents error logs on stages filled with un-renederable content. if (this.vocalDebug) { console.log("Draw["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason); } var shaderProgram = this._activeShader; var vertexPositionBuffer = this._vertexPositionBuffer; var textureIndexBuffer = this._textureIndexBuffer; var uvPositionBuffer = this._uvPositionBuffer; var alphaBuffer = this._alphaBuffer; gl.useProgram(shaderProgram); gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._vertices); gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer); gl.vertexAttribPointer(shaderProgram.textureIndexAttribute, textureIndexBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._indices); gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer); gl.vertexAttribPointer(shaderProgram.uvPositionAttribute, uvPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._uvs); gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer); gl.vertexAttribPointer(shaderProgram.alphaAttribute, alphaBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._alphas); gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, gl.FALSE, this._projectionMatrix); for (var i = 0; i < this._batchTextureCount; i++) { var texture = this._batchTextures[i]; gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, texture); this.setTextureParams(gl, texture.isPOT); } gl.drawArrays(gl.TRIANGLES, 0, this.batchCardCount*StageGL.INDICIES_PER_CARD); this._batchID++; }; /** * Draws a card that covers the entire render surface. Mainly used for filters. * @method _drawBuffers * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {Boolean} flipY Covers are used for things like RenderTextures and because of 3D vs Canvas space this can * end up meaning the `y` space sometimes requires flipping in the render. * @protected */ p._drawCover = function (gl, flipY) { if (this._isDrawing > 0) { this._drawBuffers(gl); } if (this.vocalDebug) { console.log("Draw["+ this._drawID +":"+ this._batchID +"] : "+ "Cover"); } var shaderProgram = this._activeShader; var vertexPositionBuffer = this._vertexPositionBuffer; var uvPositionBuffer = this._uvPositionBuffer; gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(shaderProgram); gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bufferSubData(gl.ARRAY_BUFFER, 0, StageGL.COVER_VERT); gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer); gl.vertexAttribPointer(shaderProgram.uvPositionAttribute, uvPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bufferSubData(gl.ARRAY_BUFFER, 0, flipY?StageGL.COVER_UV_FLIP:StageGL.COVER_UV); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.uniform1f(shaderProgram.uprightUniform, flipY?0:1); gl.drawArrays(gl.TRIANGLES, 0, StageGL.INDICIES_PER_CARD); }; createjs.StageGL = createjs.promote(StageGL, "Stage"); }()); //############################################################################## // Bitmap.js //############################################################################## (function() { /** * A Bitmap represents an Image, Canvas, or Video in the display list. A Bitmap can be instantiated using an existing * HTML element, or a string. * *

    Example

    * * var bitmap = new createjs.Bitmap("imagePath.jpg"); * * Notes: *
      *
    1. When using a video source that may loop or seek, use a {{#crossLink "VideoBuffer"}}{{/crossLink}} object to * prevent blinking / flashing. *
    2. When a string path or image tag that is not yet loaded is used, the stage may need to be redrawn before it * will be displayed.
    3. *
    4. Bitmaps with an SVG source currently will not respect an alpha value other than 0 or 1. To get around this, * the Bitmap can be cached.
    5. *
    6. Bitmaps with an SVG source will taint the canvas with cross-origin data, which prevents interactivity. This * happens in all browsers except recent Firefox builds.
    7. *
    8. Images loaded cross-origin will throw cross-origin security errors when interacted with using a mouse, using * methods such as `getObjectUnderPoint`, or using filters, or caching. You can get around this by setting * `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";`
    9. *
    * * @class Bitmap * @extends DisplayObject * @constructor * @param {CanvasImageSource | String | Object} imageOrUri The source image to display. This can be a CanvasImageSource * (image, video, canvas), an object with a `getImage` method that returns a CanvasImageSource, or a string URL to an image. * If the latter, a new Image instance with the URL as its src will be used. **/ function Bitmap(imageOrUri) { this.DisplayObject_constructor(); // public properties: /** * The source image to display. This can be a CanvasImageSource * (image, video, canvas), an object with a `getImage` method that returns a CanvasImageSource, or a string URL to an image. * If the latter, a new Image instance with the URL as its src will be used. * @property image * @type CanvasImageSource | Object **/ if (typeof imageOrUri == "string") { this.image = document.createElement("img"); this.image.src = imageOrUri; } else { this.image = imageOrUri; } /** * Specifies an area of the source image to draw. If omitted, the whole image will be drawn. * Notes: *
      *
    • that video sources must have a width / height set to work correctly with `sourceRect`
    • *
    • Cached objects will ignore the `sourceRect` property
    • *
    * @property sourceRect * @type Rectangle * @default null */ this.sourceRect = null; // private properties: /** * Docced in superclass. */ this._webGLRenderStyle = createjs.DisplayObject._StageGL_BITMAP; } var p = createjs.extend(Bitmap, createjs.DisplayObject); // public methods: /** * Constructor alias for backwards compatibility. This method will be removed in future versions. * Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. * @method initialize * @deprecated in favour of `createjs.promote()` **/ p.initialize = Bitmap; // TODO: deprecated. /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { var image = this.image; var hasContent = this.cacheCanvas || (image && (image.naturalWidth || image.getContext || image.readyState >= 2)); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). * @return {Boolean} **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } var img = this.image, rect = this.sourceRect; if (img.getImage) { img = img.getImage(); } if (!img) { return true; } if (rect) { // some browsers choke on out of bound values, so we'll fix them: var x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height, x = 0, y = 0, w = img.width, h = img.height; if (x1 < 0) { x -= x1; x1 = 0; } if (x2 > w) { x2 = w; } if (y1 < 0) { y -= y1; y1 = 0; } if (y2 > h) { y2 = h; } ctx.drawImage(img, x1, y1, x2-x1, y2-y1, x, y, x2-x1, y2-y1); } else { ctx.drawImage(img, 0, 0); } return true; }; //Note, the doc sections below document using the specified APIs (from DisplayObject) from //Bitmap. This is why they have no method implementations. /** * Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. * You should not cache Bitmap instances as it can degrade performance. * * However: If you want to use a filter on a Bitmap, you MUST cache it, or it will not work. * To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}} * method. * @method cache **/ /** * Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. * You should not cache Bitmap instances as it can degrade performance. * * However: If you want to use a filter on a Bitmap, you MUST cache it, or it will not work. * To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}} * method. * @method updateCache **/ /** * Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. * You should not cache Bitmap instances as it can degrade performance. * * However: If you want to use a filter on a Bitmap, you MUST cache it, or it will not work. * To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}} * method. * @method uncache **/ /** * Docced in superclass. */ p.getBounds = function() { var rect = this.DisplayObject_getBounds(); if (rect) { return rect; } var image = this.image, o = this.sourceRect || image; var hasContent = (image && (image.naturalWidth || image.getContext || image.readyState >= 2)); return hasContent ? this._rectangle.setValues(0, 0, o.width, o.height) : null; }; /** * Returns a clone of the Bitmap instance. * @method clone * @param {Boolean} node Whether the underlying dom element should be cloned as well. * @return {Bitmap} a clone of the Bitmap instance. **/ p.clone = function(node) { var image = this.image; if(image && node){ image = image.cloneNode(); } var o = new Bitmap(image); if (this.sourceRect) { o.sourceRect = this.sourceRect.clone(); } this._cloneProps(o); return o; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Bitmap (name="+ this.name +")]"; }; createjs.Bitmap = createjs.promote(Bitmap, "DisplayObject"); }()); //############################################################################## // Sprite.js //############################################################################## (function() { "use strict"; // constructor: /** * Displays a frame or sequence of frames (ie. an animation) from a SpriteSheet instance. A sprite sheet is a series of * images (usually animation frames) combined into a single image. For example, an animation consisting of 8 100x100 * images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames, * play frames as an animation, and even sequence animations together. * * See the {{#crossLink "SpriteSheet"}}{{/crossLink}} class for more information on setting up frames and animations. * *

    Example

    * * var instance = new createjs.Sprite(spriteSheet); * instance.gotoAndStop("frameName"); * * Until {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} is called, * only the first defined frame defined in the sprite sheet will be displayed. * * @class Sprite * @extends DisplayObject * @constructor * @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame * dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. * @param {String|Number} [frameOrAnimation] The frame number or animation to play initially. **/ function Sprite(spriteSheet, frameOrAnimation) { this.DisplayObject_constructor(); // public properties: /** * The frame index that will be drawn when draw is called. Note that with some {{#crossLink "SpriteSheet"}}{{/crossLink}} * definitions, this will advance non-sequentially. This will always be an integer value. * @property currentFrame * @type {Number} * @default 0 * @readonly **/ this.currentFrame = 0; /** * Returns the name of the currently playing animation. * @property currentAnimation * @type {String} * @final * @readonly **/ this.currentAnimation = null; /** * Prevents the animation from advancing each tick automatically. For example, you could create a sprite * sheet of icons, set paused to true, and display the appropriate icon by setting currentFrame. * @property paused * @type {Boolean} * @default false **/ this.paused = true; /** * The SpriteSheet instance to play back. This includes the source image, frame dimensions, and frame * data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. * @property spriteSheet * @type {SpriteSheet} * @readonly **/ this.spriteSheet = spriteSheet; /** * Specifies the current frame index within the currently playing animation. When playing normally, this will increase * from 0 to n-1, where n is the number of frames in the current animation. * * This could be a non-integer value if * using time-based playback (see {{#crossLink "Sprite/framerate"}}{{/crossLink}}, or if the animation's speed is * not an integer. * @property currentAnimationFrame * @type {Number} * @default 0 **/ this.currentAnimationFrame = 0; /** * By default Sprite instances advance one frame per tick. Specifying a framerate for the Sprite (or its related * SpriteSheet) will cause it to advance based on elapsed time between ticks as appropriate to maintain the target * framerate. * * For example, if a Sprite with a framerate of 10 is placed on a Stage being updated at 40fps, then the Sprite will * advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will * vary slightly between frames. * * This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being * passed into {{#crossLink "Stage/update"}}{{/crossLink}}. * @property framerate * @type {Number} * @default 0 **/ this.framerate = 0; // private properties: /** * Current animation object. * @property _animation * @protected * @type {Object} * @default null **/ this._animation = null; /** * Current frame index. * @property _currentFrame * @protected * @type {Number} * @default null **/ this._currentFrame = null; /** * Skips the next auto advance. Used by gotoAndPlay to avoid immediately jumping to the next frame * @property _skipAdvance * @protected * @type {Boolean} * @default false **/ this._skipAdvance = false; /** * Docced in superclass. */ this._webGLRenderStyle = createjs.DisplayObject._StageGL_SPRITE; if (frameOrAnimation != null) { this.gotoAndPlay(frameOrAnimation); } } var p = createjs.extend(Sprite, createjs.DisplayObject); /** * Constructor alias for backwards compatibility. This method will be removed in future versions. * Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. * @method initialize * @deprecated in favour of `createjs.promote()` **/ p.initialize = Sprite; // TODO: Deprecated. This is for backwards support of Flash/Animate spritesheet export. // events: /** * Dispatched when an animation reaches its ends. * @event animationend * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {String} name The name of the animation that just ended. * @param {String} next The name of the next animation that will be played, or null. This will be the same as name if the animation is looping. * @since 0.6.0 */ /** * Dispatched any time the current frame changes. For example, this could be due to automatic advancement on a tick, * or calling gotoAndPlay() or gotoAndStop(). * @event change * @param {Object} target The object that dispatched the event. * @param {String} type The event type. */ // public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { var hasContent = this.cacheCanvas || this.spriteSheet.complete; return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } this._normalizeFrame(); var o = this.spriteSheet.getFrame(this._currentFrame|0); if (!o) { return false; } var rect = o.rect; if (rect.width && rect.height) { ctx.drawImage(o.image, rect.x, rect.y, rect.width, rect.height, -o.regX, -o.regY, rect.width, rect.height); } return true; }; //Note, the doc sections below document using the specified APIs (from DisplayObject) from //Bitmap. This is why they have no method implementations. /** * Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. * You should not cache Sprite instances as it can degrade performance. * @method cache **/ /** * Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. * You should not cache Sprite instances as it can degrade performance. * @method updateCache **/ /** * Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. * You should not cache Sprite instances as it can degrade performance. * @method uncache **/ /** * Play (unpause) the current animation. The Sprite will be paused if either {{#crossLink "Sprite/stop"}}{{/crossLink}} * or {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} is called. Single frame animations will remain * unchanged. * @method play **/ p.play = function() { this.paused = false; }; /** * Stop playing a running animation. The Sprite will be playing if {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} * is called. Note that calling {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} or {{#crossLink "Sprite/play"}}{{/crossLink}} * will resume playback. * @method stop **/ p.stop = function() { this.paused = true; }; /** * Sets paused to false and plays the specified animation name, named frame, or frame number. * @method gotoAndPlay * @param {String|Number} frameOrAnimation The frame number or animation name that the playhead should move to * and begin playing. **/ p.gotoAndPlay = function(frameOrAnimation) { this.paused = false; this._skipAdvance = true; this._goto(frameOrAnimation); }; /** * Sets paused to true and seeks to the specified animation name, named frame, or frame number. * @method gotoAndStop * @param {String|Number} frameOrAnimation The frame number or animation name that the playhead should move to * and stop. **/ p.gotoAndStop = function(frameOrAnimation) { this.paused = true; this._goto(frameOrAnimation); }; /** * Advances the playhead. This occurs automatically each tick by default. * @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set on the Sprite * or its SpriteSheet. * @method advance */ p.advance = function(time) { var fps = this.framerate || this.spriteSheet.framerate; var t = (fps && time != null) ? time/(1000/fps) : 1; this._normalizeFrame(t); }; /** * Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the current frame relative to * the origin. For example, a 90 x 70 frame with regX=50 and regY=40 would return a * rectangle with [x=-50, y=-40, width=90, height=70]. This ignores transformations on the display object. * * Also see the SpriteSheet {{#crossLink "SpriteSheet/getFrameBounds"}}{{/crossLink}} method. * @method getBounds * @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully * loaded. **/ p.getBounds = function() { // TODO: should this normalizeFrame? return this.DisplayObject_getBounds() || this.spriteSheet.getFrameBounds(this.currentFrame, this._rectangle); }; /** * Returns a clone of the Sprite instance. Note that the same SpriteSheet is shared between cloned * instances. * @method clone * @return {Sprite} a clone of the Sprite instance. **/ p.clone = function() { return this._cloneProps(new Sprite(this.spriteSheet)); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Sprite (name="+ this.name +")]"; }; // private methods: /** * @method _cloneProps * @param {Sprite} o * @return {Sprite} o * @protected **/ p._cloneProps = function(o) { this.DisplayObject__cloneProps(o); o.currentFrame = this.currentFrame; o.currentAnimation = this.currentAnimation; o.paused = this.paused; o.currentAnimationFrame = this.currentAnimationFrame; o.framerate = this.framerate; o._animation = this._animation; o._currentFrame = this._currentFrame; o._skipAdvance = this._skipAdvance; return o; }; /** * Advances the currentFrame if paused is not true. This is called automatically when the {{#crossLink "Stage"}}{{/crossLink}} * ticks. * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * @protected * @method _tick **/ p._tick = function(evtObj) { if (!this.paused) { if (!this._skipAdvance) { this.advance(evtObj&&evtObj.delta); } this._skipAdvance = false; } this.DisplayObject__tick(evtObj); }; /** * Normalizes the current frame, advancing animations and dispatching callbacks as appropriate. * @protected * @method _normalizeFrame **/ p._normalizeFrame = function(frameDelta) { frameDelta = frameDelta || 0; var animation = this._animation; var paused = this.paused; var frame = this._currentFrame; var l; if (animation) { var speed = animation.speed || 1; var animFrame = this.currentAnimationFrame; l = animation.frames.length; if (animFrame + frameDelta * speed >= l) { var next = animation.next; if (this._dispatchAnimationEnd(animation, frame, paused, next, l - 1)) { // something changed in the event stack, so we shouldn't make any more changes here. return; } else if (next) { // sequence. Automatically calls _normalizeFrame again with the remaining frames. return this._goto(next, frameDelta - (l - animFrame) / speed); } else { // end. this.paused = true; animFrame = animation.frames.length - 1; } } else { animFrame += frameDelta * speed; } this.currentAnimationFrame = animFrame; this._currentFrame = animation.frames[animFrame | 0] } else { frame = (this._currentFrame += frameDelta); l = this.spriteSheet.getNumFrames(); if (frame >= l && l > 0) { if (!this._dispatchAnimationEnd(animation, frame, paused, l - 1)) { // looped. if ((this._currentFrame -= l) >= l) { return this._normalizeFrame(); } } } } frame = this._currentFrame | 0; if (this.currentFrame != frame) { this.currentFrame = frame; this.dispatchEvent("change"); } }; /** * Dispatches the "animationend" event. Returns true if a handler changed the animation (ex. calling {{#crossLink "Sprite/stop"}}{{/crossLink}}, * {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, etc.) * @property _dispatchAnimationEnd * @private * @type {Function} **/ p._dispatchAnimationEnd = function(animation, frame, paused, next, end) { var name = animation ? animation.name : null; if (this.hasEventListener("animationend")) { var evt = new createjs.Event("animationend"); evt.name = name; evt.next = next; this.dispatchEvent(evt); } // did the animation get changed in the event stack?: var changed = (this._animation != animation || this._currentFrame != frame); // if the animation hasn't changed, but the sprite was paused, then we want to stick to the last frame: if (!changed && !paused && this.paused) { this.currentAnimationFrame = end; changed = true; } return changed; }; /** * Moves the playhead to the specified frame number or animation. * @method _goto * @param {String|Number} frameOrAnimation The frame number or animation that the playhead should move to. * @param {Boolean} [frame] The frame of the animation to go to. Defaults to 0. * @protected **/ p._goto = function(frameOrAnimation, frame) { this.currentAnimationFrame = 0; if (isNaN(frameOrAnimation)) { var data = this.spriteSheet.getAnimation(frameOrAnimation); if (data) { this._animation = data; this.currentAnimation = frameOrAnimation; this._normalizeFrame(frame); } } else { this.currentAnimation = this._animation = null; this._currentFrame = frameOrAnimation; this._normalizeFrame(); } }; createjs.Sprite = createjs.promote(Sprite, "DisplayObject"); }()); //############################################################################## // Shape.js //############################################################################## (function() { "use strict"; // constructor: /** * A Shape allows you to display vector art in the display list. It composites a {{#crossLink "Graphics"}}{{/crossLink}} * instance which exposes all of the vector drawing methods. The Graphics instance can be shared between multiple Shape * instances to display the same vector graphics with different positions or transforms. * * If the vector art will not * change between draws, you may want to use the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method to reduce the * rendering cost. * *

    Example

    * * var graphics = new createjs.Graphics().beginFill("#ff0000").drawRect(0, 0, 100, 100); * var shape = new createjs.Shape(graphics); * * //Alternatively use can also use the graphics property of the Shape class to renderer the same as above. * var shape = new createjs.Shape(); * shape.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100); * * @class Shape * @extends DisplayObject * @constructor * @param {Graphics} graphics Optional. The graphics instance to display. If null, a new Graphics instance will be created. **/ function Shape(graphics) { this.DisplayObject_constructor(); // public properties: /** * The graphics instance to display. * @property graphics * @type Graphics **/ this.graphics = graphics ? graphics : new createjs.Graphics(); } var p = createjs.extend(Shape, createjs.DisplayObject); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. // public methods: /** * Returns true or false indicating whether the Shape would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the Shape would be visible if drawn to a canvas **/ p.isVisible = function() { var hasContent = this.cacheCanvas || (this.graphics && !this.graphics.isEmpty()); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; /** * Draws the Shape into the specified context ignoring its visible, alpha, shadow, and transform. Returns true if * the draw was handled (useful for overriding functionality). * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For example, * used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). * @return {Boolean} **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } this.graphics.draw(ctx, this); return true; }; /** * Returns a clone of this Shape. Some properties that are specific to this instance's current context are reverted to * their defaults (for example .parent). * @method clone * @param {Boolean} recursive If true, this Shape's {{#crossLink "Graphics"}}{{/crossLink}} instance will also be * cloned. If false, the Graphics instance will be shared with the new Shape. **/ p.clone = function(recursive) { var g = (recursive && this.graphics) ? this.graphics.clone() : this.graphics; return this._cloneProps(new Shape(g)); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Shape (name="+ this.name +")]"; }; createjs.Shape = createjs.promote(Shape, "DisplayObject"); }()); //############################################################################## // Text.js //############################################################################## (function() { "use strict"; // constructor: /** * Display one or more lines of dynamic text (not user editable) in the display list. Line wrapping support (using the * lineWidth) is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text, you can position HTML * text above or below the canvas relative to items in the display list using the {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} * method, or using {{#crossLink "DOMElement"}}{{/crossLink}}. * * Please note that Text does not support HTML text, and can only display one font style at a time. To use * multiple font styles, you will need to create multiple text instances, and position them manually. * *

    Example

    * * var text = new createjs.Text("Hello World", "20px Arial", "#ff7700"); * text.x = 100; * text.textBaseline = "alphabetic"; * * CreateJS Text supports web fonts (the same rules as Canvas). The font must be loaded and supported by the browser * before it can be displayed. * * Note: Text can be expensive to generate, so cache instances where possible. Be aware that not all * browsers will render Text exactly the same. * @class Text * @extends DisplayObject * @constructor * @param {String} [text] The text to display. * @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold * 36px Arial"). * @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. * "#F00", "red", or "#FF0000"). **/ function Text(text, font, color) { this.DisplayObject_constructor(); // public properties: /** * The text to display. * @property text * @type String **/ this.text = text; /** * The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial"). * @property font * @type String **/ this.font = font; /** * The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). Default is "#000". * It will also accept valid canvas fillStyle values. * @property color * @type String **/ this.color = color; /** * The horizontal text alignment. Any of "start", "end", "left", "right", and "center". For detailed * information view the * * whatwg spec. Default is "left". * @property textAlign * @type String **/ this.textAlign = "left"; /** * The vertical alignment point on the font. Any of "top", "hanging", "middle", "alphabetic", "ideographic", or * "bottom". For detailed information view the * whatwg spec. Default is "top". * @property textBaseline * @type String */ this.textBaseline = "top"; /** * The maximum width to draw the text. If maxWidth is specified (not null), the text will be condensed or * shrunk to make it fit in this width. For detailed information view the * * whatwg spec. * @property maxWidth * @type Number */ this.maxWidth = null; /** * If greater than 0, the text will be drawn as a stroke (outline) of the specified width. * @property outline * @type Number **/ this.outline = 0; /** * Indicates the line height (vertical distance between baselines) for multi-line text. If null or 0, * the value of getMeasuredLineHeight is used. * @property lineHeight * @type Number **/ this.lineHeight = 0; /** * Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null, * the text will not be wrapped. * @property lineWidth * @type Number **/ this.lineWidth = null; } var p = createjs.extend(Text, createjs.DisplayObject); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. // static properties: /** * @property _workingContext * @type CanvasRenderingContext2D * @private **/ var canvas = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")); if (canvas.getContext) { Text._workingContext = canvas.getContext("2d"); canvas.width = canvas.height = 1; } // constants: /** * Lookup table for the ratio to offset bounds x calculations based on the textAlign property. * @property H_OFFSETS * @type Object * @protected * @static **/ Text.H_OFFSETS = {start: 0, left: 0, center: -0.5, end: -1, right: -1}; /** * Lookup table for the ratio to offset bounds y calculations based on the textBaseline property. * @property H_OFFSETS * @type Object * @protected * @static **/ Text.V_OFFSETS = {top: 0, hanging: -0.01, middle: -0.4, alphabetic: -0.8, ideographic: -0.85, bottom: -1}; // public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { var hasContent = this.cacheCanvas || (this.text != null && this.text !== ""); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; /** * Draws the Text into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } var col = this.color || "#000"; if (this.outline) { ctx.strokeStyle = col; ctx.lineWidth = this.outline*1; } else { ctx.fillStyle = col; } this._drawText(this._prepContext(ctx)); return true; }; /** * Returns the measured, untransformed width of the text without wrapping. Use getBounds for a more robust value. * @method getMeasuredWidth * @return {Number} The measured, untransformed width of the text. **/ p.getMeasuredWidth = function() { return this._getMeasuredWidth(this.text); }; /** * Returns an approximate line height of the text, ignoring the lineHeight property. This is based on the measured * width of a "M" character multiplied by 1.2, which provides an approximate line height for most fonts. * @method getMeasuredLineHeight * @return {Number} an approximate line height of the text, ignoring the lineHeight property. This is * based on the measured width of a "M" character multiplied by 1.2, which approximates em for most fonts. **/ p.getMeasuredLineHeight = function() { return this._getMeasuredWidth("M")*1.2; }; /** * Returns the approximate height of multi-line text by multiplying the number of lines against either the * lineHeight (if specified) or {{#crossLink "Text/getMeasuredLineHeight"}}{{/crossLink}}. Note that * this operation requires the text flowing logic to run, which has an associated CPU cost. * @method getMeasuredHeight * @return {Number} The approximate height of the untransformed multi-line text. **/ p.getMeasuredHeight = function() { return this._drawText(null,{}).height; }; /** * Docced in superclass. */ p.getBounds = function() { var rect = this.DisplayObject_getBounds(); if (rect) { return rect; } if (this.text == null || this.text === "") { return null; } var o = this._drawText(null, {}); var w = (this.maxWidth && this.maxWidth < o.width) ? this.maxWidth : o.width; var x = w * Text.H_OFFSETS[this.textAlign||"left"]; var lineHeight = this.lineHeight||this.getMeasuredLineHeight(); var y = lineHeight * Text.V_OFFSETS[this.textBaseline||"top"]; return this._rectangle.setValues(x, y, w, o.height); }; /** * Returns an object with width, height, and lines properties. The width and height are the visual width and height * of the drawn text. The lines property contains an array of strings, one for * each line of text that will be drawn, accounting for line breaks and wrapping. These strings have trailing * whitespace removed. * @method getMetrics * @return {Object} An object with width, height, and lines properties. **/ p.getMetrics = function() { var o = {lines:[]}; o.lineHeight = this.lineHeight || this.getMeasuredLineHeight(); o.vOffset = o.lineHeight * Text.V_OFFSETS[this.textBaseline||"top"]; return this._drawText(null, o, o.lines); }; /** * Returns a clone of the Text instance. * @method clone * @return {Text} a clone of the Text instance. **/ p.clone = function() { return this._cloneProps(new Text(this.text, this.font, this.color)); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Text (text="+ (this.text.length > 20 ? this.text.substr(0, 17)+"..." : this.text) +")]"; }; // private methods: /** * @method _cloneProps * @param {Text} o * @protected * @return {Text} o **/ p._cloneProps = function(o) { this.DisplayObject__cloneProps(o); o.textAlign = this.textAlign; o.textBaseline = this.textBaseline; o.maxWidth = this.maxWidth; o.outline = this.outline; o.lineHeight = this.lineHeight; o.lineWidth = this.lineWidth; return o; }; /** * @method _getWorkingContext * @param {CanvasRenderingContext2D} ctx * @return {CanvasRenderingContext2D} * @protected **/ p._prepContext = function(ctx) { ctx.font = this.font||"10px sans-serif"; ctx.textAlign = this.textAlign||"left"; ctx.textBaseline = this.textBaseline||"top"; ctx.lineJoin = "miter"; ctx.miterLimit = 2.5; return ctx; }; /** * Draws multiline text. * @method _drawText * @param {CanvasRenderingContext2D} ctx * @param {Object} o * @param {Array} lines * @return {Object} * @protected **/ p._drawText = function(ctx, o, lines) { var paint = !!ctx; if (!paint) { ctx = Text._workingContext; ctx.save(); this._prepContext(ctx); } var lineHeight = this.lineHeight||this.getMeasuredLineHeight(); var maxW = 0, count = 0; var hardLines = String(this.text).split(/(?:\r\n|\r|\n)/); for (var i=0, l=hardLines.length; i this.lineWidth) { // text wrapping: var words = str.split(/(\s)/); str = words[0]; w = ctx.measureText(str).width; for (var j=1, jl=words.length; j this.lineWidth) { if (paint) { this._drawTextLine(ctx, str, count*lineHeight); } if (lines) { lines.push(str); } if (w > maxW) { maxW = w; } str = words[j+1]; w = ctx.measureText(str).width; count++; } else { str += words[j] + words[j+1]; w += wordW; } } } if (paint) { this._drawTextLine(ctx, str, count*lineHeight); } if (lines) { lines.push(str); } if (o && w == null) { w = ctx.measureText(str).width; } if (w > maxW) { maxW = w; } count++; } if (o) { o.width = maxW; o.height = count*lineHeight; } if (!paint) { ctx.restore(); } return o; }; /** * @method _drawTextLine * @param {CanvasRenderingContext2D} ctx * @param {String} text * @param {Number} y * @protected **/ p._drawTextLine = function(ctx, text, y) { // Chrome 17 will fail to draw the text if the last param is included but null, so we feed it a large value instead: if (this.outline) { ctx.strokeText(text, 0, y, this.maxWidth||0xFFFF); } else { ctx.fillText(text, 0, y, this.maxWidth||0xFFFF); } }; /** * @method _getMeasuredWidth * @param {String} text * @protected **/ p._getMeasuredWidth = function(text) { var ctx = Text._workingContext; ctx.save(); var w = this._prepContext(ctx).measureText(text).width; ctx.restore(); return w; }; createjs.Text = createjs.promote(Text, "DisplayObject"); }()); //############################################################################## // BitmapText.js //############################################################################## (function () { "use strict"; // constructor: /** * Displays text using bitmap glyphs defined in a sprite sheet. Multi-line text is supported using new line characters, * but automatic wrapping is not supported. See the {{#crossLink "BitmapText/spriteSheet:property"}}{{/crossLink}} * property for more information on defining glyphs. * * Important: While BitmapText extends Container, it is not designed to be used as one. * As such, methods like addChild and removeChild are disabled. * * * @class BitmapText * @extends DisplayObject * @param {String} [text=""] The text to display. * @param {SpriteSheet} [spriteSheet=null] The spritesheet that defines the character glyphs. * @constructor **/ function BitmapText(text, spriteSheet) { this.Container_constructor(); // public properties: /** * The text to display. * @property text * @type String * @default "" **/ this.text = text||""; /** * A SpriteSheet instance that defines the glyphs for this bitmap text. Each glyph/character * should have a single frame animation defined in the sprite sheet named the same as * corresponding character. For example, the following animation definition: * * "A": {frames: [0]} * * would indicate that the frame at index 0 of the spritesheet should be drawn for the "A" character. The short form * is also acceptable: * * "A": 0 * * Note that if a character in the text is not found in the sprite sheet, it will also * try to use the alternate case (upper or lower). * * See SpriteSheet for more information on defining sprite sheet data. * @property spriteSheet * @type SpriteSheet * @default null **/ this.spriteSheet = spriteSheet; /** * The height of each line of text. If 0, then it will use a line height calculated * by checking for the height of the "1", "T", or "L" character (in that order). If * those characters are not defined, it will use the height of the first frame of the * sprite sheet. * @property lineHeight * @type Number * @default 0 **/ this.lineHeight = 0; /** * This spacing (in pixels) will be added after each character in the output. * @property letterSpacing * @type Number * @default 0 **/ this.letterSpacing = 0; /** * If a space character is not defined in the sprite sheet, then empty pixels equal to * spaceWidth will be inserted instead. If 0, then it will use a value calculated * by checking for the width of the "1", "l", "E", or "A" character (in that order). If * those characters are not defined, it will use the width of the first frame of the * sprite sheet. * @property spaceWidth * @type Number * @default 0 **/ this.spaceWidth = 0; // private properties: /** * @property _oldProps * @type Object * @protected **/ this._oldProps = {text:0,spriteSheet:0,lineHeight:0,letterSpacing:0,spaceWidth:0}; /** * Used to track the object which this class attached listeners to, helps optimize listener attachment. * @property _oldStage * @type Stage * @protected */ this._oldStage = null; /** * The event listener proxy triggered drawing draw for special circumstances. * @property _drawAction * @type function * @protected */ this._drawAction = null; } var p = createjs.extend(BitmapText, createjs.Container); // static properties: /** * BitmapText uses Sprite instances to draw text. To reduce the creation and destruction of instances (and thus garbage collection), it maintains * an internal object pool of sprite instances to reuse. Increasing this value can cause more sprites to be * retained, slightly increasing memory use, but reducing instantiation. * @property maxPoolSize * @type Number * @static * @default 100 **/ BitmapText.maxPoolSize = 100; /** * Sprite object pool. * @type {Array} * @static * @private */ BitmapText._spritePool = []; // public methods: /** * Docced in superclass. **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return; } this._updateState(); this.Container_draw(ctx, ignoreCache); }; /** * Docced in superclass. **/ p.getBounds = function() { this._updateText(); return this.Container_getBounds(); }; /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { var hasContent = this.cacheCanvas || (this.spriteSheet && this.spriteSheet.complete && this.text); return !!(this.visible && this.alpha > 0 && this.scaleX !== 0 && this.scaleY !== 0 && hasContent); }; p.clone = function() { return this._cloneProps(new BitmapText(this.text, this.spriteSheet)); }; /** * Disabled in BitmapText. * @method addChild **/ /** * Disabled in BitmapText. * @method addChildAt **/ /** * Disabled in BitmapText. * @method removeChild **/ /** * Disabled in BitmapText. * @method removeChildAt **/ /** * Disabled in BitmapText. * @method removeAllChildren **/ p.addChild = p.addChildAt = p.removeChild = p.removeChildAt = p.removeAllChildren = function() {}; // private methods: /** * Docced in superclass. **/ p._updateState = function() { this._updateText(); }; /** * @method _cloneProps * @param {BitmapText} o * @return {BitmapText} o * @protected **/ p._cloneProps = function(o) { this.Container__cloneProps(o); o.lineHeight = this.lineHeight; o.letterSpacing = this.letterSpacing; o.spaceWidth = this.spaceWidth; return o; }; /** * @method _getFrameIndex * @param {String} character * @param {SpriteSheet} spriteSheet * @return {Number} * @protected **/ p._getFrameIndex = function(character, spriteSheet) { var c, o = spriteSheet.getAnimation(character); if (!o) { (character != (c = character.toUpperCase())) || (character != (c = character.toLowerCase())) || (c=null); if (c) { o = spriteSheet.getAnimation(c); } } return o && o.frames[0]; }; /** * @method _getFrame * @param {String} character * @param {SpriteSheet} spriteSheet * @return {Object} * @protected **/ p._getFrame = function(character, spriteSheet) { var index = this._getFrameIndex(character, spriteSheet); return index == null ? index : spriteSheet.getFrame(index); }; /** * @method _getLineHeight * @param {SpriteSheet} ss * @return {Number} * @protected **/ p._getLineHeight = function(ss) { var frame = this._getFrame("1",ss) || this._getFrame("T",ss) || this._getFrame("L",ss) || ss.getFrame(0); return frame ? frame.rect.height : 1; }; /** * @method _getSpaceWidth * @param {SpriteSheet} ss * @return {Number} * @protected **/ p._getSpaceWidth = function(ss) { var frame = this._getFrame("1",ss) || this._getFrame("l",ss) || this._getFrame("e",ss) || this._getFrame("a",ss) || ss.getFrame(0); return frame ? frame.rect.width : 1; }; /** * @method _updateText * @protected **/ p._updateText = function() { var x=0, y=0, o=this._oldProps, change=false, spaceW=this.spaceWidth, lineH=this.lineHeight, ss=this.spriteSheet; var pool=BitmapText._spritePool, kids=this.children, childIndex=0, numKids=kids.length, sprite; for (var n in o) { if (o[n] != this[n]) { o[n] = this[n]; change = true; } } if (!change) { return; } var hasSpace = !!this._getFrame(" ", ss); if (!hasSpace && !spaceW) { spaceW = this._getSpaceWidth(ss); } if (!lineH) { lineH = this._getLineHeight(ss); } for(var i=0, l=this.text.length; i childIndex) { // faster than removeChild. pool.push(sprite = kids.pop()); sprite.parent = null; numKids--; } if (pool.length > BitmapText.maxPoolSize) { pool.length = BitmapText.maxPoolSize; } }; createjs.BitmapText = createjs.promote(BitmapText, "Container"); }()); //############################################################################## // MovieClip.js //############################################################################## (function() { "use strict"; // constructor: /** * The MovieClip class associates a TweenJS Timeline with an EaselJS {{#crossLink "Container"}}{{/crossLink}}. It allows * you to create objects which encapsulate timeline animations, state changes, and synched actions. The MovieClip * class has been included in the EaselJS minified file since 0.7.0. * * Currently MovieClip only works properly if it is tick based (as opposed to time based) though some concessions have * been made to support time-based timelines in the future. * *

    Example

    * This example animates two shapes back and forth. The grey shape starts on the left, but we jump to a mid-point in * the animation using {{#crossLink "MovieClip/gotoAndPlay"}}{{/crossLink}}. * * var stage = new createjs.Stage("canvas"); * createjs.Ticker.addEventListener("tick", stage); * * var mc = new createjs.MovieClip({loop:-1, labels:{myLabel:20}}); * stage.addChild(mc); * * var child1 = new createjs.Shape( * new createjs.Graphics().beginFill("#999999") * .drawCircle(30,30,30)); * var child2 = new createjs.Shape( * new createjs.Graphics().beginFill("#5a9cfb") * .drawCircle(30,30,30)); * * mc.timeline.addTween( * createjs.Tween.get(child1) * .to({x:0}).to({x:60}, 50).to({x:0}, 50)); * mc.timeline.addTween( * createjs.Tween.get(child2) * .to({x:60}).to({x:0}, 50).to({x:60}, 50)); * * mc.gotoAndPlay("start"); * * It is recommended to use tween.to() to animate and set properties (use no duration to have it set * immediately), and the tween.wait() method to create delays between animations. Note that using the * tween.set() method to affect properties will likely not provide the desired result. * * @class MovieClip * @main MovieClip * @param {Object} [props] The configuration properties to apply to this instance (ex. `{mode:MovieClip.SYNCHED}`). * Supported props for the MovieClip are listed below. These props are set on the corresponding instance properties except where * specified.
      *
    • `mode`
    • *
    • `startPosition`
    • *
    • `frameBounds`
    • *
    * * This object will also be passed into the Timeline instance associated with this MovieClip. See the documentation * for Timeline for a list of supported props (ex. `paused`, `labels`, `loop`, `reversed`, etc.) * @extends Container * @constructor **/ function MovieClip(props) { this.Container_constructor(); !MovieClip.inited&&MovieClip.init(); // static init var mode, startPosition, loop, labels; // handle old params (mode, startPosition, loop, labels): // TODO: deprecated param handling: if (props instanceof String || arguments.length > 1) { mode = props; startPosition = arguments[1]; loop = arguments[2]; labels = arguments[3]; if (loop == null) { loop = -1; } props = null; } else if (props) { mode = props.mode; startPosition = props.startPosition; loop = props.loop; labels = props.labels; } if (!props) { props = {labels:labels}; } // public properties: /** * Controls how this MovieClip advances its time. Must be one of 0 (INDEPENDENT), 1 (SINGLE_FRAME), or 2 (SYNCHED). * See each constant for a description of the behaviour. * @property mode * @type String * @default null **/ this.mode = mode||MovieClip.INDEPENDENT; /** * Specifies what the first frame to play in this movieclip, or the only frame to display if mode is SINGLE_FRAME. * @property startPosition * @type Number * @default 0 */ this.startPosition = startPosition||0; /** * Specifies how many times this MovieClip should loop. A value of -1 indicates it should loop indefinitely. A value of * 1 would cause it to loop once (ie. play a total of twice). * @property loop * @type Number * @default -1 */ this.loop = loop === true ? -1 : (loop || 0); /** * The current frame of the movieclip. * @property currentFrame * @type Number * @default 0 * @readonly */ this.currentFrame = 0; /** * If true, the MovieClip's position will not advance when ticked. * @property paused * @type Boolean * @default false */ this.paused = props.paused||false; /** * If true, actions in this MovieClip's tweens will be run when the playhead advances. * @property actionsEnabled * @type Boolean * @default true */ this.actionsEnabled = true; /** * If true, the MovieClip will automatically be reset to its first frame whenever the timeline adds * it back onto the display list. This only applies to MovieClip instances with mode=INDEPENDENT. *

    * For example, if you had a character animation with a "body" child MovieClip instance * with different costumes on each frame, you could set body.autoReset = false, so that * you can manually change the frame it is on, without worrying that it will be reset * automatically. * @property autoReset * @type Boolean * @default true */ this.autoReset = true; /** * An array of bounds for each frame in the MovieClip. This is mainly intended for tool output. * @property frameBounds * @type Array * @default null */ this.frameBounds = this.frameBounds||props.frameBounds; // frameBounds are set on the prototype in Animate. /** * By default MovieClip instances advance one frame per tick. Specifying a framerate for the MovieClip * will cause it to advance based on elapsed time between ticks as appropriate to maintain the target * framerate. * * For example, if a MovieClip with a framerate of 10 is placed on a Stage being updated at 40fps, then the MovieClip will * advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will * vary slightly between frames. * * This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being * passed into {{#crossLink "Stage/update"}}{{/crossLink}}. * @property framerate * @type {Number} * @default null **/ this.framerate = null; // set up the needed props for Timeline: props.useTicks = props.paused = true; /** * The TweenJS Timeline that is associated with this MovieClip. This is created automatically when the MovieClip * instance is initialized. Animations are created by adding TweenJS Tween * instances to the timeline. * *

    Example

    * * var tween = createjs.Tween.get(target).to({x:0}).to({x:100}, 30); * var mc = new createjs.MovieClip(); * mc.timeline.addTween(tween); * * Elements can be added and removed from the timeline by toggling an "_off" property * using the tweenInstance.to() method. Note that using Tween.set is not recommended to * create MovieClip animations. The following example will toggle the target off on frame 0, and then back on for * frame 1. You can use the "visible" property to achieve the same effect. * * var tween = createjs.Tween.get(target).to({_off:false}) * .wait(1).to({_off:true}) * .wait(1).to({_off:false}); * * @property timeline * @type Timeline * @default null */ this.timeline = new createjs.Timeline(props); // private properties: /** * @property _synchOffset * @type Number * @default 0 * @private */ this._synchOffset = 0; /** * @property _rawPosition * @type Number * @default -1 * @private */ this._rawPosition = -1; // TODO: evaluate using a ._reset Boolean prop instead of -1. /** * @property _bound_resolveState * @type Function * @private */ this._bound_resolveState = this._resolveState.bind(this); /** * The time remaining from the previous tick, only applicable when .framerate is set. * @property _t * @type Number * @private */ this._t = 0; /** * List of display objects that are actively being managed by the MovieClip. * @property _managed * @type Object * @private */ this._managed = {}; } var p = createjs.extend(MovieClip, createjs.Container); // constants: /** * The MovieClip will advance independently of its parent, even if its parent is paused. * This is the default mode. * @property INDEPENDENT * @static * @type String * @default "independent" * @readonly **/ MovieClip.INDEPENDENT = "independent"; /** * The MovieClip will only display a single frame (as determined by the startPosition property). * @property SINGLE_FRAME * @static * @type String * @default "single" * @readonly **/ MovieClip.SINGLE_FRAME = "single"; /** * The MovieClip will be advanced only when its parent advances and will be synched to the position of * the parent MovieClip. * @property SYNCHED * @static * @type String * @default "synched" * @readonly **/ MovieClip.SYNCHED = "synched"; // static properties: MovieClip.inited = false; // static methods: MovieClip.init = function() { if (MovieClip.inited) { return; } // plugins introduce some overhead to Tween, so we only install this if an MC is instantiated. MovieClipPlugin.install(); MovieClip.inited = true; }; // getter / setters: /** * Use the {{#crossLink "MovieClip/labels:property"}}{{/crossLink}} property instead. * @method _getLabels * @protected * @return {Array} **/ p._getLabels = function() { return this.timeline.getLabels(); }; // MovieClip.getLabels is @deprecated. Remove for 1.1+ p.getLabels = createjs.deprecate(p._getLabels, "MovieClip.getLabels"); /** * Use the {{#crossLink "MovieClip/currentLabel:property"}}{{/crossLink}} property instead. * @method _getCurrentLabel * @protected * @return {String} **/ p._getCurrentLabel = function() { return this.timeline.currentLabel; }; // MovieClip.getCurrentLabel is @deprecated. Remove for 1.1+ p.getCurrentLabel = createjs.deprecate(p._getCurrentLabel, "MovieClip.getCurrentLabel"); /** * Use the {{#crossLink "MovieClip/duration:property"}}{{/crossLink}} property instead. * @method _getDuration * @protected * @return {Number} **/ p._getDuration = function() { return this.timeline.duration; }; // MovieClip.getDuration is @deprecated. Remove for 1.1+ p.getDuration = createjs.deprecate(p._getDuration, "MovieClip.getDuration"); /** * Returns an array of objects with label and position (aka frame) properties, sorted by position. * @property labels * @type {Array} * @readonly **/ /** * Returns the name of the label on or immediately before the current frame. * @property currentLabel * @type {String} * @readonly **/ /** * Returns the duration of this MovieClip in seconds or ticks. * @property totalFrames * @type {Number} * @readonly **/ /** * Returns the duration of this MovieClip in seconds or ticks. * @property duration * @type {Number} * @readonly **/ try { Object.defineProperties(p, { labels: { get: p._getLabels }, currentLabel: { get: p._getCurrentLabel }, totalFrames: { get: p._getDuration }, duration: { get: p._getDuration } // TODO: can we just proxy .currentFrame to tl.position as well? Ditto for .loop (or just remove entirely). }); } catch (e) {} // public methods: /** * Constructor alias for backwards compatibility. This method will be removed in future versions. * Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. * @method initialize * @deprecated in favour of `createjs.promote()` **/ p.initialize = MovieClip; // TODO: Deprecated. This is for backwards support of Adobe Flash/Animate /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { // children are placed in draw, so we can't determine if we have content. return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). **/ p.draw = function(ctx, ignoreCache) { // draw to cache first: if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } this._updateState(); this.Container_draw(ctx, ignoreCache); return true; }; /** * Sets paused to false. * @method play **/ p.play = function() { this.paused = false; }; /** * Sets paused to true. * @method stop **/ p.stop = function() { this.paused = true; }; /** * Advances this movie clip to the specified position or label and sets paused to false. * @method gotoAndPlay * @param {String|Number} positionOrLabel The animation name or frame number to go to. **/ p.gotoAndPlay = function(positionOrLabel) { this.paused = false; this._goto(positionOrLabel); }; /** * Advances this movie clip to the specified position or label and sets paused to true. * @method gotoAndStop * @param {String|Number} positionOrLabel The animation or frame name to go to. **/ p.gotoAndStop = function(positionOrLabel) { this.paused = true; this._goto(positionOrLabel); }; /** * Advances the playhead. This occurs automatically each tick by default. * @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set. * @method advance */ p.advance = function(time) { var independent = MovieClip.INDEPENDENT; if (this.mode !== independent) { return; } // update happens in draw for synched clips // if this MC doesn't have a framerate, hunt ancestors for one: var o=this, fps = o.framerate; while ((o = o.parent) && fps === null) { if (o.mode === independent) { fps = o._framerate; } } this._framerate = fps; if (this.paused) { return; } // calculate how many frames to advance: var t = (fps !== null && fps !== -1 && time !== null) ? time/(1000/fps) + this._t : 1; var frames = t|0; this._t = t-frames; // leftover time, save to add to next advance. while (frames--) { this._updateTimeline(this._rawPosition+1, false); } }; /** * MovieClip instances cannot be cloned. * @method clone **/ p.clone = function() { // TODO: add support for this? Need to clone the Timeline & retarget tweens - pretty complex. throw("MovieClip cannot be cloned."); }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[MovieClip (name="+ this.name +")]"; }; // private methods: /** * Docced in superclass. **/ p._updateState = function() { if (this._rawPosition === -1 || this.mode !== MovieClip.INDEPENDENT) { this._updateTimeline(-1); } }; /** * @method _tick * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * function. * @protected **/ p._tick = function(evtObj) { this.advance(evtObj&&evtObj.delta); this.Container__tick(evtObj); }; /** * @method _goto * @param {String|Number} positionOrLabel The animation name or frame number to go to. * @protected **/ p._goto = function(positionOrLabel) { var pos = this.timeline.resolve(positionOrLabel); if (pos == null) { return; } this._t = 0; this._updateTimeline(pos, true); }; /** * @method _reset * @private **/ p._reset = function() { this._rawPosition = -1; this._t = this.currentFrame = 0; this.paused = false; }; /** * @method _updateTimeline * @param {Boolean} jump Indicates whether this update is due to jumping (via gotoAndXX) to a new position. * @protected **/ p._updateTimeline = function(rawPosition, jump) { var synced = this.mode !== MovieClip.INDEPENDENT, tl = this.timeline; if (synced) { rawPosition = this.startPosition + (this.mode===MovieClip.SINGLE_FRAME?0:this._synchOffset); } if (rawPosition < 0) { rawPosition = 0; } if (this._rawPosition === rawPosition && !synced) { return; } this._rawPosition = rawPosition; // update timeline position, ignoring actions if this is a graphic. tl.loop = this.loop; // TODO: should we maintain this on MovieClip, or just have it on timeline? tl.setPosition(rawPosition, synced || !this.actionsEnabled, jump, this._bound_resolveState); }; /** * Renders position 0 without running actions or updating _rawPosition. * Primarily used by Animate CC to build out the first frame in the constructor of MC symbols. * NOTE: not tested when run after the MC advances past the first frame. * @method _renderFirstFrame * @protected **/ p._renderFirstFrame = function() { var tl = this.timeline, pos = tl.rawPosition; tl.setPosition(0, true, true, this._bound_resolveState); tl.rawPosition = pos; }; /** * Runs via a callback after timeline property updates and before actions. * @method _resolveState * @protected **/ p._resolveState = function() { var tl = this.timeline; this.currentFrame = tl.position; for (var n in this._managed) { this._managed[n] = 1; } var tweens = tl.tweens; for (var i=0, l=tweens.length; i=0; i--) { var id = kids[i].id; if (this._managed[id] === 1) { this.removeChildAt(i); delete(this._managed[id]); } } }; /** * @method _setState * @param {Array} state * @param {Number} offset * @protected **/ p._setState = function(state, offset) { if (!state) { return; } for (var i=state.length-1;i>=0;i--) { var o = state[i]; var target = o.t; var props = o.p; for (var n in props) { target[n] = props[n]; } this._addManagedChild(target, offset); } }; /** * Adds a child to the timeline, and sets it up as a managed child. * @method _addManagedChild * @param {MovieClip} child The child MovieClip to manage * @param {Number} offset * @private **/ p._addManagedChild = function(child, offset) { if (child._off) { return; } this.addChildAt(child,0); if (child instanceof MovieClip) { child._synchOffset = offset; // TODO: this does not precisely match Adobe Flash/Animate, which loses track of the clip if it is renamed or removed from the timeline, which causes it to reset. // TODO: should also reset when MovieClip loops, though that will be a bit tricky to detect. if (child.mode === MovieClip.INDEPENDENT && child.autoReset && (!this._managed[child.id])) { child._reset(); } } this._managed[child.id] = 2; }; /** * @method _getBounds * @param {Matrix2D} matrix * @param {Boolean} ignoreTransform * @return {Rectangle} * @protected **/ p._getBounds = function(matrix, ignoreTransform) { var bounds = this.DisplayObject_getBounds(); if (!bounds) { if (this.frameBounds) { bounds = this._rectangle.copy(this.frameBounds[this.currentFrame]); } } if (bounds) { return this._transformBounds(bounds, matrix, ignoreTransform); } return this.Container__getBounds(matrix, ignoreTransform); }; createjs.MovieClip = createjs.promote(MovieClip, "Container"); // MovieClipPlugin for TweenJS: /** * This plugin works with TweenJS to prevent the startPosition * property from tweening. * @private * @class MovieClipPlugin * @constructor **/ function MovieClipPlugin() { throw("MovieClipPlugin cannot be instantiated.") } /** * @property priority * @type {Number} * @static * @readonly **/ MovieClipPlugin.priority = 100; // very high priority, should run first /** * @property ID * @type {String} * @static * @readonly **/ MovieClipPlugin.ID = "MovieClip"; /** * @method install * @static **/ MovieClipPlugin.install = function() { createjs.Tween._installPlugin(MovieClipPlugin); }; /** * @method init * @param {Tween} tween * @param {String} prop * @param {*} value * @static **/ MovieClipPlugin.init = function(tween, prop, value) { if (prop === "startPosition" && tween.target instanceof MovieClip) { tween._addPlugin(MovieClipPlugin); } }; /** * @method step * @param {Tween} tween * @param {TweenStep} step * @param {Object} props * @static **/ MovieClipPlugin.step = function(tween, step, props) {}; /** * @method change * @param {Tween} tween * @param {TweenStep} step * @param {*} value * @param {Number} ratio * @param {Object} end * @return {*} * @static */ MovieClipPlugin.change = function(tween, step, prop, value, ratio, end) { if (prop === "startPosition") { return (ratio === 1 ? step.props[prop] : step.prev.props[prop]); } }; }()); //############################################################################## // SpriteSheetUtils.js //############################################################################## (function() { "use strict"; // constructor: /** * The SpriteSheetUtils class is a collection of static methods for working with {{#crossLink "SpriteSheet"}}{{/crossLink}}s. * A sprite sheet is a series of images (usually animation frames) combined into a single image on a regular grid. For * example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across * by 2 high). The SpriteSheetUtils class uses a static interface and should not be instantiated. * @class SpriteSheetUtils * @static **/ function SpriteSheetUtils() { throw "SpriteSheetUtils cannot be instantiated"; } // private static properties: /** * @property _workingCanvas * @static * @type HTMLCanvasElement | Object * @protected */ /** * @property _workingContext * @static * @type CanvasRenderingContext2D * @protected */ var canvas = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")); if (canvas.getContext) { SpriteSheetUtils._workingCanvas = canvas; SpriteSheetUtils._workingContext = canvas.getContext("2d"); canvas.width = canvas.height = 1; } // public static methods: /** * Returns a single frame of the specified sprite sheet as a new PNG image. An example of when this may be useful is * to use a spritesheet frame as the source for a bitmap fill. * * WARNING: In almost all cases it is better to display a single frame using a {{#crossLink "Sprite"}}{{/crossLink}} * with a {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} call than it is to slice out a frame using this * method and display it with a Bitmap instance. You can also crop an image using the {{#crossLink "Bitmap/sourceRect"}}{{/crossLink}} * property of {{#crossLink "Bitmap"}}{{/crossLink}}. * * The extractFrame method may cause cross-domain warnings since it accesses pixels directly on the canvas. * @method extractFrame * @static * @param {SpriteSheet} spriteSheet The SpriteSheet instance to extract a frame from. * @param {Number|String} frameOrAnimation The frame number or animation name to extract. If an animation * name is specified, only the first frame of the animation will be extracted. * @return {HTMLImageElement} a single frame of the specified sprite sheet as a new PNG image. */ SpriteSheetUtils.extractFrame = function(spriteSheet, frameOrAnimation) { if (isNaN(frameOrAnimation)) { frameOrAnimation = spriteSheet.getAnimation(frameOrAnimation).frames[0]; } var data = spriteSheet.getFrame(frameOrAnimation); if (!data) { return null; } var r = data.rect; var canvas = SpriteSheetUtils._workingCanvas; canvas.width = r.width; canvas.height = r.height; SpriteSheetUtils._workingContext.drawImage(data.image, r.x, r.y, r.width, r.height, 0, 0, r.width, r.height); var img = document.createElement("img"); img.src = canvas.toDataURL("image/png"); return img; }; // SpriteSheetUtils.addFlippedFrames is @deprecated. Remove for 1.1+ SpriteSheetUtils.addFlippedFrames = createjs.deprecate(null, "SpriteSheetUtils.addFlippedFrames"); // SpriteSheetUtils.addFlippedFrames is @deprecated. Remove for 1.1+ SpriteSheetUtils.mergeAlpha = createjs.deprecate(null, "SpriteSheetUtils.mergeAlpha"); // private static methods: SpriteSheetUtils._flip = function(spriteSheet, count, h, v) { var imgs = spriteSheet._images; var canvas = SpriteSheetUtils._workingCanvas; var ctx = SpriteSheetUtils._workingContext; var il = imgs.length/count; for (var i=0;i this.maxHeight) { throw SpriteSheetBuilder.ERR_DIMENSIONS; } var y=0, x=0; var img = 0; while (frames.length) { var o = this._fillRow(frames, y, img, dataFrames, pad); if (o.w > x) { x = o.w; } y += o.h; if (!o.h || !frames.length) { var canvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"); canvas.width = this._getSize(x,this.maxWidth); canvas.height = this._getSize(y,this.maxHeight); this._data.images[img] = canvas; if (!o.h) { x=y=0; img++; } } } }; /** * @method _setupMovieClipFrame * @protected * @return {Number} The width & height of the row. **/ p._setupMovieClipFrame = function(source, data) { var ae = source.actionsEnabled; source.actionsEnabled = false; source.gotoAndStop(data.i); source.actionsEnabled = ae; data.f&&data.f(source, data.d, data.i); }; /** * @method _getSize * @protected * @return {Number} The width & height of the row. **/ p._getSize = function(size,max) { var pow = 4; while (Math.pow(2,++pow) < size){} return Math.min(max,Math.pow(2,pow)); }; /** * @method _fillRow * @param {Array} frames * @param {Number} y * @param {HTMLImageElement} img * @param {Object} dataFrames * @param {Number} pad * @protected * @return {Number} The width & height of the row. **/ p._fillRow = function(frames, y, img, dataFrames, pad) { var w = this.maxWidth; var maxH = this.maxHeight; y += pad; var h = maxH-y; var x = pad; var height = 0; for (var i=frames.length-1; i>=0; i--) { var frame = frames[i]; var sc = this._scale*frame.scale; var rect = frame.sourceRect; var source = frame.source; var rx = Math.floor(sc*rect.x-pad); var ry = Math.floor(sc*rect.y-pad); var rh = Math.ceil(sc*rect.height+pad*2); var rw = Math.ceil(sc*rect.width+pad*2); if (rw > w) { throw SpriteSheetBuilder.ERR_DIMENSIONS; } if (rh > h || x+rw > w) { continue; } frame.img = img; frame.rect = new createjs.Rectangle(x,y,rw,rh); height = height || rh; frames.splice(i,1); dataFrames[frame.index] = [x,y,rw,rh,img,Math.round(-rx+sc*source.regX-pad),Math.round(-ry+sc*source.regY-pad)]; x += rw; } return {w:x, h:height}; }; /** * @method _endBuild * @protected **/ p._endBuild = function() { this.spriteSheet = new createjs.SpriteSheet(this._data); this._data = null; this.progress = 1; this.dispatchEvent("complete"); }; /** * @method _run * @protected **/ p._run = function() { var ts = Math.max(0.01, Math.min(0.99, this.timeSlice||0.3))*50; var t = (new Date()).getTime()+ts; var complete = false; while (t > (new Date()).getTime()) { if (!this._drawNext()) { complete = true; break; } } if (complete) { this._endBuild(); } else { var _this = this; this._timerID = setTimeout(function() { _this._run(); }, 50-ts); } var p = this.progress = this._index/this._frames.length; if (this.hasEventListener("progress")) { var evt = new createjs.Event("progress"); evt.progress = p; this.dispatchEvent(evt); } }; /** * @method _drawNext * @protected * @return Boolean Returns false if this is the last draw. **/ p._drawNext = function() { var frame = this._frames[this._index]; var sc = frame.scale*this._scale; var rect = frame.rect; var sourceRect = frame.sourceRect; var canvas = this._data.images[frame.img]; var ctx = canvas.getContext("2d"); frame.funct&&frame.funct(frame.source, frame.data); ctx.save(); ctx.beginPath(); ctx.rect(rect.x, rect.y, rect.width, rect.height); ctx.clip(); ctx.translate(Math.ceil(rect.x-sourceRect.x*sc), Math.ceil(rect.y-sourceRect.y*sc)); ctx.scale(sc,sc); frame.source.draw(ctx); // display object will draw itself. ctx.restore(); return (++this._index) < this._frames.length; }; createjs.SpriteSheetBuilder = createjs.promote(SpriteSheetBuilder, "EventDispatcher"); }()); //############################################################################## // DOMElement.js //############################################################################## (function() { "use strict"; // constructor: /** * This class is still experimental, and more advanced use is likely to be buggy. Please report bugs. * * A DOMElement allows you to associate a HTMLElement with the display list. It will be transformed * within the DOM as though it is child of the {{#crossLink "Container"}}{{/crossLink}} it is added to. However, it is * not rendered to canvas, and as such will retain whatever z-index it has relative to the canvas (ie. it will be * drawn in front of or behind the canvas). * * The position of a DOMElement is relative to their parent node in the DOM. It is recommended that * the DOM Object be added to a div that also contains the canvas so that they share the same position * on the page. * * DOMElement is useful for positioning HTML elements over top of canvas content, and for elements * that you want to display outside the bounds of the canvas. For example, a tooltip with rich HTML * content. * *

    Mouse Interaction

    * * DOMElement instances are not full EaselJS display objects, and do not participate in EaselJS mouse * events or support methods like hitTest. To get mouse events from a DOMElement, you must instead add handlers to * the htmlElement (note, this does not support EventDispatcher) * * var domElement = new createjs.DOMElement(htmlElement); * domElement.htmlElement.onclick = function() { * console.log("clicked"); * } * * Important: This class needs to be notified it is about to be drawn, this will happen automatically * if you call stage.update, calling stage.draw or disabling tickEnabled will miss important steps and it will render * stale information. * * @class DOMElement * @extends DisplayObject * @constructor * @param {HTMLElement} htmlElement A reference or id for the DOM element to manage. */ function DOMElement(htmlElement) { this.DisplayObject_constructor(); if (typeof(htmlElement)=="string") { htmlElement = document.getElementById(htmlElement); } this.mouseEnabled = false; var style = htmlElement.style; style.position = "absolute"; style.transformOrigin = style.WebkitTransformOrigin = style.msTransformOrigin = style.MozTransformOrigin = style.OTransformOrigin = "0% 0%"; // public properties: /** * The DOM object to manage. * @property htmlElement * @type HTMLElement */ this.htmlElement = htmlElement; // private properties: /** * @property _oldMtx * @type Matrix2D * @protected */ this._oldProps = null; /** * Used to track the object which this class attached listeners to, helps optimize listener attachment. * @property _oldStage * @type Stage * @protected */ this._oldStage = null; /** * The event listener proxy triggered drawing draw for special circumstances. * @property _drawAction * @type function * @protected */ this._drawAction = null; } var p = createjs.extend(DOMElement, createjs.DisplayObject); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. // public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas */ p.isVisible = function() { return this.htmlElement != null; }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). * @return {Boolean} */ p.draw = function(ctx, ignoreCache) { // this relies on the _tick method because draw isn't called if the parent is not visible. // the actual update happens in _handleDrawEnd return true; }; /** * Not applicable to DOMElement. * @method cache */ p.cache = function() {}; /** * Not applicable to DOMElement. * @method uncache */ p.uncache = function() {}; /** * Not applicable to DOMElement. * @method updateCache */ p.updateCache = function() {}; /** * Not applicable to DOMElement. * @method hitTest */ p.hitTest = function() {}; /** * Not applicable to DOMElement. * @method localToGlobal */ p.localToGlobal = function() {}; /** * Not applicable to DOMElement. * @method globalToLocal */ p.globalToLocal = function() {}; /** * Not applicable to DOMElement. * @method localToLocal */ p.localToLocal = function() {}; /** * DOMElement cannot be cloned. Throws an error. * @method clone */ p.clone = function() { throw("DOMElement cannot be cloned.") }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. */ p.toString = function() { return "[DOMElement (name="+ this.name +")]"; }; /** * Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances * are not full EaselJS display objects and do not participate in EaselJS mouse events. * @event click */ /** * Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances * are not full EaselJS display objects and do not participate in EaselJS mouse events. * @event dblClick */ /** * Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances * are not full EaselJS display objects and do not participate in EaselJS mouse events. * @event mousedown */ /** * The HTMLElement can listen for the mouseover event, not the DOMElement instance. * Since DOMElement instances are not full EaselJS display objects and do not participate in EaselJS mouse events. * @event mouseover */ /** * Not applicable to DOMElement. * @event tick */ // private methods: /** * @method _tick * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * function. * @protected */ p._tick = function(evtObj) { var stage = this.stage; if(stage && stage !== this._oldStage) { this._drawAction && stage.off("drawend", this._drawAction); this._drawAction = stage.on("drawend", this._handleDrawEnd, this); this._oldStage = stage; } this.DisplayObject__tick(evtObj); }; /** * @method _handleDrawEnd * @param {Event} evt * @protected */ /* p._handleDrawEnd = function(evt) { var o = this.htmlElement; if (!o) { return; } var style = o.style; var props = this.getConcatenatedDisplayProps(this._props), mtx = props.matrix; var visibility = props.visible ? "visible" : "hidden"; if (visibility != style.visibility) { style.visibility = visibility; } if (!props.visible) { return; } var oldProps = this._oldProps, oldMtx = oldProps&&oldProps.matrix; var n = 10000; // precision if (!oldMtx || !oldMtx.equals(mtx)) { var str = "matrix(" + (mtx.a*n|0)/n +","+ (mtx.b*n|0)/n +","+ (mtx.c*n|0)/n +","+ (mtx.d*n|0)/n +","+ (mtx.tx+0.5|0); style.transform = style.WebkitTransform = style.OTransform = style.msTransform = str +","+ (mtx.ty+0.5|0) +")"; style.MozTransform = str +"px,"+ (mtx.ty+0.5|0) +"px)"; if (!oldProps) { oldProps = this._oldProps = new createjs.DisplayProps(true, null); } oldProps.matrix.copy(mtx); } if (oldProps.alpha != props.alpha) { style.opacity = ""+(props.alpha*n|0)/n; oldProps.alpha = props.alpha; } }; */ //---------- p._handleDrawEnd = function(evt) { var o = this.htmlElement; if (!o) { return; } var style = o.style; var props = this.getConcatenatedDisplayProps(this._props), mtx = props.matrix; var visibility = props.visible ? "visible" : "hidden"; if (visibility != style.visibility) { style.visibility = visibility; } if (!props.visible) { return; } var oldProps = this._oldProps, oldMtx = oldProps&&oldProps.matrix; var n = 10000; // precision this._devicePixelRatio = p._getDevicePixelRatio(); if (!oldMtx || !oldMtx.equals(mtx)) { var str = "matrix(" + (mtx.a*n|0)/n/this._devicePixelRatio +","+ (mtx.b*n|0)/n +","+ (mtx.c*n|0)/n +","+ (mtx.d*n|0)/n/this._devicePixelRatio +","+ (mtx.tx+0.5|0)/this._devicePixelRatio; style.transform = style.WebkitTransform = style.OTransform = style.msTransform = str +","+ (mtx.ty+0.5|0)/this._devicePixelRatio +")"; style.MozTransform = str +"px,"+ (mtx.ty+0.5|0)/this._devicePixelRatio +"px)"; if (!oldProps) { oldProps = this._oldProps = new createjs.DisplayProps(true, NaN); } oldProps.matrix.copy(mtx); } if (oldProps.alpha != props.alpha) { style.opacity = ""+(props.alpha*n|0)/n; oldProps.alpha = props.alpha; } }; /** * @method _getDevicePixelRatio * @protected */ p._getDevicePixelRatio = function() { var ratio = 1; // To account for zoom, change to use deviceXDPI instead of systemXDPI if (window.screen.systemXDPI != null && window.screen.logicalXDPI != null && window.screen.systemXDPI > window.screen.logicalXDPI) { // Only allow for values > 1 ratio = window.screen.systemXDPI / window.screen.logicalXDPI; } else if (window.devicePixelRatio != null) { ratio = window.devicePixelRatio; } return ratio; }; //---------- createjs.DOMElement = createjs.promote(DOMElement, "DisplayObject"); }()); //############################################################################## // Filter.js //############################################################################## (function() { "use strict"; // constructor: /** * Base class that all filters should inherit from. Filters need to be applied to objects that have been cached using * the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. If an object changes, please cache it again, or use * {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching. * *

    Example

    * * myInstance.filters = [ * new createjs.ColorFilter(0, 0, 0, 1, 255, 0, 0), * new createjs.BlurFilter(5, 5, 10) * ]; * myInstance.cache(0,0, 100, 100); * * Note that each filter can implement a {{#crossLink "Filter/getBounds"}}{{/crossLink}} method, which returns the * margins that need to be applied in order to fully display the filter. For example, the {{#crossLink "BlurFilter"}}{{/crossLink}} * will cause an object to feather outwards, resulting in a margin around the shape. * *

    EaselJS Filters

    * EaselJS comes with a number of pre-built filters: *
    • {{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object
    • *
    • {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object
    • *
    • {{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object
    • *
    • {{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object
    • *
    • {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}
    • *
    * * @class Filter * @constructor **/ function Filter() { /** * A flag stating that this filter uses a context draw mode and cannot be batched into imageData processing. * @property usesContext * @type {boolean} * @default false */ this.usesContext = false; /** * Another filter that is required to act as part of this filter and created and managed under the hood. * @private * @property _multiPass * @type {Filter} * @default null */ this._multiPass = null; /** * Pre-processed template shader code. It will be parsed before being fed in into the shader compiler. * This should be based upon StageGL.SHADER_VERTEX_BODY_REGULAR * @property VTX_SHADER * @virtual * @type {String} * @readonly */ this.VTX_SHADER_BODY = null; /** * Pre-processed template shader code. It will be parsed before being fed in into the shader compiler. * This should be based upon StageGL.SHADER_FRAGMENT_BODY_REGULAR * @property FRAG_SHADER * @virtual * @type {String} * @readonly */ this.FRAG_SHADER_BODY = null; } var p = Filter.prototype; // public methods: /** * Provides padding values for this filter. That is, how much the filter will extend the visual bounds of an object it is applied to. * @method getBounds * @param {Rectangle} [rect] If specified, the provided Rectangle instance will be expanded by the padding amounts and returned. * @return {Rectangle} If a `rect` param was provided, it is returned. If not, either a new rectangle with the padding values, or null if no padding is required for this filter. **/ p.getBounds = function(rect) { return rect; }; /** * Assign any unique uniforms or other setup functionality here. * @method shaderParamSetup * @virtual * @param {WebGLContext} gl The context associated with the stage performing the render. * @param {StageGL} stage The stage instance that will be rendering. * @param {ShaderProgram} shaderProgram The compiled shader that is going to be used to perform the render. */ p.shaderParamSetup = function(gl, stage, shaderProgram) {}; /** * Applies the filter to the specified context. * @method applyFilter * @param {CanvasRenderingContext2D} ctx The 2D context to use as the source. * @param {Number} x The x position to use for the source rect. * @param {Number} y The y position to use for the source rect. * @param {Number} width The width to use for the source rect. * @param {Number} height The height to use for the source rect. * @param {CanvasRenderingContext2D} [targetCtx] The 2D context to draw the result to. Defaults to the context passed to ctx. * @param {Number} [targetX] The x position to draw the result to. Defaults to the value passed to x. * @param {Number} [targetY] The y position to draw the result to. Defaults to the value passed to y. * @return {Boolean} If the filter was applied successfully. **/ p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) { // this is the default behaviour because most filters access pixel data. It is overridden when not needed. targetCtx = targetCtx || ctx; if (targetX == null) { targetX = x; } if (targetY == null) { targetY = y; } try { var imageData = ctx.getImageData(x, y, width, height); } catch (e) { return false; } if (this._applyFilter(imageData)) { targetCtx.putImageData(imageData, targetX, targetY); return true; } return false; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Filter]"; }; /** * Returns a clone of this Filter instance. * @method clone * @return {Filter} A clone of the current Filter instance. **/ p.clone = function() { return new Filter(); }; // private methods: /** * @method _applyFilter * @param {ImageData} imageData Target ImageData instance. * @return {Boolean} **/ p._applyFilter = function(imageData) { return true; }; createjs.Filter = Filter; }()); //############################################################################## // BitmapCache.js //############################################################################## (function() { "use strict"; // constructor: /** * The BitmapCache is an internal representation of all the cache properties and logic required in order to "cache" * an object. This information and functionality used to be located on a {{#crossLink "DisplayObject/cache"}}{{/crossLink}} * method in {{#crossLink "DisplayObject"}}{{/crossLink}}, but was moved to its own class. * * Caching in this context is purely visual, and will render the DisplayObject out into an image to be used instead * of the object. The actual cache itself is still stored on the target with the {{#crossLink "DisplayObject/cacheCanvas:property"}}{{/crossLink}}. * Working with a singular image like a {{#crossLink "Bitmap"}}{{/crossLink}} there is little benefit to performing * a cache as it is already a single image. Caching is best done on containers containing multiple complex parts that * do not move often, so that rendering the image instead will improve overall rendering speed. A cached object will * not visually update until explicitly told to do so with a call to update, much like a Stage. If a cache is being * updated every frame it is likely not improving rendering performance. Cache are best used when updates will be sparse. * * Caching is also a co-requisite for applying filters to prevent expensive filters running constantly without need, * and to physically enable some effects. The BitmapCache is also responsible for applying filters to objects and * reads each {{#crossLink "Filter"}}{{/crossLink}} due to this relationship. Real-time Filters are not recommended * performance wise when dealing with a Context2D canvas. For best performance and to still allow for some visual * effects use a compositeOperation when possible. * @class BitmapCache * @constructor **/ function BitmapCache() { // public: /** * Width of the cache relative to the target object. * @property width * @protected * @type {Number} * @default undefined **/ this.width = undefined; /** * Height of the cache relative to the target object. * @property height * @protected * @type {Number} * @default undefined * @todo Should the width and height be protected? **/ this.height = undefined; /** * Horizontal position of the cache relative to the target's origin. * @property x * @protected * @type {Number} * @default undefined **/ this.x = undefined; /** * Vertical position of the cache relative to target's origin. * @property y * @protected * @type {Number} * @default undefined **/ this.y = undefined; /** * The internal scale of the cache image, does not affects display size. This is useful to both increase and * decrease render quality. Objects with increased scales are more likely to look good when scaled up or rotated. * Objects with decreased scales can save on rendering performance. * @property scale * @protected * @type {Number} * @default 1 **/ this.scale = 1; /** * The x offset used for drawing into the cache itself, accounts for both transforms applied. * @property offX * @protected * @type {Number} * @default 0 **/ this.offX = 0; /** * The y offset used for drawing into the cache itself, accounts for both transforms applied. * @property offY * @protected * @type {Number} * @default 0 **/ this.offY = 0; /** * Track how many times the cache has been updated, mostly used for preventing duplicate cacheURLs. * This can be useful to see if a cache has been updated. * @property cacheID * @type {Number} * @default 0 **/ this.cacheID = 0; // protected: /** * The relative offset of the filter's x position, used for drawing the cache onto its container. * Re-calculated every update call before drawing. * @property _filterOffY * @protected * @type {Number} * @default 0 **/ this._filterOffX = 0; /** * The relative offset of the filter's y position, used for drawing the cache onto its container. * Re-calculated every update call before drawing. * @property _filterOffY * @protected * @type {Number} * @default 0 **/ this._filterOffY = 0; /** * The cacheID when a DataURL was requested. * @property _cacheDataURLID * @protected * @type {Number} * @default 0 **/ this._cacheDataURLID = 0; /** * The cache's DataURL, generated on-demand using the getter. * @property _cacheDataURL * @protected * @type {String} * @default null **/ this._cacheDataURL = null; /** * Internal tracking of final bounding width, approximately width*scale; however, filters can complicate the actual value. * @property _drawWidth * @protected * @type {Number} * @default 0 **/ this._drawWidth = 0; /** * Internal tracking of final bounding height, approximately height*scale; however, filters can complicate the actual value. * @property _drawHeight * @protected * @type {Number} * @default 0 **/ this._drawHeight = 0; } var p = BitmapCache.prototype; /** * Returns the bounds that surround all applied filters, relies on each filter to describe how it changes bounds. * @method getFilterBounds * @param {DisplayObject} target The object to check the filter bounds for. * @param {Rectangle} [output=null] Optional parameter, if provided then calculated bounds will be applied to that object. * @return {Rectangle} bounds object representing the bounds with filters. * @static **/ BitmapCache.getFilterBounds = function(target, output) { if(!output){ output = new createjs.Rectangle(); } var filters = target.filters; var filterCount = filters && filters.length; if (!!filterCount <= 0) { return output; } for(var i=0; iWebGL cache with a 2D context * * var stage = new createjs.Stage(); * var bmp = new createjs.Bitmap(src); * bmp.cache(0, 0, bmp.width, bmp.height, 1, {gl: "new"}); // no StageGL to use, so make one * * var shape = new createjs.Shape(); * shape.graphics.clear().fill("red").drawRect(0,0,20,20); * shape.cache(0, 0, 20, 20, 1); // cannot use WebGL cache * *

    WebGL cache with a WebGL context

    * * var stageGL = new createjs.StageGL(); * var bmp = new createjs.Bitmap(src); * bmp.cache(0, 0, bmp.width, bmp.height, 1, {gl: "stage"}); // use our StageGL to cache * * var shape = new createjs.Shape(); * shape.graphics.clear().fill("red").drawRect(0,0,20,20); * shape.cache(0, 0, 20, 20, 1); // cannot use WebGL cache * * You may wish to create your own StageGL instance to control factors like clear color, transparency, AA, and * others. If you do, pass a new instance in instead of "true", the library will automatically set the * {{#crossLink "StageGL/isCacheControlled"}}{{/crossLink}} to true on your instance. This will trigger it to behave * correctly, and not assume your main context is WebGL. * * @public * @method BitmapCache.cache * @param {Number} x The x coordinate origin for the cache region. * @param {Number} y The y coordinate origin for the cache region. * @param {Number} width The width of the cache region. * @param {Number} height The height of the cache region. * @param {Number} [scale=1] The scale at which the cache will be created. For example, if you cache a vector shape * using myShape.cache(0,0,100,100,2) then the resulting cacheCanvas will be 200x200 px. This lets you scale and * rotate cached elements with greater fidelity. Default is 1. * @param {Object} [options=undefined] Specify additional parameters for the cache logic * @param {undefined|"new"|"stage"|StageGL} [options.useGL=undefined] Select whether to use context 2D, or WebGL rendering, and * whether to make a new stage instance or use an existing one. See above for extensive details on use. * @for BitmapCache */ p.define = function(target, x, y, width, height, scale, options) { if(!target){ throw "No symbol to cache"; } this._options = options; this.target = target; this.width = width >= 1 ? width : 1; this.height = height >= 1 ? height : 1; this.x = x || 0; this.y = y || 0; this.scale = scale || 1; this.update(); }; /** * Directly called via {{#crossLink "DisplayObject/updateCache:method"}}{{/crossLink}}, but also internally. This * has the dual responsibility of making sure the surface is ready to be drawn to, and performing the draw. For * full details of each behaviour, check the protected functions {{#crossLink "BitmapCache/_updateSurface"}}{{/crossLink}} * and {{#crossLink "BitmapCache/_drawToCache"}}{{/crossLink}} respectively. * @method update * @param {String} [compositeOperation=null] The DisplayObject this cache is linked to. **/ p.update = function(compositeOperation) { if(!this.target) { throw "define() must be called before update()"; } var filterBounds = BitmapCache.getFilterBounds(this.target); var surface = this.target.cacheCanvas; this._drawWidth = Math.ceil(this.width*this.scale) + filterBounds.width; this._drawHeight = Math.ceil(this.height*this.scale) + filterBounds.height; if(!surface || this._drawWidth != surface.width || this._drawHeight != surface.height) { this._updateSurface(); } this._filterOffX = filterBounds.x; this._filterOffY = filterBounds.y; this.offX = this.x*this.scale + this._filterOffX; this.offY = this.y*this.scale + this._filterOffY; this._drawToCache(compositeOperation); this.cacheID = this.cacheID?this.cacheID+1:1; }; /** * Reset and release all the properties and memory associated with this cache. * @method release **/ p.release = function() { if (this._webGLCache) { // if it isn't cache controlled clean up after yourself if (!this._webGLCache.isCacheControlled) { if (this.__lastRT){ this.__lastRT = undefined; } if (this.__rtA){ this._webGLCache._killTextureObject(this.__rtA); } if (this.__rtB){ this._webGLCache._killTextureObject(this.__rtB); } if (this.target && this.target.cacheCanvas){ this._webGLCache._killTextureObject(this.target.cacheCanvas); } } // set the context to none and let the garbage collector get the rest when the canvas itself gets removed this._webGLCache = false; } else { var stage = this.target.stage; if (stage instanceof createjs.StageGL){ stage.releaseTexture(this.target.cacheCanvas); } } this.target = this.target.cacheCanvas = null; this.cacheID = this._cacheDataURLID = this._cacheDataURL = undefined; this.width = this.height = this.x = this.y = this.offX = this.offY = 0; this.scale = 1; }; /** * Returns a data URL for the cache, or `null` if this display object is not cached. * Uses {{#crossLink "BitmapCache/cacheID:property"}}{{/crossLink}} to ensure a new data URL is not generated if the * cache has not changed. * @method getCacheDataURL * @return {String} The image data url for the cache. **/ p.getCacheDataURL = function() { var cacheCanvas = this.target && this.target.cacheCanvas; if (!cacheCanvas) { return null; } if (this.cacheID != this._cacheDataURLID) { this._cacheDataURLID = this.cacheID; this._cacheDataURL = cacheCanvas.toDataURL?cacheCanvas.toDataURL():null; // incase function is } return this._cacheDataURL; }; /** * Use context2D drawing commands to display the cache canvas being used. * @method draw * @param {CanvasRenderingContext2D} ctx The context to draw into. * @return {Boolean} Whether the draw was handled successfully. **/ p.draw = function(ctx) { if(!this.target) { return false; } ctx.drawImage(this.target.cacheCanvas, this.x + (this._filterOffX/this.scale), this.y + (this._filterOffY/this.scale), this._drawWidth/this.scale, this._drawHeight/this.scale ); return true; }; // private methods: /** * Create or resize the invisible canvas/surface that is needed for the display object(s) to draw to, * and in turn be used in their stead when drawing. The surface is resized to the size defined * by the width and height, factoring in scaling and filters. Adjust them to adjust the output size. * @method _updateSurface * @protected **/ p._updateSurface = function() { if (!this._options || !this._options.useGL) { var surface = this.target.cacheCanvas; // create it if it's missing if(!surface) { surface = this.target.cacheCanvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"); } // now size it surface.width = this._drawWidth; surface.height = this._drawHeight; return; } // create it if it's missing if (!this._webGLCache) { if (this._options.useGL === "stage") { if(!(this.target.stage && this.target.stage.isWebGL)){ var error = "Cannot use 'stage' for cache because the object's parent stage is "; error += this.target.stage ? "non WebGL." : "not set, please addChild to the correct stage."; throw error; } this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks this._webGLCache = this.target.stage; } else if(this._options.useGL === "new") { this.target.cacheCanvas = document.createElement("canvas"); // we can turn off autopurge because we wont be making textures here this._webGLCache = new createjs.StageGL(this.target.cacheCanvas, {antialias: true, transparent: true, autoPurge: -1}); this._webGLCache.isCacheControlled = true; // use this flag to control stage sizing and final output } else if(this._options.useGL instanceof createjs.StageGL) { this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks this._webGLCache = this._options.useGL; this._webGLCache.isCacheControlled = true; // use this flag to control stage sizing and final output } else { throw "Invalid option provided to useGL, expected ['stage', 'new', StageGL, undefined], got "+ this._options.useGL; } } // now size render surfaces var surface = this.target.cacheCanvas; var stageGL = this._webGLCache; // if we have a dedicated stage we've gotta size it if (stageGL.isCacheControlled) { surface.width = this._drawWidth; surface.height = this._drawHeight; stageGL.updateViewport(this._drawWidth, this._drawHeight); } if (this.target.filters) { // with filters we can't tell how many we'll need but the most we'll ever need is two, so make them now stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight); stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight); } else { // without filters then we only need one RenderTexture, and that's only if its not a dedicated stage if (!stageGL.isCacheControlled) { stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight); } } }; /** * Perform the cache draw out for context 2D now that the setup properties have been performed. * @method _drawToCache * @protected **/ p._drawToCache = function(compositeOperation) { var surface = this.target.cacheCanvas; var target = this.target; var webGL = this._webGLCache; if (webGL){ //TODO: auto split blur into an x/y pass webGL.cacheDraw(target, target.filters, this); // we may of swapped around which element the surface is, so we re-fetch it surface = this.target.cacheCanvas; surface.width = this._drawWidth; surface.height = this._drawHeight; } else { var ctx = surface.getContext("2d"); if (!compositeOperation) { ctx.clearRect(0, 0, this._drawWidth+1, this._drawHeight+1); } ctx.save(); ctx.globalCompositeOperation = compositeOperation; ctx.setTransform(this.scale,0,0,this.scale, -this._filterOffX,-this._filterOffY); ctx.translate(-this.x, -this.y); target.draw(ctx, true); ctx.restore(); if (target.filters && target.filters.length) { this._applyFilters(ctx); } } surface._invalid = true; }; /** * Work through every filter and apply its individual visual transformation. * @method _applyFilters * @protected **/ p._applyFilters = function(ctx) { var filters = this.target.filters; var w = this._drawWidth; var h = this._drawHeight; var data; var i = 0, filter = filters[i]; do { // this is safe because we wouldn't be in apply filters without a filter count of at least 1 if(filter.usesContext){ if(data) { ctx.putImageData(data, 0,0); data = null; } filter.applyFilter(ctx, 0,0, w,h); } else { if(!data) { data = ctx.getImageData(0,0, w,h); } filter._applyFilter(data); } // work through the multipass if it's there, otherwise move on filter = filter._multiPass !== null ? filter._multiPass : filters[++i]; } while (filter); //done if(data) { ctx.putImageData(data, 0,0); } }; createjs.BitmapCache = BitmapCache; }()); //############################################################################## // BlurFilter.js //############################################################################## (function() { "use strict"; // constructor: /** * Applies a box blur to DisplayObjects in context 2D and a Gaussian blur in webgl. Note that this filter is fairly * intensive, particularly if the quality is set higher than 1. * *

    Example

    * This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}} * method to account for the spread that the blur causes. * * var shape = new createjs.Shape().set({x:100,y:100}); * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); * * var blurFilter = new createjs.BlurFilter(5, 5, 1); * shape.filters = [blurFilter]; * var bounds = blurFilter.getBounds(); * * shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height); * * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. * @class BlurFilter * @extends Filter * @constructor * @param {Number} [blurX=0] The horizontal blur radius in pixels. * @param {Number} [blurY=0] The vertical blur radius in pixels. * @param {Number} [quality=1] The number of blur iterations. **/ function BlurFilter( blurX, blurY, quality) { this.Filter_constructor(); // public properties: /** * Horizontal blur radius in pixels * @property blurX * @default 0 * @type Number **/ this._blurX = blurX; this._blurXTable = []; this._lastBlurX = null; /** * Vertical blur radius in pixels * @property blurY * @default 0 * @type Number **/ this._blurY = blurY; this._blurYTable = []; this._lastBlurY = null; /** * Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a * smoother blur, but take twice as long to run. * @property quality * @default 1 * @type Number **/ this._quality; this._lastQuality = null; /** * This is a template to generate the shader for {{#crossLink FRAG_SHADER_BODY}}{{/crossLink}} */ this.FRAG_SHADER_TEMPLATE = ( "uniform float xWeight[{{blurX}}];" + "uniform float yWeight[{{blurY}}];" + "uniform vec2 textureOffset;" + "void main(void) {" + "vec4 color = vec4(0.0);" + "float xAdj = ({{blurX}}.0-1.0)/2.0;" + "float yAdj = ({{blurY}}.0-1.0)/2.0;" + "vec2 sampleOffset;" + "for(int i=0; i<{{blurX}}; i++) {" + "for(int j=0; j<{{blurY}}; j++) {" + "sampleOffset = vRenderCoord + (textureOffset * vec2(float(i)-xAdj, float(j)-yAdj));" + "color += texture2D(uSampler, sampleOffset) * (xWeight[i] * yWeight[j]);" + "}" + "}" + "gl_FragColor = color.rgba;" + "}" ); // update the filter using the setters if(isNaN(quality) || quality < 1){ quality = 1; } this.setQuality(quality|0); } var p = createjs.extend(BlurFilter, createjs.Filter); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. p.getBlurX = function() { return this._blurX; }; p.getBlurY = function() { return this._blurY; }; p.setBlurX = function(value) { if(isNaN(value) || value < 0){ value = 0; } this._blurX = value; }; p.setBlurY = function(value) { if(isNaN(value) || value < 0){ value = 0; } this._blurY = value; }; p.getQuality = function() { return this._quality; }; p.setQuality = function(value) { if(isNaN(value) || value < 0){ value = 0; } this._quality = value | 0; }; p._getShader = function() { var xChange = this._lastBlurX !== this._blurX; var yChange = this._lastBlurY !== this._blurY; var qChange = this._lastQuality !== this._quality; if(xChange || yChange || qChange) { if(xChange || qChange) { this._blurXTable = this._getTable(this._blurX * this._quality); } if(yChange || qChange) { this._blurYTable = this._getTable(this._blurY * this._quality); } this._updateShader(); this._lastBlurX = this._blurX; this._lastBlurY = this._blurY; this._lastQuality = this._quality; return undefined; // force a rebuild } return this._compiledShader; }; p._setShader = function() { this._compiledShader; }; try { Object.defineProperties(p, { blurX: { get: p.getBlurX, set: p.setBlurX }, blurY: { get: p.getBlurY, set: p.setBlurY }, quality: { get: p.getQuality, set: p.setQuality }, _builtShader: { get: p._getShader, set: p._setShader} }); } catch (e) { console.log(e); } /** * Internal lookup function to create gaussian distribution. * @method _getTable * @param {Number} spread How many steps in the curve. * @return {Array} An array with Math.ceil(spread*2) entries with appropriately distributed weights. */ p._getTable = function(spread) { var EDGE = 4.2; if(spread<=1) { return [1]; } var result = []; var count = Math.ceil(spread*2); count += (count%2)?0:1; var adjust = (count/2)|0; for(var i = -adjust; i<=adjust; i++) { var x = (i/adjust)*EDGE; result.push(1/Math.sqrt(2*Math.PI) * Math.pow(Math.E, -(Math.pow(x,2)/4))); } var factor = result.reduce(function(a, b) { return a + b; }); return result.map(function(currentValue, index, array) { return currentValue/factor; }); }; /** * Internal update function to create shader properties. * @method _updateShader */ p._updateShader = function() { if(this._blurX === undefined || this._blurY === undefined){ return; } var result = this.FRAG_SHADER_TEMPLATE; result = result.replace(/\{\{blurX\}\}/g, (this._blurXTable.length).toFixed(0)); result = result.replace(/\{\{blurY\}\}/g, (this._blurYTable.length).toFixed(0)); this.FRAG_SHADER_BODY = result; }; /** docced in super class **/ p.shaderParamSetup = function(gl, stage, shaderProgram) { // load the normalized gaussian weight tables gl.uniform1fv( gl.getUniformLocation(shaderProgram, "xWeight"), this._blurXTable ); gl.uniform1fv( gl.getUniformLocation(shaderProgram, "yWeight"), this._blurYTable ); // what is the size of a single pixel in -1, 1 (webGL) space gl.uniform2f( gl.getUniformLocation(shaderProgram, "textureOffset"), 2/(stage._viewportWidth*this._quality), 2/(stage._viewportHeight*this._quality) ); }; // constants: /** * Array of multiply values for blur calculations. * @property MUL_TABLE * @type Array * @protected * @static **/ BlurFilter.MUL_TABLE = [1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265, 497, 469, 443, 421, 25, 191, 365, 349, 335, 161, 155, 149, 9, 278, 269, 261, 505, 245, 475, 231, 449, 437, 213, 415, 405, 395, 193, 377, 369, 361, 353, 345, 169, 331, 325, 319, 313, 307, 301, 37, 145, 285, 281, 69, 271, 267, 263, 259, 509, 501, 493, 243, 479, 118, 465, 459, 113, 446, 55, 435, 429, 423, 209, 413, 51, 403, 199, 393, 97, 3, 379, 375, 371, 367, 363, 359, 355, 351, 347, 43, 85, 337, 333, 165, 327, 323, 5, 317, 157, 311, 77, 305, 303, 75, 297, 294, 73, 289, 287, 71, 141, 279, 277, 275, 68, 135, 67, 133, 33, 262, 260, 129, 511, 507, 503, 499, 495, 491, 61, 121, 481, 477, 237, 235, 467, 232, 115, 457, 227, 451, 7, 445, 221, 439, 218, 433, 215, 427, 425, 211, 419, 417, 207, 411, 409, 203, 202, 401, 399, 396, 197, 49, 389, 387, 385, 383, 95, 189, 47, 187, 93, 185, 23, 183, 91, 181, 45, 179, 89, 177, 11, 175, 87, 173, 345, 343, 341, 339, 337, 21, 167, 83, 331, 329, 327, 163, 81, 323, 321, 319, 159, 79, 315, 313, 39, 155, 309, 307, 153, 305, 303, 151, 75, 299, 149, 37, 295, 147, 73, 291, 145, 289, 287, 143, 285, 71, 141, 281, 35, 279, 139, 69, 275, 137, 273, 17, 271, 135, 269, 267, 133, 265, 33, 263, 131, 261, 130, 259, 129, 257, 1]; /** * Array of shift values for blur calculations. * @property SHG_TABLE * @type Array * @protected * @static **/ BlurFilter.SHG_TABLE = [0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13, 14, 14, 14, 14, 10, 13, 14, 14, 14, 13, 13, 13, 9, 14, 14, 14, 15, 14, 15, 14, 15, 15, 14, 15, 15, 15, 14, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 12, 14, 15, 15, 13, 15, 15, 15, 15, 16, 16, 16, 15, 16, 14, 16, 16, 14, 16, 13, 16, 16, 16, 15, 16, 13, 16, 15, 16, 14, 9, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 14, 16, 16, 15, 16, 16, 10, 16, 15, 16, 14, 16, 16, 14, 16, 16, 14, 16, 16, 14, 15, 16, 16, 16, 14, 15, 14, 15, 13, 16, 16, 15, 17, 17, 17, 17, 17, 17, 14, 15, 17, 17, 16, 16, 17, 16, 15, 17, 16, 17, 11, 17, 16, 17, 16, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 16, 17, 17, 17, 16, 14, 17, 17, 17, 17, 15, 16, 14, 16, 15, 16, 13, 16, 15, 16, 14, 16, 15, 16, 12, 16, 15, 16, 17, 17, 17, 17, 17, 13, 16, 15, 17, 17, 17, 16, 15, 17, 17, 17, 16, 15, 17, 17, 14, 16, 17, 17, 16, 17, 17, 16, 15, 17, 16, 14, 17, 16, 15, 17, 16, 17, 17, 16, 17, 15, 16, 17, 14, 17, 16, 15, 17, 16, 17, 13, 17, 16, 17, 17, 16, 17, 14, 17, 16, 17, 16, 17, 16, 17, 9]; // public methods: /** docced in super class **/ p.getBounds = function (rect) { var x = this.blurX|0, y = this.blurY| 0; if(x <= 0 && y <= 0) { return rect; } var q = Math.pow(this.quality, 0.2); return (rect || new createjs.Rectangle()).pad(y*q+1,x*q+1,y*q+1,x*q+1); }; /** docced in super class **/ p.clone = function() { return new BlurFilter(this.blurX, this.blurY, this.quality); }; /** docced in super class **/ p.toString = function() { return "[BlurFilter]"; }; // private methods: /** docced in super class **/ p._applyFilter = function (imageData) { var radiusX = this._blurX >> 1; if (isNaN(radiusX) || radiusX < 0) return false; var radiusY = this._blurY >> 1; if (isNaN(radiusY) || radiusY < 0) return false; if (radiusX == 0 && radiusY == 0) return false; var iterations = this.quality; if (isNaN(iterations) || iterations < 1) iterations = 1; iterations |= 0; if (iterations > 3) iterations = 3; if (iterations < 1) iterations = 1; var px = imageData.data; var x=0, y=0, i=0, p=0, yp=0, yi=0, yw=0, r=0, g=0, b=0, a=0, pr=0, pg=0, pb=0, pa=0; var divx = (radiusX + radiusX + 1) | 0; var divy = (radiusY + radiusY + 1) | 0; var w = imageData.width | 0; var h = imageData.height | 0; var w1 = (w - 1) | 0; var h1 = (h - 1) | 0; var rxp1 = (radiusX + 1) | 0; var ryp1 = (radiusY + 1) | 0; var ssx = {r:0,b:0,g:0,a:0}; var sx = ssx; for ( i = 1; i < divx; i++ ) { sx = sx.n = {r:0,b:0,g:0,a:0}; } sx.n = ssx; var ssy = {r:0,b:0,g:0,a:0}; var sy = ssy; for ( i = 1; i < divy; i++ ) { sy = sy.n = {r:0,b:0,g:0,a:0}; } sy.n = ssy; var si = null; var mtx = BlurFilter.MUL_TABLE[radiusX] | 0; var stx = BlurFilter.SHG_TABLE[radiusX] | 0; var mty = BlurFilter.MUL_TABLE[radiusY] | 0; var sty = BlurFilter.SHG_TABLE[radiusY] | 0; while (iterations-- > 0) { yw = yi = 0; var ms = mtx; var ss = stx; for (y = h; --y > -1;) { r = rxp1 * (pr = px[(yi) | 0]); g = rxp1 * (pg = px[(yi + 1) | 0]); b = rxp1 * (pb = px[(yi + 2) | 0]); a = rxp1 * (pa = px[(yi + 3) | 0]); sx = ssx; for( i = rxp1; --i > -1; ) { sx.r = pr; sx.g = pg; sx.b = pb; sx.a = pa; sx = sx.n; } for( i = 1; i < rxp1; i++ ) { p = (yi + ((w1 < i ? w1 : i) << 2)) | 0; r += ( sx.r = px[p]); g += ( sx.g = px[p+1]); b += ( sx.b = px[p+2]); a += ( sx.a = px[p+3]); sx = sx.n; } si = ssx; for ( x = 0; x < w; x++ ) { px[yi++] = (r * ms) >>> ss; px[yi++] = (g * ms) >>> ss; px[yi++] = (b * ms) >>> ss; px[yi++] = (a * ms) >>> ss; p = ((yw + ((p = x + radiusX + 1) < w1 ? p : w1)) << 2); r -= si.r - ( si.r = px[p]); g -= si.g - ( si.g = px[p+1]); b -= si.b - ( si.b = px[p+2]); a -= si.a - ( si.a = px[p+3]); si = si.n; } yw += w; } ms = mty; ss = sty; for (x = 0; x < w; x++) { yi = (x << 2) | 0; r = (ryp1 * (pr = px[yi])) | 0; g = (ryp1 * (pg = px[(yi + 1) | 0])) | 0; b = (ryp1 * (pb = px[(yi + 2) | 0])) | 0; a = (ryp1 * (pa = px[(yi + 3) | 0])) | 0; sy = ssy; for( i = 0; i < ryp1; i++ ) { sy.r = pr; sy.g = pg; sy.b = pb; sy.a = pa; sy = sy.n; } yp = w; for( i = 1; i <= radiusY; i++ ) { yi = ( yp + x ) << 2; r += ( sy.r = px[yi]); g += ( sy.g = px[yi+1]); b += ( sy.b = px[yi+2]); a += ( sy.a = px[yi+3]); sy = sy.n; if( i < h1 ) { yp += w; } } yi = x; si = ssy; if ( iterations > 0 ) { for ( y = 0; y < h; y++ ) { p = yi << 2; px[p+3] = pa =(a * ms) >>> ss; if ( pa > 0 ) { px[p] = ((r * ms) >>> ss ); px[p+1] = ((g * ms) >>> ss ); px[p+2] = ((b * ms) >>> ss ); } else { px[p] = px[p+1] = px[p+2] = 0 } p = ( x + (( ( p = y + ryp1) < h1 ? p : h1 ) * w )) << 2; r -= si.r - ( si.r = px[p]); g -= si.g - ( si.g = px[p+1]); b -= si.b - ( si.b = px[p+2]); a -= si.a - ( si.a = px[p+3]); si = si.n; yi += w; } } else { for ( y = 0; y < h; y++ ) { p = yi << 2; px[p+3] = pa =(a * ms) >>> ss; if ( pa > 0 ) { pa = 255 / pa; px[p] = ((r * ms) >>> ss ) * pa; px[p+1] = ((g * ms) >>> ss ) * pa; px[p+2] = ((b * ms) >>> ss ) * pa; } else { px[p] = px[p+1] = px[p+2] = 0 } p = ( x + (( ( p = y + ryp1) < h1 ? p : h1 ) * w )) << 2; r -= si.r - ( si.r = px[p]); g -= si.g - ( si.g = px[p+1]); b -= si.b - ( si.b = px[p+2]); a -= si.a - ( si.a = px[p+3]); si = si.n; yi += w; } } } } return true; }; createjs.BlurFilter = createjs.promote(BlurFilter, "Filter"); }()); //############################################################################## // AlphaMapFilter.js //############################################################################## (function () { "use strict"; // constructor: /** * Applies a greyscale alpha map image (or canvas) to the target, such that the alpha channel of the result will * be copied from the red channel of the map, and the RGB channels will be copied from the target. * * Generally, it is recommended that you use {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}, because it has much * better performance. * *

    Example

    * This example draws a red->blue box, caches it, and then uses the cache canvas as an alpha map on a 100x100 image. * * var box = new createjs.Shape(); * box.graphics.beginLinearGradientFill(["#ff0000", "#0000ff"], [0, 1], 0, 0, 0, 100) * box.graphics.drawRect(0, 0, 100, 100); * box.cache(0, 0, 100, 100); * * var bmp = new createjs.Bitmap("path/to/image.jpg"); * bmp.filters = [ * new createjs.AlphaMapFilter(box.cacheCanvas) * ]; * bmp.cache(0, 0, 100, 100); * stage.addChild(bmp); * * See {{#crossLink "Filter"}}{{/crossLink}} for more information on applying filters. * @class AlphaMapFilter * @extends Filter * @constructor * @param {HTMLImageElement|HTMLCanvasElement} alphaMap The greyscale image (or canvas) to use as the alpha value for the * result. This should be exactly the same dimensions as the target. **/ function AlphaMapFilter(alphaMap) { this.Filter_constructor(); // public properties: /** * The greyscale image (or canvas) to use as the alpha value for the result. This should be exactly the same * dimensions as the target. * @property alphaMap * @type HTMLImageElement|HTMLCanvasElement **/ this.alphaMap = alphaMap; // private properties: /** * @property _alphaMap * @protected * @type HTMLImageElement|HTMLCanvasElement **/ this._alphaMap = null; /** * @property _mapData * @protected * @type Uint8ClampedArray **/ this._mapData = null; this._mapTexture = null; this.FRAG_SHADER_BODY = ( "uniform sampler2D uAlphaSampler;"+ "void main(void) {" + "vec4 color = texture2D(uSampler, vRenderCoord);" + "vec4 alphaMap = texture2D(uAlphaSampler, vTextureCoord);" + // some image formats can have transparent white rgba(1,1,1, 0) when put on the GPU, this means we need a slight tweak // using ceil ensure that the colour will be used so long as it exists but pure transparency will be treated black "gl_FragColor = vec4(color.rgb, color.a * (alphaMap.r * ceil(alphaMap.a)));" + "}" ); } var p = createjs.extend(AlphaMapFilter, createjs.Filter); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. /** docced in super class **/ p.shaderParamSetup = function(gl, stage, shaderProgram) { if(!this._mapTexture) { this._mapTexture = gl.createTexture(); } gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, this._mapTexture); stage.setTextureParams(gl); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.alphaMap); gl.uniform1i( gl.getUniformLocation(shaderProgram, "uAlphaSampler"), 1 ); }; // public methods: /** docced in super class **/ p.clone = function () { var o = new AlphaMapFilter(this.alphaMap); o._alphaMap = this._alphaMap; o._mapData = this._mapData; return o; }; /** docced in super class **/ p.toString = function () { return "[AlphaMapFilter]"; }; // private methods: /** docced in super class **/ p._applyFilter = function (imageData) { if (!this.alphaMap) { return true; } if (!this._prepAlphaMap()) { return false; } // TODO: update to support scenarios where the target has different dimensions. var data = imageData.data; var map = this._mapData; for(var i=0, l=data.length; iIMPORTANT NOTE: This filter currently does not support the targetCtx, or targetX/Y parameters correctly. * *

    Example

    * This example draws a gradient box, then caches it and uses the "cacheCanvas" as the alpha mask on a 100x100 image. * * var box = new createjs.Shape(); * box.graphics.beginLinearGradientFill(["#000000", "rgba(0, 0, 0, 0)"], [0, 1], 0, 0, 100, 100) * box.graphics.drawRect(0, 0, 100, 100); * box.cache(0, 0, 100, 100); * * var bmp = new createjs.Bitmap("path/to/image.jpg"); * bmp.filters = [ * new createjs.AlphaMaskFilter(box.cacheCanvas) * ]; * bmp.cache(0, 0, 100, 100); * * See {{#crossLink "Filter"}}{{/crossLink}} for more information on applying filters. * @class AlphaMaskFilter * @extends Filter * @constructor * @param {HTMLImageElement|HTMLCanvasElement} mask **/ function AlphaMaskFilter(mask) { this.Filter_constructor(); // public properties: /** * The image (or canvas) to use as the mask. * @property mask * @type HTMLImageElement|HTMLCanvasElement **/ this.mask = mask; /** docced in super class **/ this.usesContext = true; this.FRAG_SHADER_BODY = ( "uniform sampler2D uAlphaSampler;"+ "void main(void) {" + "vec4 color = texture2D(uSampler, vRenderCoord);" + "vec4 alphaMap = texture2D(uAlphaSampler, vTextureCoord);" + "gl_FragColor = vec4(color.rgb, color.a * alphaMap.a);" + "}" ); } var p = createjs.extend(AlphaMaskFilter, createjs.Filter); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. /** docced in super class **/ p.shaderParamSetup = function(gl, stage, shaderProgram) { if(!this._mapTexture) { this._mapTexture = gl.createTexture(); } gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, this._mapTexture); stage.setTextureParams(gl); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.mask); gl.uniform1i( gl.getUniformLocation(shaderProgram, "uAlphaSampler"), 1 ); }; // public methods: /** * Applies the filter to the specified context. * * IMPORTANT NOTE: This filter currently does not support the targetCtx, or targetX/Y parameters * correctly. * @method applyFilter * @param {CanvasRenderingContext2D} ctx The 2D context to use as the source. * @param {Number} x The x position to use for the source rect. * @param {Number} y The y position to use for the source rect. * @param {Number} width The width to use for the source rect. * @param {Number} height The height to use for the source rect. * @param {CanvasRenderingContext2D} [targetCtx] NOT SUPPORTED IN THIS FILTER. The 2D context to draw the result to. Defaults to the context passed to ctx. * @param {Number} [targetX] NOT SUPPORTED IN THIS FILTER. The x position to draw the result to. Defaults to the value passed to x. * @param {Number} [targetY] NOT SUPPORTED IN THIS FILTER. The y position to draw the result to. Defaults to the value passed to y. * @return {Boolean} If the filter was applied successfully. **/ p.applyFilter = function (ctx, x, y, width, height, targetCtx, targetX, targetY) { if (!this.mask) { return true; } targetCtx = targetCtx || ctx; if (targetX == null) { targetX = x; } if (targetY == null) { targetY = y; } targetCtx.save(); if (ctx != targetCtx) { // TODO: support targetCtx and targetX/Y // clearRect, then draw the ctx in? return false; } targetCtx.globalCompositeOperation = "destination-in"; targetCtx.drawImage(this.mask, targetX, targetY); targetCtx.restore(); return true; }; /** docced in super class **/ p.clone = function () { return new AlphaMaskFilter(this.mask); }; /** docced in super class **/ p.toString = function () { return "[AlphaMaskFilter]"; }; createjs.AlphaMaskFilter = createjs.promote(AlphaMaskFilter, "Filter"); }()); //############################################################################## // ColorFilter.js //############################################################################## (function() { "use strict"; // constructor: /** * Applies a color transform to DisplayObjects. * *

    Example

    * This example draws a red circle, and then transforms it to Blue. This is accomplished by multiplying all the channels * to 0 (except alpha, which is set to 1), and then adding 255 to the blue channel. * * var shape = new createjs.Shape().set({x:100,y:100}); * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); * * shape.filters = [ * new createjs.ColorFilter(0,0,0,1, 0,0,255,0) * ]; * shape.cache(-50, -50, 100, 100); * * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. * @class ColorFilter * @param {Number} [redMultiplier=1] The amount to multiply against the red channel. This is a range between 0 and 1. * @param {Number} [greenMultiplier=1] The amount to multiply against the green channel. This is a range between 0 and 1. * @param {Number} [blueMultiplier=1] The amount to multiply against the blue channel. This is a range between 0 and 1. * @param {Number} [alphaMultiplier=1] The amount to multiply against the alpha channel. This is a range between 0 and 1. * @param {Number} [redOffset=0] The amount to add to the red channel after it has been multiplied. This is a range * between -255 and 255. * @param {Number} [greenOffset=0] The amount to add to the green channel after it has been multiplied. This is a range * between -255 and 255. * @param {Number} [blueOffset=0] The amount to add to the blue channel after it has been multiplied. This is a range * between -255 and 255. * @param {Number} [alphaOffset=0] The amount to add to the alpha channel after it has been multiplied. This is a range * between -255 and 255. * @constructor * @extends Filter **/ function ColorFilter(redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier, redOffset, greenOffset, blueOffset, alphaOffset) { this.Filter_constructor(); // public properties: /** * Red channel multiplier. * @property redMultiplier * @type Number **/ this.redMultiplier = redMultiplier != null ? redMultiplier : 1; /** * Green channel multiplier. * @property greenMultiplier * @type Number **/ this.greenMultiplier = greenMultiplier != null ? greenMultiplier : 1; /** * Blue channel multiplier. * @property blueMultiplier * @type Number **/ this.blueMultiplier = blueMultiplier != null ? blueMultiplier : 1; /** * Alpha channel multiplier. * @property alphaMultiplier * @type Number **/ this.alphaMultiplier = alphaMultiplier != null ? alphaMultiplier : 1; /** * Red channel offset (added to value). * @property redOffset * @type Number **/ this.redOffset = redOffset || 0; /** * Green channel offset (added to value). * @property greenOffset * @type Number **/ this.greenOffset = greenOffset || 0; /** * Blue channel offset (added to value). * @property blueOffset * @type Number **/ this.blueOffset = blueOffset || 0; /** * Alpha channel offset (added to value). * @property alphaOffset * @type Number **/ this.alphaOffset = alphaOffset || 0; this.FRAG_SHADER_BODY = ( "uniform vec4 uColorMultiplier;" + "uniform vec4 uColorOffset;" + "void main(void) {" + "vec4 color = texture2D(uSampler, vRenderCoord);" + "gl_FragColor = (color * uColorMultiplier) + uColorOffset;" + "}" ); } var p = createjs.extend(ColorFilter, createjs.Filter); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. // public methods: /** docced in super class **/ p.shaderParamSetup = function(gl, stage, shaderProgram) { gl.uniform4f( gl.getUniformLocation(shaderProgram, "uColorMultiplier"), this.redMultiplier, this.greenMultiplier, this.blueMultiplier, this.alphaMultiplier ); gl.uniform4f( gl.getUniformLocation(shaderProgram, "uColorOffset"), this.redOffset/255, this.greenOffset/255, this.blueOffset/255, this.alphaOffset/255 ); }; /** docced in super class **/ p.toString = function() { return "[ColorFilter]"; }; /** docced in super class **/ p.clone = function() { return new ColorFilter( this.redMultiplier, this.greenMultiplier, this.blueMultiplier, this.alphaMultiplier, this.redOffset, this.greenOffset, this.blueOffset, this.alphaOffset ); }; // private methods: /** docced in super class **/ p._applyFilter = function(imageData) { var data = imageData.data; var l = data.length; for (var i=0; iExample * * myColorMatrix.adjustHue(20).adjustBrightness(50); * * See {{#crossLink "Filter"}}{{/crossLink}} for an example of how to apply filters, or {{#crossLink "ColorMatrixFilter"}}{{/crossLink}} * for an example of how to use ColorMatrix to change a DisplayObject's color. * @class ColorMatrix * @param {Number} brightness * @param {Number} contrast * @param {Number} saturation * @param {Number} hue * @constructor **/ function ColorMatrix(brightness, contrast, saturation, hue) { this.setColor(brightness, contrast, saturation, hue); } var p = ColorMatrix.prototype; // constants: /** * Array of delta values for contrast calculations. * @property DELTA_INDEX * @type Array * @protected * @static **/ ColorMatrix.DELTA_INDEX = [ 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8, 10.0 ]; /** * Identity matrix values. * @property IDENTITY_MATRIX * @type Array * @protected * @static **/ ColorMatrix.IDENTITY_MATRIX = [ 1,0,0,0,0, 0,1,0,0,0, 0,0,1,0,0, 0,0,0,1,0, 0,0,0,0,1 ]; /** * The constant length of a color matrix. * @property LENGTH * @type Number * @protected * @static **/ ColorMatrix.LENGTH = ColorMatrix.IDENTITY_MATRIX.length; // public methods: /** * Resets the instance with the specified values. * @method setColor * @param {Number} brightness * @param {Number} contrast * @param {Number} saturation * @param {Number} hue * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable */ p.setColor = function(brightness,contrast,saturation,hue) { return this.reset().adjustColor(brightness,contrast,saturation,hue); }; /** * Resets the matrix to identity values. * @method reset * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable */ p.reset = function() { return this.copy(ColorMatrix.IDENTITY_MATRIX); }; /** * Shortcut method to adjust brightness, contrast, saturation and hue. * Equivalent to calling adjustHue(hue), adjustContrast(contrast), * adjustBrightness(brightness), adjustSaturation(saturation), in that order. * @method adjustColor * @param {Number} brightness * @param {Number} contrast * @param {Number} saturation * @param {Number} hue * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable **/ p.adjustColor = function(brightness,contrast,saturation,hue) { this.adjustHue(hue); this.adjustContrast(contrast); this.adjustBrightness(brightness); return this.adjustSaturation(saturation); }; /** * Adjusts the brightness of pixel color by adding the specified value to the red, green and blue channels. * Positive values will make the image brighter, negative values will make it darker. * @method adjustBrightness * @param {Number} value A value between -255 & 255 that will be added to the RGB channels. * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable **/ p.adjustBrightness = function(value) { if (value == 0 || isNaN(value)) { return this; } value = this._cleanValue(value,255); this._multiplyMatrix([ 1,0,0,0,value, 0,1,0,0,value, 0,0,1,0,value, 0,0,0,1,0, 0,0,0,0,1 ]); return this; }; /** * Adjusts the contrast of pixel color. * Positive values will increase contrast, negative values will decrease contrast. * @method adjustContrast * @param {Number} value A value between -100 & 100. * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable **/ p.adjustContrast = function(value) { if (value == 0 || isNaN(value)) { return this; } value = this._cleanValue(value,100); var x; if (value<0) { x = 127+value/100*127; } else { x = value%1; if (x == 0) { x = ColorMatrix.DELTA_INDEX[value]; } else { x = ColorMatrix.DELTA_INDEX[(value<<0)]*(1-x)+ColorMatrix.DELTA_INDEX[(value<<0)+1]*x; // use linear interpolation for more granularity. } x = x*127+127; } this._multiplyMatrix([ x/127,0,0,0,0.5*(127-x), 0,x/127,0,0,0.5*(127-x), 0,0,x/127,0,0.5*(127-x), 0,0,0,1,0, 0,0,0,0,1 ]); return this; }; /** * Adjusts the color saturation of the pixel. * Positive values will increase saturation, negative values will decrease saturation (trend towards greyscale). * @method adjustSaturation * @param {Number} value A value between -100 & 100. * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable **/ p.adjustSaturation = function(value) { if (value == 0 || isNaN(value)) { return this; } value = this._cleanValue(value,100); var x = 1+((value > 0) ? 3*value/100 : value/100); var lumR = 0.3086; var lumG = 0.6094; var lumB = 0.0820; this._multiplyMatrix([ lumR*(1-x)+x,lumG*(1-x),lumB*(1-x),0,0, lumR*(1-x),lumG*(1-x)+x,lumB*(1-x),0,0, lumR*(1-x),lumG*(1-x),lumB*(1-x)+x,0,0, 0,0,0,1,0, 0,0,0,0,1 ]); return this; }; /** * Adjusts the hue of the pixel color. * @method adjustHue * @param {Number} value A value between -180 & 180. * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable **/ p.adjustHue = function(value) { if (value == 0 || isNaN(value)) { return this; } value = this._cleanValue(value,180)/180*Math.PI; var cosVal = Math.cos(value); var sinVal = Math.sin(value); var lumR = 0.213; var lumG = 0.715; var lumB = 0.072; this._multiplyMatrix([ lumR+cosVal*(1-lumR)+sinVal*(-lumR),lumG+cosVal*(-lumG)+sinVal*(-lumG),lumB+cosVal*(-lumB)+sinVal*(1-lumB),0,0, lumR+cosVal*(-lumR)+sinVal*(0.143),lumG+cosVal*(1-lumG)+sinVal*(0.140),lumB+cosVal*(-lumB)+sinVal*(-0.283),0,0, lumR+cosVal*(-lumR)+sinVal*(-(1-lumR)),lumG+cosVal*(-lumG)+sinVal*(lumG),lumB+cosVal*(1-lumB)+sinVal*(lumB),0,0, 0,0,0,1,0, 0,0,0,0,1 ]); return this; }; /** * Concatenates (multiplies) the specified matrix with this one. * @method concat * @param {Array} matrix An array or ColorMatrix instance. * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) * @chainable **/ p.concat = function(matrix) { matrix = this._fixMatrix(matrix); if (matrix.length != ColorMatrix.LENGTH) { return this; } this._multiplyMatrix(matrix); return this; }; /** * Returns a clone of this ColorMatrix. * @method clone * @return {ColorMatrix} A clone of this ColorMatrix. **/ p.clone = function() { return (new ColorMatrix()).copy(this); }; /** * Return a length 25 (5x5) array instance containing this matrix's values. * @method toArray * @return {Array} An array holding this matrix's values. **/ p.toArray = function() { var arr = []; for (var i= 0, l=ColorMatrix.LENGTH; i ColorMatrix.LENGTH) { matrix = matrix.slice(0,ColorMatrix.LENGTH); } return matrix; }; createjs.ColorMatrix = ColorMatrix; }()); //############################################################################## // ColorMatrixFilter.js //############################################################################## (function() { "use strict"; // constructor: /** * Allows you to carry out complex color operations such as modifying saturation, brightness, or inverting. See the * {{#crossLink "ColorMatrix"}}{{/crossLink}} for more information on changing colors. For an easier color transform, * consider the {{#crossLink "ColorFilter"}}{{/crossLink}}. * *

    Example

    * This example creates a red circle, inverts its hue, and then saturates it to brighten it up. * * var shape = new createjs.Shape().set({x:100,y:100}); * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); * * var matrix = new createjs.ColorMatrix().adjustHue(180).adjustSaturation(100); * shape.filters = [ * new createjs.ColorMatrixFilter(matrix) * ]; * * shape.cache(-50, -50, 100, 100); * * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. * @class ColorMatrixFilter * @constructor * @extends Filter * @param {Array | ColorMatrix} matrix A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} * class. **/ function ColorMatrixFilter(matrix) { this.Filter_constructor(); // public properties: /** * A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} * @property matrix * @type Array | ColorMatrix **/ this.matrix = matrix; this.FRAG_SHADER_BODY = ( "uniform mat4 uColorMatrix;" + "uniform vec4 uColorMatrixOffset;" + "void main(void) {" + "vec4 color = texture2D(uSampler, vRenderCoord);" + "mat4 m = uColorMatrix;" + "vec4 newColor = vec4(0,0,0,0);" + "newColor.r = color.r*m[0][0] + color.g*m[0][1] + color.b*m[0][2] + color.a*m[0][3];" + "newColor.g = color.r*m[1][0] + color.g*m[1][1] + color.b*m[1][2] + color.a*m[1][3];" + "newColor.b = color.r*m[2][0] + color.g*m[2][1] + color.b*m[2][2] + color.a*m[2][3];" + "newColor.a = color.r*m[3][0] + color.g*m[3][1] + color.b*m[3][2] + color.a*m[3][3];" + "gl_FragColor = newColor + uColorMatrixOffset;" + "}" ); } var p = createjs.extend(ColorMatrixFilter, createjs.Filter); // TODO: deprecated // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. /** docced in super class **/ p.shaderParamSetup = function(gl, stage, shaderProgram) { var mat = this.matrix; var colorMatrix = new Float32Array([ mat[0],mat[1],mat[2],mat[3], mat[5],mat[6],mat[7],mat[8], mat[10],mat[11],mat[12],mat[13], mat[15],mat[16],mat[17],mat[18] ]); gl.uniformMatrix4fv( gl.getUniformLocation(shaderProgram, "uColorMatrix"), false, colorMatrix ); gl.uniform4f( gl.getUniformLocation(shaderProgram, "uColorMatrixOffset"), mat[4]/255, mat[9]/255, mat[14]/255, mat[19]/255 ); }; // public methods: /** docced in super class **/ p.toString = function() { return "[ColorMatrixFilter]"; }; /** docced in super class **/ p.clone = function() { return new ColorMatrixFilter(this.matrix); }; // private methods: /** docced in super class **/ p._applyFilter = function(imageData) { var data = imageData.data; var l = data.length; var r,g,b,a; var mtx = this.matrix; var m0 = mtx[0], m1 = mtx[1], m2 = mtx[2], m3 = mtx[3], m4 = mtx[4]; var m5 = mtx[5], m6 = mtx[6], m7 = mtx[7], m8 = mtx[8], m9 = mtx[9]; var m10 = mtx[10], m11 = mtx[11], m12 = mtx[12], m13 = mtx[13], m14 = mtx[14]; var m15 = mtx[15], m16 = mtx[16], m17 = mtx[17], m18 = mtx[18], m19 = mtx[19]; for (var i=0; iExample * * var stage = new createjs.Stage("canvasId"); * createjs.Touch.enable(stage); * * Note: It is important to disable Touch on a stage that you are no longer using: * * createjs.Touch.disable(stage); * * @class Touch * @static **/ function Touch() { throw "Touch cannot be instantiated"; } // public static methods: /** * Returns `true` if touch is supported in the current browser. * @method isSupported * @return {Boolean} Indicates whether touch is supported in the current browser. * @static **/ Touch.isSupported = function() { return !!(('ontouchstart' in window) // iOS & Android || (window.navigator['msPointerEnabled'] && window.navigator['msMaxTouchPoints'] > 0) // IE10 || (window.navigator['pointerEnabled'] && window.navigator['maxTouchPoints'] > 0)); // IE11+ }; /** * Enables touch interaction for the specified EaselJS {{#crossLink "Stage"}}{{/crossLink}}. Currently supports iOS * (and compatible browsers, such as modern Android browsers), and IE10/11. Supports both single touch and * multi-touch modes. Extends the EaselJS {{#crossLink "MouseEvent"}}{{/crossLink}} model, but without support for * double click or over/out events. See the MouseEvent {{#crossLink "MouseEvent/pointerId:property"}}{{/crossLink}} * for more information. * @method enable * @param {Stage} stage The {{#crossLink "Stage"}}{{/crossLink}} to enable touch on. * @param {Boolean} [singleTouch=false] If `true`, only a single touch will be active at a time. * @param {Boolean} [allowDefault=false] If `true`, then default gesture actions (ex. scrolling, zooming) will be * allowed when the user is interacting with the target canvas. * @return {Boolean} Returns `true` if touch was successfully enabled on the target stage. * @static **/ Touch.enable = function(stage, singleTouch, allowDefault) { if (!stage || !stage.canvas || !Touch.isSupported()) { return false; } if (stage.__touch) { return true; } // inject required properties on stage: stage.__touch = {pointers:{}, multitouch:!singleTouch, preventDefault:!allowDefault, count:0}; // note that in the future we may need to disable the standard mouse event model before adding // these to prevent duplicate calls. It doesn't seem to be an issue with iOS devices though. if ('ontouchstart' in window) { Touch._IOS_enable(stage); } else if (window.navigator['msPointerEnabled'] || window.navigator["pointerEnabled"]) { Touch._IE_enable(stage); } return true; }; /** * Removes all listeners that were set up when calling `Touch.enable()` on a stage. * @method disable * @param {Stage} stage The {{#crossLink "Stage"}}{{/crossLink}} to disable touch on. * @static **/ Touch.disable = function(stage) { if (!stage) { return; } if ('ontouchstart' in window) { Touch._IOS_disable(stage); } else if (window.navigator['msPointerEnabled'] || window.navigator["pointerEnabled"]) { Touch._IE_disable(stage); } delete stage.__touch; }; // Private static methods: /** * @method _IOS_enable * @protected * @param {Stage} stage * @static **/ Touch._IOS_enable = function(stage) { var canvas = stage.canvas; var f = stage.__touch.f = function(e) { Touch._IOS_handleEvent(stage,e); }; canvas.addEventListener("touchstart", f, false); canvas.addEventListener("touchmove", f, false); canvas.addEventListener("touchend", f, false); canvas.addEventListener("touchcancel", f, false); }; /** * @method _IOS_disable * @protected * @param {Stage} stage * @static **/ Touch._IOS_disable = function(stage) { var canvas = stage.canvas; if (!canvas) { return; } var f = stage.__touch.f; canvas.removeEventListener("touchstart", f, false); canvas.removeEventListener("touchmove", f, false); canvas.removeEventListener("touchend", f, false); canvas.removeEventListener("touchcancel", f, false); }; /** * @method _IOS_handleEvent * @param {Stage} stage * @param {Object} e The event to handle * @protected * @static **/ Touch._IOS_handleEvent = function(stage, e) { if (!stage) { return; } if (stage.__touch.preventDefault) { e.preventDefault&&e.preventDefault(); } var touches = e.changedTouches; var type = e.type; for (var i= 0,l=touches.length; iExample * * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); * * @class Utility Methods * @main Utility Methods */ (function() { "use strict"; /** * A function proxy for methods. By default, JavaScript methods do not maintain scope, so passing a method as a * callback will result in the method getting called in the scope of the caller. Using a proxy ensures that the * method gets called in the correct scope. * * Additional arguments can be passed that will be applied to the function when it is called. * *

    Example

    * * myObject.addEventListener("event", createjs.proxy(myHandler, this, arg1, arg2)); * * function myHandler(arg1, arg2) { * // This gets called when myObject.myCallback is executed. * } * * @method proxy * @param {Function} method The function to call * @param {Object} scope The scope to call the method name on * @param {mixed} [arg] * Arguments that are appended to the callback for additional params. * @public * @static */ createjs.proxy = function (method, scope) { var aArgs = Array.prototype.slice.call(arguments, 2); return function () { return method.apply(scope, Array.prototype.slice.call(arguments, 0).concat(aArgs)); }; } }()); //############################################################################## // ErrorEvent.js //############################################################################## (function() { "use strict"; /** * A general error {{#crossLink "Event"}}{{/crossLink}}, that describes an error that occurred, as well as any details. * @class ErrorEvent * @param {String} [title] The error title * @param {String} [message] The error description * @param {Object} [data] Additional error data * @constructor */ function ErrorEvent(title, message, data) { this.Event_constructor("error"); /** * The short error title, which indicates the type of error that occurred. * @property title * @type String */ this.title = title; /** * The verbose error message, containing details about the error. * @property message * @type String */ this.message = message; /** * Additional data attached to an error. * @property data * @type {Object} */ this.data = data; } var p = createjs.extend(ErrorEvent, createjs.Event); p.clone = function() { return new createjs.ErrorEvent(this.title, this.message, this.data); }; createjs.ErrorEvent = createjs.promote(ErrorEvent, "Event"); }()); //############################################################################## // ProgressEvent.js //############################################################################## (function (scope) { "use strict"; // constructor /** * A CreateJS {{#crossLink "Event"}}{{/crossLink}} that is dispatched when progress changes. * @class ProgressEvent * @param {Number} loaded The amount that has been loaded. This can be any number relative to the total. * @param {Number} [total=1] The total amount that will load. This will default to 1, so if the `loaded` value is * a percentage (between 0 and 1), it can be omitted. * @todo Consider having this event be a "fileprogress" event as well * @constructor */ function ProgressEvent(loaded, total) { this.Event_constructor("progress"); /** * The amount that has been loaded (out of a total amount) * @property loaded * @type {Number} */ this.loaded = loaded; /** * The total "size" of the load. * @property total * @type {Number} * @default 1 */ this.total = (total == null) ? 1 : total; /** * The percentage (out of 1) that the load has been completed. This is calculated using `loaded/total`. * @property progress * @type {Number} * @default 0 */ this.progress = (total == 0) ? 0 : this.loaded / this.total; }; var p = createjs.extend(ProgressEvent, createjs.Event); /** * Returns a clone of the ProgressEvent instance. * @method clone * @return {ProgressEvent} a clone of the Event instance. **/ p.clone = function() { return new createjs.ProgressEvent(this.loaded, this.total); }; createjs.ProgressEvent = createjs.promote(ProgressEvent, "Event"); }(window)); //############################################################################## // json3.js //############################################################################## /*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */ ;(function () { // Detect the `define` function exposed by asynchronous module loaders. The // strict `define` check is necessary for compatibility with `r.js`. var isLoader = typeof define === "function" && define.amd; // A set of types used to distinguish objects from primitives. var objectTypes = { "function": true, "object": true }; // Detect the `exports` object exposed by CommonJS implementations. var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports; // Use the `global` object exposed by Node (including Browserify via // `insert-module-globals`), Narwhal, and Ringo as the default context, // and the `window` object in browsers. Rhino exports a `global` function // instead. var root = objectTypes[typeof window] && window || this, freeGlobal = freeExports && objectTypes[typeof module] && module && !module.nodeType && typeof global == "object" && global; if (freeGlobal && (freeGlobal["global"] === freeGlobal || freeGlobal["window"] === freeGlobal || freeGlobal["self"] === freeGlobal)) { root = freeGlobal; } // Public: Initializes JSON 3 using the given `context` object, attaching the // `stringify` and `parse` functions to the specified `exports` object. function runInContext(context, exports) { context || (context = root["Object"]()); exports || (exports = root["Object"]()); // Native constructor aliases. var Number = context["Number"] || root["Number"], String = context["String"] || root["String"], Object = context["Object"] || root["Object"], Date = context["Date"] || root["Date"], SyntaxError = context["SyntaxError"] || root["SyntaxError"], TypeError = context["TypeError"] || root["TypeError"], Math = context["Math"] || root["Math"], nativeJSON = context["JSON"] || root["JSON"]; // Delegate to the native `stringify` and `parse` implementations. if (typeof nativeJSON == "object" && nativeJSON) { exports.stringify = nativeJSON.stringify; exports.parse = nativeJSON.parse; } // Convenience aliases. var objectProto = Object.prototype, getClass = objectProto.toString, isProperty, forEach, undef; // Test the `Date#getUTC*` methods. Based on work by @Yaffle. var isExtended = new Date(-3509827334573292); try { // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical // results for certain dates in Opera >= 10.53. isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 && // Safari < 2.0.2 stores the internal millisecond time value correctly, // but clips the values returned by the date methods to the range of // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]). isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708; } catch (exception) {} // Internal: Determines whether the native `JSON.stringify` and `parse` // implementations are spec-compliant. Based on work by Ken Snyder. function has(name) { if (has[name] !== undef) { // Return cached feature test result. return has[name]; } var isSupported; if (name == "bug-string-char-index") { // IE <= 7 doesn't support accessing string characters using square // bracket notation. IE 8 only supports this for primitives. isSupported = "a"[0] != "a"; } else if (name == "json") { // Indicates whether both `JSON.stringify` and `JSON.parse` are // supported. isSupported = has("json-stringify") && has("json-parse"); } else { var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'; // Test `JSON.stringify`. if (name == "json-stringify") { var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended; if (stringifySupported) { // A test function object with a custom `toJSON` method. (value = function () { return 1; }).toJSON = value; try { stringifySupported = // Firefox 3.1b1 and b2 serialize string, number, and boolean // primitives as object literals. stringify(0) === "0" && // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object // literals. stringify(new Number()) === "0" && stringify(new String()) == '""' && // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or // does not define a canonical JSON representation (this applies to // objects with `toJSON` properties as well, *unless* they are nested // within an object or array). stringify(getClass) === undef && // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and // FF 3.1b3 pass this test. stringify(undef) === undef && // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s, // respectively, if the value is omitted entirely. stringify() === undef && // FF 3.1b1, 2 throw an error if the given value is not a number, // string, array, object, Boolean, or `null` literal. This applies to // objects with custom `toJSON` methods as well, unless they are nested // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON` // methods entirely. stringify(value) === "1" && stringify([value]) == "[1]" && // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of // `"[null]"`. stringify([undef]) == "[null]" && // YUI 3.0.0b1 fails to serialize `null` literals. stringify(null) == "null" && // FF 3.1b1, 2 halts serialization if an array contains a function: // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3 // elides non-JSON values from objects and arrays, unless they // define custom `toJSON` methods. stringify([undef, getClass, null]) == "[null,null,null]" && // Simple serialization test. FF 3.1b1 uses Unicode escape sequences // where character escape codes are expected (e.g., `\b` => `\u0008`). stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized && // FF 3.1b1 and b2 ignore the `filter` and `width` arguments. stringify(null, value) === "1" && stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" && // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly // serialize extended years. stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' && // The milliseconds are optional in ES 5, but required in 5.1. stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' && // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative // four-digit years instead of six-digit years. Credits: @Yaffle. stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' && // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond // values less than 1000. Credits: @Yaffle. stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"'; } catch (exception) { stringifySupported = false; } } isSupported = stringifySupported; } // Test `JSON.parse`. if (name == "json-parse") { var parse = exports.parse; if (typeof parse == "function") { try { // FF 3.1b1, b2 will throw an exception if a bare literal is provided. // Conforming implementations should also coerce the initial argument to // a string prior to parsing. if (parse("0") === 0 && !parse(false)) { // Simple parsing test. value = parse(serialized); var parseSupported = value["a"].length == 5 && value["a"][0] === 1; if (parseSupported) { try { // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings. parseSupported = !parse('"\t"'); } catch (exception) {} if (parseSupported) { try { // FF 4.0 and 4.0.1 allow leading `+` signs and leading // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow // certain octal literals. parseSupported = parse("01") !== 1; } catch (exception) {} } if (parseSupported) { try { // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal // points. These environments, along with FF 3.1b1 and 2, // also allow trailing commas in JSON objects and arrays. parseSupported = parse("1.") !== 1; } catch (exception) {} } } } } catch (exception) { parseSupported = false; } } isSupported = parseSupported; } } return has[name] = !!isSupported; } if (!has("json")) { // Common `[[Class]]` name aliases. var functionClass = "[object Function]", dateClass = "[object Date]", numberClass = "[object Number]", stringClass = "[object String]", arrayClass = "[object Array]", booleanClass = "[object Boolean]"; // Detect incomplete support for accessing string characters by index. var charIndexBuggy = has("bug-string-char-index"); // Define additional utility methods if the `Date` methods are buggy. if (!isExtended) { var floor = Math.floor; // A mapping between the months of the year and the number of days between // January 1st and the first of the respective month. var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; // Internal: Calculates the number of days between the Unix epoch and the // first day of the given month. var getDay = function (year, month) { return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400); }; } // Internal: Determines if a property is a direct property of the given // object. Delegates to the native `Object#hasOwnProperty` method. if (!(isProperty = objectProto.hasOwnProperty)) { isProperty = function (property) { var members = {}, constructor; if ((members.__proto__ = null, members.__proto__ = { // The *proto* property cannot be set multiple times in recent // versions of Firefox and SeaMonkey. "toString": 1 }, members).toString != getClass) { // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but // supports the mutable *proto* property. isProperty = function (property) { // Capture and break the object's prototype chain (see section 8.6.2 // of the ES 5.1 spec). The parenthesized expression prevents an // unsafe transformation by the Closure Compiler. var original = this.__proto__, result = property in (this.__proto__ = null, this); // Restore the original prototype chain. this.__proto__ = original; return result; }; } else { // Capture a reference to the top-level `Object` constructor. constructor = members.constructor; // Use the `constructor` property to simulate `Object#hasOwnProperty` in // other environments. isProperty = function (property) { var parent = (this.constructor || constructor).prototype; return property in this && !(property in parent && this[property] === parent[property]); }; } members = null; return isProperty.call(this, property); }; } // Internal: Normalizes the `for...in` iteration algorithm across // environments. Each enumerated key is yielded to a `callback` function. forEach = function (object, callback) { var size = 0, Properties, members, property; // Tests for bugs in the current environment's `for...in` algorithm. The // `valueOf` property inherits the non-enumerable flag from // `Object.prototype` in older versions of IE, Netscape, and Mozilla. (Properties = function () { this.valueOf = 0; }).prototype.valueOf = 0; // Iterate over a new instance of the `Properties` class. members = new Properties(); for (property in members) { // Ignore all properties inherited from `Object.prototype`. if (isProperty.call(members, property)) { size++; } } Properties = members = null; // Normalize the iteration algorithm. if (!size) { // A list of non-enumerable properties inherited from `Object.prototype`. members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"]; // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable // properties. forEach = function (object, callback) { var isFunction = getClass.call(object) == functionClass, property, length; var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty; for (property in object) { // Gecko <= 1.0 enumerates the `prototype` property of functions under // certain conditions; IE does not. if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) { callback(property); } } // Manually invoke the callback for each non-enumerable property. for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property)); }; } else if (size == 2) { // Safari <= 2.0.4 enumerates shadowed properties twice. forEach = function (object, callback) { // Create a set of iterated properties. var members = {}, isFunction = getClass.call(object) == functionClass, property; for (property in object) { // Store each property name to prevent double enumeration. The // `prototype` property of functions is not enumerated due to cross- // environment inconsistencies. if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) { callback(property); } } }; } else { // No bugs detected; use the standard `for...in` algorithm. forEach = function (object, callback) { var isFunction = getClass.call(object) == functionClass, property, isConstructor; for (property in object) { if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) { callback(property); } } // Manually invoke the callback for the `constructor` property due to // cross-environment inconsistencies. if (isConstructor || isProperty.call(object, (property = "constructor"))) { callback(property); } }; } return forEach(object, callback); }; // Public: Serializes a JavaScript `value` as a JSON string. The optional // `filter` argument may specify either a function that alters how object and // array members are serialized, or an array of strings and numbers that // indicates which properties should be serialized. The optional `width` // argument may be either a string or number that specifies the indentation // level of the output. if (!has("json-stringify")) { // Internal: A map of control characters and their escaped equivalents. var Escapes = { 92: "\\\\", 34: '\\"', 8: "\\b", 12: "\\f", 10: "\\n", 13: "\\r", 9: "\\t" }; // Internal: Converts `value` into a zero-padded string such that its // length is at least equal to `width`. The `width` must be <= 6. var leadingZeroes = "000000"; var toPaddedString = function (width, value) { // The `|| 0` expression is necessary to work around a bug in // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`. return (leadingZeroes + (value || 0)).slice(-width); }; // Internal: Double-quotes a string `value`, replacing all ASCII control // characters (characters with code unit values between 0 and 31) with // their escaped equivalents. This is an implementation of the // `Quote(value)` operation defined in ES 5.1 section 15.12.3. var unicodePrefix = "\\u00"; var quote = function (value) { var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10; var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value); for (; index < length; index++) { var charCode = value.charCodeAt(index); // If the character is a control character, append its Unicode or // shorthand escape sequence; otherwise, append the character as-is. switch (charCode) { case 8: case 9: case 10: case 12: case 13: case 34: case 92: result += Escapes[charCode]; break; default: if (charCode < 32) { result += unicodePrefix + toPaddedString(2, charCode.toString(16)); break; } result += useCharIndex ? symbols[index] : value.charAt(index); } } return result + '"'; }; // Internal: Recursively serializes an object. Implements the // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations. var serialize = function (property, object, callback, properties, whitespace, indentation, stack) { var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result; try { // Necessary for host object support. value = object[property]; } catch (exception) {} if (typeof value == "object" && value) { className = getClass.call(value); if (className == dateClass && !isProperty.call(value, "toJSON")) { if (value > -1 / 0 && value < 1 / 0) { // Dates are serialized according to the `Date#toJSON` method // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15 // for the ISO 8601 date time string format. if (getDay) { // Manually compute the year, month, date, hours, minutes, // seconds, and milliseconds if the `getUTC*` methods are // buggy. Adapted from @Yaffle's `date-shim` project. date = floor(value / 864e5); for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++); for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++); date = 1 + date - getDay(year, month); // The `time` value specifies the time within the day (see ES // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used // to compute `A modulo B`, as the `%` operator does not // correspond to the `modulo` operation for negative numbers. time = (value % 864e5 + 864e5) % 864e5; // The hours, minutes, seconds, and milliseconds are obtained by // decomposing the time within the day. See section 15.9.1.10. hours = floor(time / 36e5) % 24; minutes = floor(time / 6e4) % 60; seconds = floor(time / 1e3) % 60; milliseconds = time % 1e3; } else { year = value.getUTCFullYear(); month = value.getUTCMonth(); date = value.getUTCDate(); hours = value.getUTCHours(); minutes = value.getUTCMinutes(); seconds = value.getUTCSeconds(); milliseconds = value.getUTCMilliseconds(); } // Serialize extended years correctly. value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) + "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) + // Months, dates, hours, minutes, and seconds should have two // digits; milliseconds should have three. "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) + // Milliseconds are optional in ES 5.0, but required in 5.1. "." + toPaddedString(3, milliseconds) + "Z"; } else { value = null; } } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) { // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3 // ignores all `toJSON` methods on these objects unless they are // defined directly on an instance. value = value.toJSON(property); } } if (callback) { // If a replacement function was provided, call it to obtain the value // for serialization. value = callback.call(object, property, value); } if (value === null) { return "null"; } className = getClass.call(value); if (className == booleanClass) { // Booleans are represented literally. return "" + value; } else if (className == numberClass) { // JSON numbers must be finite. `Infinity` and `NaN` are serialized as // `"null"`. return value > -1 / 0 && value < 1 / 0 ? "" + value : "null"; } else if (className == stringClass) { // Strings are double-quoted and escaped. return quote("" + value); } // Recursively serialize objects and arrays. if (typeof value == "object") { // Check for cyclic structures. This is a linear search; performance // is inversely proportional to the number of unique nested objects. for (length = stack.length; length--;) { if (stack[length] === value) { // Cyclic structures cannot be serialized by `JSON.stringify`. throw TypeError(); } } // Add the object to the stack of traversed objects. stack.push(value); results = []; // Save the current indentation level and indent one additional level. prefix = indentation; indentation += whitespace; if (className == arrayClass) { // Recursively serialize array elements. for (index = 0, length = value.length; index < length; index++) { element = serialize(index, value, callback, properties, whitespace, indentation, stack); results.push(element === undef ? "null" : element); } result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]"; } else { // Recursively serialize object members. Members are selected from // either a user-specified list of property names, or the object // itself. forEach(properties || value, function (property) { var element = serialize(property, value, callback, properties, whitespace, indentation, stack); if (element !== undef) { // According to ES 5.1 section 15.12.3: "If `gap` {whitespace} // is not the empty string, let `member` {quote(property) + ":"} // be the concatenation of `member` and the `space` character." // The "`space` character" refers to the literal space // character, not the `space` {width} argument provided to // `JSON.stringify`. results.push(quote(property) + ":" + (whitespace ? " " : "") + element); } }); result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}"; } // Remove the object from the traversed object stack. stack.pop(); return result; } }; // Public: `JSON.stringify`. See ES 5.1 section 15.12.3. exports.stringify = function (source, filter, width) { var whitespace, callback, properties, className; if (objectTypes[typeof filter] && filter) { if ((className = getClass.call(filter)) == functionClass) { callback = filter; } else if (className == arrayClass) { // Convert the property names array into a makeshift set. properties = {}; for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1)); } } if (width) { if ((className = getClass.call(width)) == numberClass) { // Convert the `width` to an integer and create a string containing // `width` number of space characters. if ((width -= width % 1) > 0) { for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " "); } } else if (className == stringClass) { whitespace = width.length <= 10 ? width : width.slice(0, 10); } } // Opera <= 7.54u2 discards the values associated with empty string keys // (`""`) only if they are used directly within an object member list // (e.g., `!("" in { "": 1})`). return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []); }; } // Public: Parses a JSON source string. if (!has("json-parse")) { var fromCharCode = String.fromCharCode; // Internal: A map of escaped control characters and their unescaped // equivalents. var Unescapes = { 92: "\\", 34: '"', 47: "/", 98: "\b", 116: "\t", 110: "\n", 102: "\f", 114: "\r" }; // Internal: Stores the parser state. var Index, Source; // Internal: Resets the parser state and throws a `SyntaxError`. var abort = function () { Index = Source = null; throw SyntaxError(); }; // Internal: Returns the next token, or `"$"` if the parser has reached // the end of the source string. A token may be a string, number, `null` // literal, or Boolean literal. var lex = function () { var source = Source, length = source.length, value, begin, position, isSigned, charCode; while (Index < length) { charCode = source.charCodeAt(Index); switch (charCode) { case 9: case 10: case 13: case 32: // Skip whitespace tokens, including tabs, carriage returns, line // feeds, and space characters. Index++; break; case 123: case 125: case 91: case 93: case 58: case 44: // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at // the current position. value = charIndexBuggy ? source.charAt(Index) : source[Index]; Index++; return value; case 34: // `"` delimits a JSON string; advance to the next character and // begin parsing the string. String tokens are prefixed with the // sentinel `@` character to distinguish them from punctuators and // end-of-string tokens. for (value = "@", Index++; Index < length;) { charCode = source.charCodeAt(Index); if (charCode < 32) { // Unescaped ASCII control characters (those with a code unit // less than the space character) are not permitted. abort(); } else if (charCode == 92) { // A reverse solidus (`\`) marks the beginning of an escaped // control character (including `"`, `\`, and `/`) or Unicode // escape sequence. charCode = source.charCodeAt(++Index); switch (charCode) { case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114: // Revive escaped control characters. value += Unescapes[charCode]; Index++; break; case 117: // `\u` marks the beginning of a Unicode escape sequence. // Advance to the first character and validate the // four-digit code point. begin = ++Index; for (position = Index + 4; Index < position; Index++) { charCode = source.charCodeAt(Index); // A valid sequence comprises four hexdigits (case- // insensitive) that form a single hexadecimal value. if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) { // Invalid Unicode escape sequence. abort(); } } // Revive the escaped character. value += fromCharCode("0x" + source.slice(begin, Index)); break; default: // Invalid escape sequence. abort(); } } else { if (charCode == 34) { // An unescaped double-quote character marks the end of the // string. break; } charCode = source.charCodeAt(Index); begin = Index; // Optimize for the common case where a string is valid. while (charCode >= 32 && charCode != 92 && charCode != 34) { charCode = source.charCodeAt(++Index); } // Append the string as-is. value += source.slice(begin, Index); } } if (source.charCodeAt(Index) == 34) { // Advance to the next character and return the revived string. Index++; return value; } // Unterminated string. abort(); default: // Parse numbers and literals. begin = Index; // Advance past the negative sign, if one is specified. if (charCode == 45) { isSigned = true; charCode = source.charCodeAt(++Index); } // Parse an integer or floating-point value. if (charCode >= 48 && charCode <= 57) { // Leading zeroes are interpreted as octal literals. if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) { // Illegal octal literal. abort(); } isSigned = false; // Parse the integer component. for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++); // Floats cannot contain a leading decimal point; however, this // case is already accounted for by the parser. if (source.charCodeAt(Index) == 46) { position = ++Index; // Parse the decimal component. for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); if (position == Index) { // Illegal trailing decimal. abort(); } Index = position; } // Parse exponents. The `e` denoting the exponent is // case-insensitive. charCode = source.charCodeAt(Index); if (charCode == 101 || charCode == 69) { charCode = source.charCodeAt(++Index); // Skip past the sign following the exponent, if one is // specified. if (charCode == 43 || charCode == 45) { Index++; } // Parse the exponential component. for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); if (position == Index) { // Illegal empty exponent. abort(); } Index = position; } // Coerce the parsed value to a JavaScript number. return +source.slice(begin, Index); } // A negative sign may only precede numbers. if (isSigned) { abort(); } // `true`, `false`, and `null` literals. if (source.slice(Index, Index + 4) == "true") { Index += 4; return true; } else if (source.slice(Index, Index + 5) == "false") { Index += 5; return false; } else if (source.slice(Index, Index + 4) == "null") { Index += 4; return null; } // Unrecognized token. abort(); } } // Return the sentinel `$` character if the parser has reached the end // of the source string. return "$"; }; // Internal: Parses a JSON `value` token. var get = function (value) { var results, hasMembers; if (value == "$") { // Unexpected end of input. abort(); } if (typeof value == "string") { if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") { // Remove the sentinel `@` character. return value.slice(1); } // Parse object and array literals. if (value == "[") { // Parses a JSON array, returning a new JavaScript array. results = []; for (;; hasMembers || (hasMembers = true)) { value = lex(); // A closing square bracket marks the end of the array literal. if (value == "]") { break; } // If the array literal contains elements, the current token // should be a comma separating the previous element from the // next. if (hasMembers) { if (value == ",") { value = lex(); if (value == "]") { // Unexpected trailing `,` in array literal. abort(); } } else { // A `,` must separate each array element. abort(); } } // Elisions and leading commas are not permitted. if (value == ",") { abort(); } results.push(get(value)); } return results; } else if (value == "{") { // Parses a JSON object, returning a new JavaScript object. results = {}; for (;; hasMembers || (hasMembers = true)) { value = lex(); // A closing curly brace marks the end of the object literal. if (value == "}") { break; } // If the object literal contains members, the current token // should be a comma separator. if (hasMembers) { if (value == ",") { value = lex(); if (value == "}") { // Unexpected trailing `,` in object literal. abort(); } } else { // A `,` must separate each object member. abort(); } } // Leading commas are not permitted, object property names must be // double-quoted strings, and a `:` must separate each property // name and value. if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") { abort(); } results[value.slice(1)] = get(lex()); } return results; } // Unexpected token encountered. abort(); } return value; }; // Internal: Updates a traversed object member. var update = function (source, property, callback) { var element = walk(source, property, callback); if (element === undef) { delete source[property]; } else { source[property] = element; } }; // Internal: Recursively traverses a parsed JSON object, invoking the // `callback` function for each value. This is an implementation of the // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2. var walk = function (source, property, callback) { var value = source[property], length; if (typeof value == "object" && value) { // `forEach` can't be used to traverse an array in Opera <= 8.54 // because its `Object#hasOwnProperty` implementation returns `false` // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`). if (getClass.call(value) == arrayClass) { for (length = value.length; length--;) { update(value, length, callback); } } else { forEach(value, function (property) { update(value, property, callback); }); } } return callback.call(source, property, value); }; // Public: `JSON.parse`. See ES 5.1 section 15.12.2. exports.parse = function (source, callback) { var result, value; Index = 0; Source = "" + source; result = get(lex()); // If a JSON string contains multiple tokens, it is invalid. if (lex() != "$") { abort(); } // Reset the parser state. Index = Source = null; return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result; }; } } exports["runInContext"] = runInContext; return exports; } if (freeExports && !isLoader) { // Export for CommonJS environments. runInContext(root, freeExports); } else { // Export for web browsers and JavaScript engines. var nativeJSON = root.JSON, previousJSON = root["JSON3"], isRestored = false; var JSON3 = runInContext(root, (root["JSON3"] = { // Public: Restores the original value of the global `JSON` object and // returns a reference to the `JSON3` object. "noConflict": function () { if (!isRestored) { isRestored = true; root.JSON = nativeJSON; root["JSON3"] = previousJSON; nativeJSON = previousJSON = null; } return JSON3; } })); root.JSON = { "parse": JSON3.parse, "stringify": JSON3.stringify }; } // Export for asynchronous module loaders. if (isLoader) { define(function () { return JSON3; }); } }).call(this); //############################################################################## // Elements.js //############################################################################## (function () { /** * Convenience methods for creating various elements used by PrelaodJS. * * @class DomUtils */ var s = {}; s.a = function() { return s.el("a"); } s.svg = function() { return s.el("svg"); } s.object = function() { return s.el("object"); } s.image = function() { return s.el("image"); } s.img = function() { return s.el("img"); } s.style = function() { return s.el("style"); } s.link = function() { return s.el("link"); } s.script = function() { return s.el("script"); } s.audio = function() { return s.el("audio"); } s.video = function() { return s.el("video"); } s.text = function(value) { return document.createTextNode(value); } s.el = function(name) { return document.createElement(name); } createjs.Elements = s; }()); //############################################################################## // URLUtils.js //############################################################################## (function () { /** * Utilities that assist with parsing load items, and determining file types, etc. * @class URLUtils */ var s = {}; /** * The Regular Expression used to test file URLS for an absolute path. * @property ABSOLUTE_PATH * @type {RegExp} * @static */ s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i; /** * The Regular Expression used to test file URLS for a relative path. * @property RELATIVE_PATH * @type {RegExp} * @static */ s.RELATIVE_PATT = (/^[./]*?\//i); /** * The Regular Expression used to test file URLS for an extension. Note that URIs must already have the query string * removed. * @property EXTENSION_PATT * @type {RegExp} * @static */ s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i; /** * Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know: *
      *
    • If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or * `//networkPath`)
    • *
    • If the path is relative. Relative paths start with `../` or `/path` (or similar)
    • *
    • The file extension. This is determined by the filename with an extension. Query strings are dropped, and * the file path is expected to follow the format `name.ext`.
    • *
    * * @method parseURI * @param {String} path * @returns {Object} An Object with an `absolute` and `relative` Boolean values, * the pieces of the path (protocol, hostname, port, pathname, search, hash, host) * as well as an optional 'extension` property, which is the lowercase extension. * * @static */ s.parseURI = function (path) { var info = { absolute: false, relative: false, protocol: null, hostname: null, port: null, pathname: null, search: null, hash: null, host: null }; if (path == null) { return info; } // Inject the path parts. var parser = createjs.Elements.a(); parser.href = path; for (var n in info) { if (n in parser) { info[n] = parser[n]; } } // Drop the query string var queryIndex = path.indexOf("?"); if (queryIndex > -1) { path = path.substr(0, queryIndex); } // Absolute var match; if (s.ABSOLUTE_PATT.test(path)) { info.absolute = true; // Relative } else if (s.RELATIVE_PATT.test(path)) { info.relative = true; } // Extension if (match = path.match(s.EXTENSION_PATT)) { info.extension = match[1].toLowerCase(); } return info; }; /** * Formats an object into a query string for either a POST or GET request. * @method formatQueryString * @param {Object} data The data to convert to a query string. * @param {Array} [query] Existing name/value pairs to append on to this query. * @static */ s.formatQueryString = function (data, query) { if (data == null) { throw new Error("You must specify data."); } var params = []; for (var n in data) { params.push(n + "=" + escape(data[n])); } if (query) { params = params.concat(query); } return params.join("&"); }; /** * A utility method that builds a file path using a source and a data object, and formats it into a new path. * @method buildURI * @param {String} src The source path to add values to. * @param {Object} [data] Object used to append values to this request as a query string. Existing parameters on the * path will be preserved. * @returns {string} A formatted string that contains the path and the supplied parameters. * @static */ s.buildURI = function (src, data) { if (data == null) { return src; } var query = []; var idx = src.indexOf("?"); if (idx != -1) { var q = src.slice(idx + 1); query = query.concat(q.split("&")); } if (idx != -1) { return src.slice(0, idx) + "?" + this.formatQueryString(data, query); } else { return src + "?" + this.formatQueryString(data, query); } }; /** * @method isCrossDomain * @param {LoadItem|Object} item A load item with a `src` property. * @return {Boolean} If the load item is loading from a different domain than the current location. * @static */ s.isCrossDomain = function (item) { var target = createjs.Elements.a(); target.href = item.src; var host = createjs.Elements.a(); host.href = location.href; var crossdomain = (target.hostname != "") && (target.port != host.port || target.protocol != host.protocol || target.hostname != host.hostname); return crossdomain; }; /** * @method isLocal * @param {LoadItem|Object} item A load item with a `src` property * @return {Boolean} If the load item is loading from the "file:" protocol. Assume that the host must be local as * well. * @static */ s.isLocal = function (item) { var target = createjs.Elements.a(); target.href = item.src; return target.hostname == "" && target.protocol == "file:"; }; createjs.URLUtils = s; }()); //############################################################################## // DomUtils.js //############################################################################## (function () { /** * A few utilities for interacting with the dom. * @class DomUtils */ var s = { container: null }; s.appendToHead = function (el) { s.getHead().appendChild(el); } s.appendToBody = function (el) { if (s.container == null) { s.container = document.createElement("div"); s.container.id = "preloadjs-container"; var style = s.container.style; style.visibility = "hidden"; style.position = "absolute"; style.width = s.container.style.height = "10px"; style.overflow = "hidden"; style.transform = style.msTransform = style.webkitTransform = style.oTransform = "translate(-10px, -10px)"; //LM: Not working s.getBody().appendChild(s.container); } s.container.appendChild(el); } s.getHead = function () { return document.head || document.getElementsByTagName("head")[0]; } s.getBody = function () { return document.body || document.getElementsByTagName("body")[0]; } s.removeChild = function(el) { if (el.parent) { el.parent.removeChild(el); } } /** * Check if item is a valid HTMLImageElement * @method isImageTag * @param {Object} item * @returns {Boolean} * @static */ s.isImageTag = function(item) { return item instanceof HTMLImageElement; }; /** * Check if item is a valid HTMLAudioElement * @method isAudioTag * @param {Object} item * @returns {Boolean} * @static */ s.isAudioTag = function(item) { if (window.HTMLAudioElement) { return item instanceof HTMLAudioElement; } else { return false; } }; /** * Check if item is a valid HTMLVideoElement * @method isVideoTag * @param {Object} item * @returns {Boolean} * @static */ s.isVideoTag = function(item) { if (window.HTMLVideoElement) { return item instanceof HTMLVideoElement; } else { return false; } }; createjs.DomUtils = s; }()); //############################################################################## // DataUtils.js //############################################################################## (function () { /** * A few data utilities for formatting different data types. * @class DataUtils */ var s = {}; // static methods /** * Parse XML using the DOM. This is required when preloading XML or SVG. * @method parseXML * @param {String} text The raw text or XML that is loaded by XHR. * @return {XML} An XML document * @static */ s.parseXML = function (text) { var xml = null; // CocoonJS does not support XML parsing with either method. // Most browsers will use DOMParser // IE fails on certain SVG files, so we have a fallback below. try { if (window.DOMParser) { var parser = new DOMParser(); xml = parser.parseFromString(text, "text/xml"); } } catch (e) { } // Fallback for IE support. if (!xml) { try { xml = new ActiveXObject("Microsoft.XMLDOM"); xml.async = false; xml.loadXML(text); } catch (e) { xml = null; } } return xml; }; /** * Parse a string into an Object. * @method parseJSON * @param {String} value The loaded JSON string * @returns {Object} A JavaScript object. */ s.parseJSON = function (value) { if (value == null) { return null; } try { return JSON.parse(value); } catch (e) { // TODO; Handle this with a custom error? throw e; } }; createjs.DataUtils = s; }()); //############################################################################## // Types.js //############################################################################## (function() { var s = {}; /** * The preload type for generic binary types. Note that images are loaded as binary files when using XHR. * @property BINARY * @type {String} * @default binary * @static * @since 0.6.0 */ s.BINARY = "binary"; /** * The preload type for css files. CSS files are loaded using a <link> when loaded with XHR, or a * <style> tag when loaded with tags. * @property CSS * @type {String} * @default css * @static * @since 0.6.0 */ s.CSS = "css"; /** * The preload type for font files. * @property FONT * @type {String} * @default font * @static * @since 0.9.0 */ s.FONT = "font"; /** * The preload type for fonts specified with CSS (such as Google fonts) * @property FONTCSS * @type {String} * @default fontcss * @static * @since 0.9.0 */ s.FONTCSS = "fontcss"; /** * The preload type for image files, usually png, gif, or jpg/jpeg. Images are loaded into an <image> tag. * @property IMAGE * @type {String} * @default image * @static * @since 0.6.0 */ s.IMAGE = "image"; /** * The preload type for javascript files, usually with the "js" file extension. JavaScript files are loaded into a * <script> tag. * * Since version 0.4.1+, due to how tag-loaded scripts work, all JavaScript files are automatically injected into * the body of the document to maintain parity between XHR and tag-loaded scripts. In version 0.4.0 and earlier, * only tag-loaded scripts are injected. * @property JAVASCRIPT * @type {String} * @default javascript * @static * @since 0.6.0 */ s.JAVASCRIPT = "javascript"; /** * The preload type for json files, usually with the "json" file extension. JSON data is loaded and parsed into a * JavaScript object. Note that if a `callback` is present on the load item, the file will be loaded with JSONP, * no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to, and the JSON * must contain a matching wrapper function. * @property JSON * @type {String} * @default json * @static * @since 0.6.0 */ s.JSON = "json"; /** * The preload type for jsonp files, usually with the "json" file extension. JSON data is loaded and parsed into a * JavaScript object. You are required to pass a callback parameter that matches the function wrapper in the JSON. * Note that JSONP will always be used if there is a callback present, no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} * property is set to. * @property JSONP * @type {String} * @default jsonp * @static * @since 0.6.0 */ s.JSONP = "jsonp"; /** * The preload type for json-based manifest files, usually with the "json" file extension. The JSON data is loaded * and parsed into a JavaScript object. PreloadJS will then look for a "manifest" property in the JSON, which is an * Array of files to load, following the same format as the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} * method. If a "callback" is specified on the manifest object, then it will be loaded using JSONP instead, * regardless of what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to. * @property MANIFEST * @type {String} * @default manifest * @static * @since 0.6.0 */ s.MANIFEST = "manifest"; /** * The preload type for sound files, usually mp3, ogg, or wav. When loading via tags, audio is loaded into an * <audio> tag. * @property SOUND * @type {String} * @default sound * @static * @since 0.6.0 */ s.SOUND = "sound"; /** * The preload type for video files, usually mp4, ts, or ogg. When loading via tags, video is loaded into an * <video> tag. * @property VIDEO * @type {String} * @default video * @static * @since 0.6.0 */ s.VIDEO = "video"; /** * The preload type for SpriteSheet files. SpriteSheet files are JSON files that contain string image paths. * @property SPRITESHEET * @type {String} * @default spritesheet * @static * @since 0.6.0 */ s.SPRITESHEET = "spritesheet"; /** * The preload type for SVG files. * @property SVG * @type {String} * @default svg * @static * @since 0.6.0 */ s.SVG = "svg"; /** * The preload type for text files, which is also the default file type if the type can not be determined. Text is * loaded as raw text. * @property TEXT * @type {String} * @default text * @static * @since 0.6.0 */ s.TEXT = "text"; /** * The preload type for xml files. XML is loaded into an XML document. * @property XML * @type {String} * @default xml * @static * @since 0.6.0 */ s.XML = "xml"; createjs.Types = s; }()); //############################################################################## // Methods.js //############################################################################## (function() { var s = {}; /** * Defines a POST request, use for a method value when loading data. * @property POST * @type {string} * @default post * @static */ s.POST = "POST"; /** * Defines a GET request, use for a method value when loading data. * @property GET * @type {string} * @default get * @static */ s.GET = "GET"; createjs.Methods = s; }()); //############################################################################## // LoadItem.js //############################################################################## (function () { "use strict"; /** * All loaders accept an item containing the properties defined in this class. If a raw object is passed instead, * it will not be affected, but it must contain at least a {{#crossLink "src:property"}}{{/crossLink}} property. A * string path or HTML tag is also acceptable, but it will be automatically converted to a LoadItem using the * {{#crossLink "create"}}{{/crossLink}} method by {{#crossLink "AbstractLoader"}}{{/crossLink}} * @class LoadItem * @constructor * @since 0.6.0 */ function LoadItem() { /** * The source of the file that is being loaded. This property is required. The source can either be a * string (recommended), or an HTML tag. * This can also be an object, but in that case it has to include a type and be handled by a plugin. * @property src * @type {String} * @default null */ this.src = null; /** * The type file that is being loaded. The type of the file is usually inferred by the extension, but can also * be set manually. This is helpful in cases where a file does not have an extension. * @property type * @type {String} * @default null */ this.type = null; /** * A string identifier which can be used to reference the loaded object. If none is provided, this will be * automatically set to the {{#crossLink "src:property"}}{{/crossLink}}. * @property id * @type {String} * @default null */ this.id = null; /** * Determines if a manifest will maintain the order of this item, in relation to other items in the manifest * that have also set the `maintainOrder` property to `true`. This only applies when the max connections has * been set above 1 (using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}). Everything with this * property set to `false` will finish as it is loaded. Ordered items are combined with script tags loading in * order when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}} is set to `true`. * @property maintainOrder * @type {Boolean} * @default false */ this.maintainOrder = false; /** * A callback used by JSONP requests that defines what global method to call when the JSONP content is loaded. * @property callback * @type {String} * @default null */ this.callback = null; /** * An arbitrary data object, which is included with the loaded object. * @property data * @type {Object} * @default null */ this.data = null; /** * The request method used for HTTP calls. Both {{#crossLink "Methods/GET:property"}}{{/crossLink}} or * {{#crossLink "Methods/POST:property"}}{{/crossLink}} request types are supported, and are defined as * constants on {{#crossLink "AbstractLoader"}}{{/crossLink}}. * @property method * @type {String} * @default GET */ this.method = createjs.Methods.GET; /** * An object hash of name/value pairs to send to the server. * @property values * @type {Object} * @default null */ this.values = null; /** * An object hash of headers to attach to an XHR request. PreloadJS will automatically attach some default * headers when required, including "Origin", "Content-Type", and "X-Requested-With". You may override the * default headers by including them in your headers object. * @property headers * @type {Object} * @default null */ this.headers = null; /** * Enable credentials for XHR requests. * @property withCredentials * @type {Boolean} * @default false */ this.withCredentials = false; /** * Set the mime type of XHR-based requests. This is automatically set to "text/plain; charset=utf-8" for text * based files (json, xml, text, css, js). * @property mimeType * @type {String} * @default null */ this.mimeType = null; /** * Sets the crossOrigin attribute for CORS-enabled images loading cross-domain. * @property crossOrigin * @type {boolean} * @default Anonymous */ this.crossOrigin = null; /** * The duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR * (level one) loading, as XHR (level 2) provides its own timeout event. * @property loadTimeout * @type {Number} * @default 8000 (8 seconds) */ this.loadTimeout = s.LOAD_TIMEOUT_DEFAULT; }; var p = LoadItem.prototype = {}; var s = LoadItem; /** * Default duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR * (level one) loading, as XHR (level 2) provides its own timeout event. * @property LOAD_TIMEOUT_DEFAULT * @type {number} * @static */ s.LOAD_TIMEOUT_DEFAULT = 8000; /** * Create a LoadItem. *
      *
    • String-based items are converted to a LoadItem with a populated {{#crossLink "src:property"}}{{/crossLink}}.
    • *
    • LoadItem instances are returned as-is
    • *
    • Objects are returned with any needed properties added
    • *
    * @method create * @param {LoadItem|String|Object} value The load item value * @returns {LoadItem|Object} * @static */ s.create = function (value) { if (typeof value == "string") { var item = new LoadItem(); item.src = value; return item; } else if (value instanceof s) { return value; } else if (value instanceof Object && value.src) { if (value.loadTimeout == null) { value.loadTimeout = s.LOAD_TIMEOUT_DEFAULT; } return value; } else { throw new Error("Type not recognized."); } }; /** * Provides a chainable shortcut method for setting a number of properties on the instance. * *

    Example

    * * var loadItem = new createjs.LoadItem().set({src:"image.png", maintainOrder:true}); * * @method set * @param {Object} props A generic object containing properties to copy to the LoadItem instance. * @return {LoadItem} Returns the instance the method is called on (useful for chaining calls.) */ p.set = function(props) { for (var n in props) { this[n] = props[n]; } return this; }; createjs.LoadItem = s; }()); //############################################################################## // RequestUtils.js //############################################################################## (function () { /** * Utilities that assist with parsing load items, and determining file types, etc. * @class RequestUtils */ var s = {}; /** * Determine if a specific type should be loaded as a binary file. Currently, only images and items marked * specifically as "binary" are loaded as binary. Note that audio is not a binary type, as we can not play * back using an audio tag if it is loaded as binary. Plugins can change the item type to binary to ensure they get * a binary result to work with. Binary files are loaded using XHR2. Types are defined as static constants on * {{#crossLink "AbstractLoader"}}{{/crossLink}}. * @method isBinary * @param {String} type The item type. * @return {Boolean} If the specified type is binary. * @static */ s.isBinary = function (type) { switch (type) { case createjs.Types.IMAGE: case createjs.Types.BINARY: return true; default: return false; } }; /** * Determine if a specific type is a text-based asset, and should be loaded as UTF-8. * @method isText * @param {String} type The item type. * @return {Boolean} If the specified type is text. * @static */ s.isText = function (type) { switch (type) { case createjs.Types.TEXT: case createjs.Types.JSON: case createjs.Types.MANIFEST: case createjs.Types.XML: case createjs.Types.CSS: case createjs.Types.SVG: case createjs.Types.JAVASCRIPT: case createjs.Types.SPRITESHEET: return true; default: return false; } }; /** * Determine the type of the object using common extensions. Note that the type can be passed in with the load item * if it is an unusual extension. * @method getTypeByExtension * @param {String} extension The file extension to use to determine the load type. * @return {String} The determined load type (for example, `createjs.Types.IMAGE`). Will return `null` if * the type can not be determined by the extension. * @static */ s.getTypeByExtension = function (extension) { if (extension == null) { return createjs.Types.TEXT; } switch (extension.toLowerCase()) { case "jpeg": case "jpg": case "gif": case "png": case "webp": case "bmp": return createjs.Types.IMAGE; case "ogg": case "mp3": case "webm": return createjs.Types.SOUND; case "mp4": case "webm": case "ts": return createjs.Types.VIDEO; case "json": return createjs.Types.JSON; case "xml": return createjs.Types.XML; case "css": return createjs.Types.CSS; case "js": return createjs.Types.JAVASCRIPT; case 'svg': return createjs.Types.SVG; default: return createjs.Types.TEXT; } }; createjs.RequestUtils = s; }()); //############################################################################## // AbstractLoader.js //############################################################################## (function () { "use strict"; // constructor /** * The base loader, which defines all the generic methods, properties, and events. All loaders extend this class, * including the {{#crossLink "LoadQueue"}}{{/crossLink}}. * @class AbstractLoader * @param {LoadItem|object|string} loadItem The item to be loaded. * @param {Boolean} [preferXHR] Determines if the LoadItem should try and load using XHR, or take a * tag-based approach, which can be better in cross-domain situations. Not all loaders can load using one or the * other, so this is a suggested directive. * @param {String} [type] The type of loader. Loader types are defined as constants on the AbstractLoader class, * such as {{#crossLink "IMAGE:property"}}{{/crossLink}}, {{#crossLink "CSS:property"}}{{/crossLink}}, etc. * @extends EventDispatcher */ function AbstractLoader(loadItem, preferXHR, type) { this.EventDispatcher_constructor(); // public properties /** * If the loader has completed loading. This provides a quick check, but also ensures that the different approaches * used for loading do not pile up resulting in more than one `complete` {{#crossLink "Event"}}{{/crossLink}}. * @property loaded * @type {Boolean} * @default false */ this.loaded = false; /** * Determine if the loader was canceled. Canceled loads will not fire complete events. Note that this property * is readonly, so {{#crossLink "LoadQueue"}}{{/crossLink}} queues should be closed using {{#crossLink "LoadQueue/close"}}{{/crossLink}} * instead. * @property canceled * @type {Boolean} * @default false * @readonly */ this.canceled = false; /** * The current load progress (percentage) for this item. This will be a number between 0 and 1. * *

    Example

    * * var queue = new createjs.LoadQueue(); * queue.loadFile("largeImage.png"); * queue.on("progress", function() { * console.log("Progress:", queue.progress, event.progress); * }); * * @property progress * @type {Number} * @default 0 */ this.progress = 0; /** * The type of item this loader will load. See {{#crossLink "AbstractLoader"}}{{/crossLink}} for a full list of * supported types. * @property type * @type {String} */ this.type = type; /** * A formatter function that converts the loaded raw result into the final result. For example, the JSONLoader * converts a string of text into a JavaScript object. Not all loaders have a resultFormatter, and this property * can be overridden to provide custom formatting. * * Optionally, a resultFormatter can return a callback function in cases where the formatting needs to be * asynchronous, such as creating a new image. The callback function is passed 2 parameters, which are callbacks * to handle success and error conditions in the resultFormatter. Note that the resultFormatter method is * called in the current scope, as well as the success and error callbacks. * *

    Example asynchronous resultFormatter

    * * function _formatResult(loader) { * return function(success, error) { * if (errorCondition) { error(errorDetailEvent); } * success(result); * } * } * @property resultFormatter * @type {Function} * @default null */ this.resultFormatter = null; // protected properties /** * The {{#crossLink "LoadItem"}}{{/crossLink}} this loader represents. Note that this is null in a {{#crossLink "LoadQueue"}}{{/crossLink}}, * but will be available on loaders such as {{#crossLink "XMLLoader"}}{{/crossLink}} and {{#crossLink "ImageLoader"}}{{/crossLink}}. * @property _item * @type {LoadItem|Object} * @private */ if (loadItem) { this._item = createjs.LoadItem.create(loadItem); } else { this._item = null; } /** * Whether the loader will try and load content using XHR (true) or HTML tags (false). * @property _preferXHR * @type {Boolean} * @private */ this._preferXHR = preferXHR; /** * The loaded result after it is formatted by an optional {{#crossLink "resultFormatter"}}{{/crossLink}}. For * items that are not formatted, this will be the same as the {{#crossLink "_rawResult:property"}}{{/crossLink}}. * The result is accessed using the {{#crossLink "getResult"}}{{/crossLink}} method. * @property _result * @type {Object|String} * @private */ this._result = null; /** * The loaded result before it is formatted. The rawResult is accessed using the {{#crossLink "getResult"}}{{/crossLink}} * method, and passing `true`. * @property _rawResult * @type {Object|String} * @private */ this._rawResult = null; /** * A list of items that loaders load behind the scenes. This does not include the main item the loader is * responsible for loading. Examples of loaders that have sub-items include the {{#crossLink "SpriteSheetLoader"}}{{/crossLink}} and * {{#crossLink "ManifestLoader"}}{{/crossLink}}. * @property _loadItems * @type {null} * @protected */ this._loadedItems = null; /** * The attribute the items loaded using tags use for the source. * @type {string} * @default null * @private */ this._tagSrcAttribute = null; /** * An HTML tag (or similar) that a loader may use to load HTML content, such as images, scripts, etc. * @property _tag * @type {Object} * @private */ this._tag = null; }; var p = createjs.extend(AbstractLoader, createjs.EventDispatcher); var s = AbstractLoader; // Remove these @deprecated properties after 1.0 try { Object.defineProperties(s, { POST: { get: createjs.deprecate(function() { return createjs.Methods.POST; }, "AbstractLoader.POST") }, GET: { get: createjs.deprecate(function() { return createjs.Methods.GET; }, "AbstractLoader.GET") }, BINARY: { get: createjs.deprecate(function() { return createjs.Types.BINARY; }, "AbstractLoader.BINARY") }, CSS: { get: createjs.deprecate(function() { return createjs.Types.CSS; }, "AbstractLoader.CSS") }, FONT: { get: createjs.deprecate(function() { return createjs.Types.FONT; }, "AbstractLoader.FONT") }, FONTCSS: { get: createjs.deprecate(function() { return createjs.Types.FONTCSS; }, "AbstractLoader.FONTCSS") }, IMAGE: { get: createjs.deprecate(function() { return createjs.Types.IMAGE; }, "AbstractLoader.IMAGE") }, JAVASCRIPT: { get: createjs.deprecate(function() { return createjs.Types.JAVASCRIPT; }, "AbstractLoader.JAVASCRIPT") }, JSON: { get: createjs.deprecate(function() { return createjs.Types.JSON; }, "AbstractLoader.JSON") }, JSONP: { get: createjs.deprecate(function() { return createjs.Types.JSONP; }, "AbstractLoader.JSONP") }, MANIFEST: { get: createjs.deprecate(function() { return createjs.Types.MANIFEST; }, "AbstractLoader.MANIFEST") }, SOUND: { get: createjs.deprecate(function() { return createjs.Types.SOUND; }, "AbstractLoader.SOUND") }, VIDEO: { get: createjs.deprecate(function() { return createjs.Types.VIDEO; }, "AbstractLoader.VIDEO") }, SPRITESHEET: { get: createjs.deprecate(function() { return createjs.Types.SPRITESHEET; }, "AbstractLoader.SPRITESHEET") }, SVG: { get: createjs.deprecate(function() { return createjs.Types.SVG; }, "AbstractLoader.SVG") }, TEXT: { get: createjs.deprecate(function() { return createjs.Types.TEXT; }, "AbstractLoader.TEXT") }, XML: { get: createjs.deprecate(function() { return createjs.Types.XML; }, "AbstractLoader.XML") } }); } catch (e) {} // Events /** * The {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when the overall progress changes. Prior to * version 0.6.0, this was just a regular {{#crossLink "Event"}}{{/crossLink}}. * @event progress * @since 0.3.0 */ /** * The {{#crossLink "Event"}}{{/crossLink}} that is fired when a load starts. * @event loadstart * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.3.1 */ /** * The {{#crossLink "Event"}}{{/crossLink}} that is fired when the entire queue has been loaded. * @event complete * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.3.0 */ /** * The {{#crossLink "ErrorEvent"}}{{/crossLink}} that is fired when the loader encounters an error. If the error was * encountered by a file, the event will contain the item that caused the error. Prior to version 0.6.0, this was * just a regular {{#crossLink "Event"}}{{/crossLink}}. * @event error * @since 0.3.0 */ /** * The {{#crossLink "Event"}}{{/crossLink}} that is fired when the loader encounters an internal file load error. * This enables loaders to maintain internal queues, and surface file load errors. * @event fileerror * @param {Object} target The object that dispatched the event. * @param {String} type The event type ("fileerror") * @param {LoadItem|object} The item that encountered the error * @since 0.6.0 */ /** * The {{#crossLink "Event"}}{{/crossLink}} that is fired when a loader internally loads a file. This enables * loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} to maintain internal {{#crossLink "LoadQueue"}}{{/crossLink}}s * and notify when they have loaded a file. The {{#crossLink "LoadQueue"}}{{/crossLink}} class dispatches a * slightly different {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event. * @event fileload * @param {Object} target The object that dispatched the event. * @param {String} type The event type ("fileload") * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the * object will contain that value as a `src` property. * @param {Object} result The HTML tag or parsed result of the loaded item. * @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted * to a usable object. * @since 0.6.0 */ /** * The {{#crossLink "Event"}}{{/crossLink}} that is fired after the internal request is created, but before a load. * This allows updates to the loader for specific loading needs, such as binary or XHR image loading. * @event initialize * @param {Object} target The object that dispatched the event. * @param {String} type The event type ("initialize") * @param {AbstractLoader} loader The loader that has been initialized. */ /** * Get a reference to the manifest item that is loaded by this loader. In some cases this will be the value that was * passed into {{#crossLink "LoadQueue"}}{{/crossLink}} using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or * {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. However if only a String path was passed in, then it will * be a {{#crossLink "LoadItem"}}{{/crossLink}}. * @method getItem * @return {Object} The manifest item that this loader is responsible for loading. * @since 0.6.0 */ p.getItem = function () { return this._item; }; /** * Get a reference to the content that was loaded by the loader (only available after the {{#crossLink "complete:event"}}{{/crossLink}} * event is dispatched. * @method getResult * @param {Boolean} [raw=false] Determines if the returned result will be the formatted content, or the raw loaded * data (if it exists). * @return {Object} * @since 0.6.0 */ p.getResult = function (raw) { return raw ? this._rawResult : this._result; }; /** * Return the `tag` this object creates or uses for loading. * @method getTag * @return {Object} The tag instance * @since 0.6.0 */ p.getTag = function () { return this._tag; }; /** * Set the `tag` this item uses for loading. * @method setTag * @param {Object} tag The tag instance * @since 0.6.0 */ p.setTag = function(tag) { this._tag = tag; }; /** * Begin loading the item. This method is required when using a loader by itself. * *

    Example

    * * var queue = new createjs.LoadQueue(); * queue.on("complete", handleComplete); * queue.loadManifest(fileArray, false); // Note the 2nd argument that tells the queue not to start loading yet * queue.load(); * * @method load */ p.load = function () { this._createRequest(); this._request.on("complete", this, this); this._request.on("progress", this, this); this._request.on("loadStart", this, this); this._request.on("abort", this, this); this._request.on("timeout", this, this); this._request.on("error", this, this); var evt = new createjs.Event("initialize"); evt.loader = this._request; this.dispatchEvent(evt); this._request.load(); }; /** * Close the the item. This will stop any open requests (although downloads using HTML tags may still continue in * the background), but events will not longer be dispatched. * @method cancel */ p.cancel = function () { this.canceled = true; this.destroy(); }; /** * Clean up the loader. * @method destroy */ p.destroy = function() { if (this._request) { this._request.removeAllEventListeners(); this._request.destroy(); } this._request = null; this._item = null; this._rawResult = null; this._result = null; this._loadItems = null; this.removeAllEventListeners(); }; /** * Get any items loaded internally by the loader. The enables loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} * to expose items it loads internally. * @method getLoadedItems * @return {Array} A list of the items loaded by the loader. * @since 0.6.0 */ p.getLoadedItems = function () { return this._loadedItems; }; // Private methods /** * Create an internal request used for loading. By default, an {{#crossLink "XHRRequest"}}{{/crossLink}} or * {{#crossLink "TagRequest"}}{{/crossLink}} is created, depending on the value of {{#crossLink "preferXHR:property"}}{{/crossLink}}. * Other loaders may override this to use different request types, such as {{#crossLink "ManifestLoader"}}{{/crossLink}}, * which uses {{#crossLink "JSONLoader"}}{{/crossLink}} or {{#crossLink "JSONPLoader"}}{{/crossLink}} under the hood. * @method _createRequest * @protected */ p._createRequest = function() { if (!this._preferXHR) { this._request = new createjs.TagRequest(this._item, this._tag || this._createTag(), this._tagSrcAttribute); } else { this._request = new createjs.XHRRequest(this._item); } }; /** * Create the HTML tag used for loading. This method does nothing by default, and needs to be implemented * by loaders that require tag loading. * @method _createTag * @param {String} src The tag source * @return {HTMLElement} The tag that was created * @protected */ p._createTag = function(src) { return null; }; /** * Dispatch a loadstart {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/loadstart:event"}}{{/crossLink}} * event for details on the event payload. * @method _sendLoadStart * @protected */ p._sendLoadStart = function () { if (this._isCanceled()) { return; } this.dispatchEvent("loadstart"); }; /** * Dispatch a {{#crossLink "ProgressEvent"}}{{/crossLink}}. * @method _sendProgress * @param {Number | Object} value The progress of the loaded item, or an object containing loaded * and total properties. * @protected */ p._sendProgress = function (value) { if (this._isCanceled()) { return; } var event = null; if (typeof(value) == "number") { this.progress = value; event = new createjs.ProgressEvent(this.progress); } else { event = value; this.progress = value.loaded / value.total; event.progress = this.progress; if (isNaN(this.progress) || this.progress == Infinity) { this.progress = 0; } } this.hasEventListener("progress") && this.dispatchEvent(event); }; /** * Dispatch a complete {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} event * @method _sendComplete * @protected */ p._sendComplete = function () { if (this._isCanceled()) { return; } this.loaded = true; var event = new createjs.Event("complete"); event.rawResult = this._rawResult; if (this._result != null) { event.result = this._result; } this.dispatchEvent(event); }; /** * Dispatch an error {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}} * event for details on the event payload. * @method _sendError * @param {ErrorEvent} event The event object containing specific error properties. * @protected */ p._sendError = function (event) { if (this._isCanceled() || !this.hasEventListener("error")) { return; } if (event == null) { event = new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY"); // TODO: Populate error } this.dispatchEvent(event); }; /** * Determine if the load has been canceled. This is important to ensure that method calls or asynchronous events * do not cause issues after the queue has been cleaned up. * @method _isCanceled * @return {Boolean} If the loader has been canceled. * @protected */ p._isCanceled = function () { if (window.createjs == null || this.canceled) { return true; } return false; }; /** * A custom result formatter function, which is called just before a request dispatches its complete event. Most * loader types already have an internal formatter, but this can be user-overridden for custom formatting. The * formatted result will be available on Loaders using {{#crossLink "getResult"}}{{/crossLink}}, and passing `true`. * @property resultFormatter * @type Function * @return {Object} The formatted result * @since 0.6.0 */ p.resultFormatter = null; /** * Handle events from internal requests. By default, loaders will handle, and redispatch the necessary events, but * this method can be overridden for custom behaviours. * @method handleEvent * @param {Event} event The event that the internal request dispatches. * @protected * @since 0.6.0 */ p.handleEvent = function (event) { switch (event.type) { case "complete": this._rawResult = event.target._response; var result = this.resultFormatter && this.resultFormatter(this); // The resultFormatter is asynchronous if (result instanceof Function) { result.call(this, createjs.proxy(this._resultFormatSuccess, this), createjs.proxy(this._resultFormatFailed, this) ); // The result formatter is synchronous } else { this._result = result || this._rawResult; this._sendComplete(); } break; case "progress": this._sendProgress(event); break; case "error": this._sendError(event); break; case "loadstart": this._sendLoadStart(); break; case "abort": case "timeout": if (!this._isCanceled()) { this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_" + event.type.toUpperCase() + "_ERROR")); } break; } }; /** * The "success" callback passed to {{#crossLink "AbstractLoader/resultFormatter"}}{{/crossLink}} asynchronous * functions. * @method _resultFormatSuccess * @param {Object} result The formatted result * @private */ p._resultFormatSuccess = function (result) { this._result = result; this._sendComplete(); }; /** * The "error" callback passed to {{#crossLink "AbstractLoader/resultFormatter"}}{{/crossLink}} asynchronous * functions. * @method _resultFormatSuccess * @param {Object} error The error event * @private */ p._resultFormatFailed = function (event) { this._sendError(event); }; /** * @method toString * @return {String} a string representation of the instance. */ p.toString = function () { return "[PreloadJS AbstractLoader]"; }; createjs.AbstractLoader = createjs.promote(AbstractLoader, "EventDispatcher"); }()); //############################################################################## // AbstractMediaLoader.js //############################################################################## (function () { "use strict"; // constructor /** * The AbstractMediaLoader is a base class that handles some of the shared methods and properties of loaders that * handle HTML media elements, such as Video and Audio. * @class AbstractMediaLoader * @param {LoadItem|Object} loadItem * @param {Boolean} preferXHR * @param {String} type The type of media to load. Usually "video" or "audio". * @extends AbstractLoader * @constructor */ function AbstractMediaLoader(loadItem, preferXHR, type) { this.AbstractLoader_constructor(loadItem, preferXHR, type); // public properties this.resultFormatter = this._formatResult; // protected properties this._tagSrcAttribute = "src"; this.on("initialize", this._updateXHR, this); }; var p = createjs.extend(AbstractMediaLoader, createjs.AbstractLoader); // static properties // public methods p.load = function () { // TagRequest will handle most of this, but Sound / Video need a few custom properties, so just handle them here. if (!this._tag) { this._tag = this._createTag(this._item.src); } this._tag.preload = "auto"; this._tag.load(); this.AbstractLoader_load(); }; // protected methods /** * Creates a new tag for loading if it doesn't exist yet. * @method _createTag * @private */ p._createTag = function () {}; p._createRequest = function() { if (!this._preferXHR) { this._request = new createjs.MediaTagRequest(this._item, this._tag || this._createTag(), this._tagSrcAttribute); } else { this._request = new createjs.XHRRequest(this._item); } }; // protected methods /** * Before the item loads, set its mimeType and responseType. * @property _updateXHR * @param {Event} event * @private */ p._updateXHR = function (event) { // Only exists for XHR if (event.loader.setResponseType) { event.loader.setResponseType("blob"); } }; /** * The result formatter for media files. * @method _formatResult * @param {AbstractLoader} loader * @returns {HTMLVideoElement|HTMLAudioElement} * @private */ p._formatResult = function (loader) { this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); this._tag.onstalled = null; if (this._preferXHR) { var URL = window.URL || window.webkitURL; var result = loader.getResult(true); loader.getTag().src = URL.createObjectURL(result); } return loader.getTag(); }; createjs.AbstractMediaLoader = createjs.promote(AbstractMediaLoader, "AbstractLoader"); }()); //############################################################################## // AbstractRequest.js //############################################################################## (function () { "use strict"; /** * A base class for actual data requests, such as {{#crossLink "XHRRequest"}}{{/crossLink}}, {{#crossLink "TagRequest"}}{{/crossLink}}, * and {{#crossLink "MediaRequest"}}{{/crossLink}}. PreloadJS loaders will typically use a data loader under the * hood to get data. * @class AbstractRequest * @param {LoadItem} item * @constructor */ var AbstractRequest = function (item) { this._item = item; }; var p = createjs.extend(AbstractRequest, createjs.EventDispatcher); // public methods /** * Begin a load. * @method load */ p.load = function() {}; /** * Clean up a request. * @method destroy */ p.destroy = function() {}; /** * Cancel an in-progress request. * @method cancel */ p.cancel = function() {}; createjs.AbstractRequest = createjs.promote(AbstractRequest, "EventDispatcher"); }()); //############################################################################## // TagRequest.js //############################################################################## (function () { "use strict"; // constructor /** * An {{#crossLink "AbstractRequest"}}{{/crossLink}} that loads HTML tags, such as images and scripts. * @class TagRequest * @param {LoadItem} loadItem * @param {HTMLElement} tag * @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc. */ function TagRequest(loadItem, tag, srcAttribute) { this.AbstractRequest_constructor(loadItem); // protected properties /** * The HTML tag instance that is used to load. * @property _tag * @type {HTMLElement} * @protected */ this._tag = tag; /** * The tag attribute that specifies the source, such as "src", "href", etc. * @property _tagSrcAttribute * @type {String} * @protected */ this._tagSrcAttribute = srcAttribute; /** * A method closure used for handling the tag load event. * @property _loadedHandler * @type {Function} * @private */ this._loadedHandler = createjs.proxy(this._handleTagComplete, this); /** * Determines if the element was added to the DOM automatically by PreloadJS, so it can be cleaned up after. * @property _addedToDOM * @type {Boolean} * @private */ this._addedToDOM = false; }; var p = createjs.extend(TagRequest, createjs.AbstractRequest); // public methods p.load = function () { this._tag.onload = createjs.proxy(this._handleTagComplete, this); this._tag.onreadystatechange = createjs.proxy(this._handleReadyStateChange, this); this._tag.onerror = createjs.proxy(this._handleError, this); var evt = new createjs.Event("initialize"); evt.loader = this._tag; this.dispatchEvent(evt); this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); this._tag[this._tagSrcAttribute] = this._item.src; // wdg:: Append the tag AFTER setting the src, or SVG loading on iOS will fail. if (this._tag.parentNode == null) { createjs.DomUtils.appendToBody(this._tag); this._addedToDOM = true; } }; p.destroy = function() { this._clean(); this._tag = null; this.AbstractRequest_destroy(); }; // private methods /** * Handle the readyStateChange event from a tag. We need this in place of the `onload` callback (mainly SCRIPT * and LINK tags), but other cases may exist. * @method _handleReadyStateChange * @private */ p._handleReadyStateChange = function () { clearTimeout(this._loadTimeout); // This is strictly for tags in browsers that do not support onload. var tag = this._tag; // Complete is for old IE support. if (tag.readyState == "loaded" || tag.readyState == "complete") { this._handleTagComplete(); } }; /** * Handle any error events from the tag. * @method _handleError * @protected */ p._handleError = function() { this._clean(); this.dispatchEvent("error"); }; /** * Handle the tag's onload callback. * @method _handleTagComplete * @private */ p._handleTagComplete = function () { this._rawResult = this._tag; this._result = this.resultFormatter && this.resultFormatter(this) || this._rawResult; this._clean(); this.dispatchEvent("complete"); }; /** * The tag request has not loaded within the time specified in loadTimeout. * @method _handleError * @param {Object} event The XHR error event. * @private */ p._handleTimeout = function () { this._clean(); this.dispatchEvent(new createjs.Event("timeout")); }; /** * Remove event listeners, but don't destroy the request object * @method _clean * @private */ p._clean = function() { this._tag.onload = null; this._tag.onreadystatechange = null; this._tag.onerror = null; if (this._addedToDOM && this._tag.parentNode != null) { this._tag.parentNode.removeChild(this._tag); } clearTimeout(this._loadTimeout); }; /** * Handle a stalled audio event. The main place this happens is with HTMLAudio in Chrome when playing back audio * that is already in a load, but not complete. * @method _handleStalled * @private */ p._handleStalled = function () { //Ignore, let the timeout take care of it. Sometimes its not really stopped. }; createjs.TagRequest = createjs.promote(TagRequest, "AbstractRequest"); }()); //############################################################################## // MediaTagRequest.js //############################################################################## (function () { "use strict"; // constructor /** * An {{#crossLink "TagRequest"}}{{/crossLink}} that loads HTML tags for video and audio. * @class MediaTagRequest * @param {LoadItem} loadItem * @param {HTMLAudioElement|HTMLVideoElement} tag * @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc. * @constructor */ function MediaTagRequest(loadItem, tag, srcAttribute) { this.AbstractRequest_constructor(loadItem); // protected properties this._tag = tag; this._tagSrcAttribute = srcAttribute; this._loadedHandler = createjs.proxy(this._handleTagComplete, this); }; var p = createjs.extend(MediaTagRequest, createjs.TagRequest); var s = MediaTagRequest; // public methods p.load = function () { var sc = createjs.proxy(this._handleStalled, this); this._stalledCallback = sc; var pc = createjs.proxy(this._handleProgress, this); this._handleProgress = pc; this._tag.addEventListener("stalled", sc); this._tag.addEventListener("progress", pc); // This will tell us when audio is buffered enough to play through, but not when its loaded. // The tag doesn't keep loading in Chrome once enough has buffered, and we have decided that behaviour is sufficient. this._tag.addEventListener && this._tag.addEventListener("canplaythrough", this._loadedHandler, false); // canplaythrough callback doesn't work in Chrome, so we use an event. this.TagRequest_load(); }; // private methods p._handleReadyStateChange = function () { clearTimeout(this._loadTimeout); // This is strictly for tags in browsers that do not support onload. var tag = this._tag; // Complete is for old IE support. if (tag.readyState == "loaded" || tag.readyState == "complete") { this._handleTagComplete(); } }; p._handleStalled = function () { //Ignore, let the timeout take care of it. Sometimes its not really stopped. }; /** * An XHR request has reported progress. * @method _handleProgress * @param {Object} event The XHR progress event. * @private */ p._handleProgress = function (event) { if (!event || event.loaded > 0 && event.total == 0) { return; // Sometimes we get no "total", so just ignore the progress event. } var newEvent = new createjs.ProgressEvent(event.loaded, event.total); this.dispatchEvent(newEvent); }; // protected methods p._clean = function () { this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); this._tag.removeEventListener("stalled", this._stalledCallback); this._tag.removeEventListener("progress", this._progressCallback); this.TagRequest__clean(); }; createjs.MediaTagRequest = createjs.promote(MediaTagRequest, "TagRequest"); }()); //############################################################################## // XHRRequest.js //############################################################################## (function () { "use strict"; // constructor /** * A preloader that loads items using XHR requests, usually XMLHttpRequest. However XDomainRequests will be used * for cross-domain requests if possible, and older versions of IE fall back on to ActiveX objects when necessary. * XHR requests load the content as text or binary data, provide progress and consistent completion events, and * can be canceled during load. Note that XHR is not supported in IE 6 or earlier, and is not recommended for * cross-domain loading. * @class XHRRequest * @constructor * @param {Object} item The object that defines the file to load. Please see the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * for an overview of supported file properties. * @extends AbstractLoader */ function XHRRequest (item) { this.AbstractRequest_constructor(item); // protected properties /** * A reference to the XHR request used to load the content. * @property _request * @type {XMLHttpRequest | XDomainRequest | ActiveX.XMLHTTP} * @private */ this._request = null; /** * A manual load timeout that is used for browsers that do not support the onTimeout event on XHR (XHR level 1, * typically IE9). * @property _loadTimeout * @type {Number} * @private */ this._loadTimeout = null; /** * The browser's XHR (XMLHTTPRequest) version. Supported versions are 1 and 2. There is no official way to detect * the version, so we use capabilities to make a best guess. * @property _xhrLevel * @type {Number} * @default 1 * @private */ this._xhrLevel = 1; /** * The response of a loaded file. This is set because it is expensive to look up constantly. This property will be * null until the file is loaded. * @property _response * @type {mixed} * @private */ this._response = null; /** * The response of the loaded file before it is modified. In most cases, content is converted from raw text to * an HTML tag or a formatted object which is set to the result property, but the developer may still * want to access the raw content as it was loaded. * @property _rawResponse * @type {String|Object} * @private */ this._rawResponse = null; this._canceled = false; // Setup our event handlers now. this._handleLoadStartProxy = createjs.proxy(this._handleLoadStart, this); this._handleProgressProxy = createjs.proxy(this._handleProgress, this); this._handleAbortProxy = createjs.proxy(this._handleAbort, this); this._handleErrorProxy = createjs.proxy(this._handleError, this); this._handleTimeoutProxy = createjs.proxy(this._handleTimeout, this); this._handleLoadProxy = createjs.proxy(this._handleLoad, this); this._handleReadyStateChangeProxy = createjs.proxy(this._handleReadyStateChange, this); if (!this._createXHR(item)) { //TODO: Throw error? } }; var p = createjs.extend(XHRRequest, createjs.AbstractRequest); // static properties /** * A list of XMLHTTP object IDs to try when building an ActiveX object for XHR requests in earlier versions of IE. * @property ACTIVEX_VERSIONS * @type {Array} * @since 0.4.2 * @private */ XHRRequest.ACTIVEX_VERSIONS = [ "Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ]; // Public methods /** * Look up the loaded result. * @method getResult * @param {Boolean} [raw=false] Return a raw result instead of a formatted result. This applies to content * loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be * returned instead. * @return {Object} A result object containing the content that was loaded, such as: *
      *
    • An image tag (<image />) for images
    • *
    • A script tag for JavaScript (<script />). Note that scripts loaded with tags may be added to the * HTML head.
    • *
    • A style tag for CSS (<style />)
    • *
    • Raw text for TEXT
    • *
    • A formatted JavaScript object defined by JSON
    • *
    • An XML document
    • *
    • An binary arraybuffer loaded by XHR
    • *
    * Note that if a raw result is requested, but not found, the result will be returned instead. */ p.getResult = function (raw) { if (raw && this._rawResponse) { return this._rawResponse; } return this._response; }; // Overrides abstract method in AbstractRequest p.cancel = function () { this.canceled = true; this._clean(); this._request.abort(); }; // Overrides abstract method in AbstractLoader p.load = function () { if (this._request == null) { this._handleError(); return; } //Events if (this._request.addEventListener != null) { this._request.addEventListener("loadstart", this._handleLoadStartProxy, false); this._request.addEventListener("progress", this._handleProgressProxy, false); this._request.addEventListener("abort", this._handleAbortProxy, false); this._request.addEventListener("error", this._handleErrorProxy, false); this._request.addEventListener("timeout", this._handleTimeoutProxy, false); // Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these. this._request.addEventListener("load", this._handleLoadProxy, false); this._request.addEventListener("readystatechange", this._handleReadyStateChangeProxy, false); } else { // IE9 support this._request.onloadstart = this._handleLoadStartProxy; this._request.onprogress = this._handleProgressProxy; this._request.onabort = this._handleAbortProxy; this._request.onerror = this._handleErrorProxy; this._request.ontimeout = this._handleTimeoutProxy; // Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these. this._request.onload = this._handleLoadProxy; this._request.onreadystatechange = this._handleReadyStateChangeProxy; } // Set up a timeout if we don't have XHR2 if (this._xhrLevel == 1) { this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); } // Sometimes we get back 404s immediately, particularly when there is a cross origin request. // note this does not catch in Chrome try { if (!this._item.values) { this._request.send(); } else { this._request.send(createjs.URLUtils.formatQueryString(this._item.values)); } } catch (error) { this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND", null, error)); } }; p.setResponseType = function (type) { // Some old browsers doesn't support blob, so we convert arraybuffer to blob after response is downloaded if (type === 'blob') { type = window.URL ? 'blob' : 'arraybuffer'; this._responseType = type; } this._request.responseType = type; }; /** * Get all the response headers from the XmlHttpRequest. * * From the docs: Return all the HTTP headers, excluding headers that are a case-insensitive match * for Set-Cookie or Set-Cookie2, as a single string, with each header line separated by a U+000D CR U+000A LF pair, * excluding the status line, and with each header name and header value separated by a U+003A COLON U+0020 SPACE * pair. * @method getAllResponseHeaders * @return {String} * @since 0.4.1 */ p.getAllResponseHeaders = function () { if (this._request.getAllResponseHeaders instanceof Function) { return this._request.getAllResponseHeaders(); } else { return null; } }; /** * Get a specific response header from the XmlHttpRequest. * * From the docs: Returns the header field value from the response of which the field name matches * header, unless the field name is Set-Cookie or Set-Cookie2. * @method getResponseHeader * @param {String} header The header name to retrieve. * @return {String} * @since 0.4.1 */ p.getResponseHeader = function (header) { if (this._request.getResponseHeader instanceof Function) { return this._request.getResponseHeader(header); } else { return null; } }; // protected methods /** * The XHR request has reported progress. * @method _handleProgress * @param {Object} event The XHR progress event. * @private */ p._handleProgress = function (event) { if (!event || event.loaded > 0 && event.total == 0) { return; // Sometimes we get no "total", so just ignore the progress event. } var newEvent = new createjs.ProgressEvent(event.loaded, event.total); this.dispatchEvent(newEvent); }; /** * The XHR request has reported a load start. * @method _handleLoadStart * @param {Object} event The XHR loadStart event. * @private */ p._handleLoadStart = function (event) { clearTimeout(this._loadTimeout); this.dispatchEvent("loadstart"); }; /** * The XHR request has reported an abort event. * @method handleAbort * @param {Object} event The XHR abort event. * @private */ p._handleAbort = function (event) { this._clean(); this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED", null, event)); }; /** * The XHR request has reported an error event. * @method _handleError * @param {Object} event The XHR error event. * @private */ p._handleError = function (event) { this._clean(); this.dispatchEvent(new createjs.ErrorEvent(event.message)); }; /** * The XHR request has reported a readyState change. Note that older browsers (IE 7 & 8) do not provide an onload * event, so we must monitor the readyStateChange to determine if the file is loaded. * @method _handleReadyStateChange * @param {Object} event The XHR readyStateChange event. * @private */ p._handleReadyStateChange = function (event) { if (this._request.readyState == 4) { this._handleLoad(); } }; /** * The XHR request has completed. This is called by the XHR request directly, or by a readyStateChange that has * request.readyState == 4. Only the first call to this method will be processed. * * Note that This method uses {{#crossLink "_checkError"}}{{/crossLink}} to determine if the server has returned an * error code. * @method _handleLoad * @param {Object} event The XHR load event. * @private */ p._handleLoad = function (event) { if (this.loaded) { return; } this.loaded = true; var error = this._checkError(); if (error) { this._handleError(error); return; } this._response = this._getResponse(); // Convert arraybuffer back to blob if (this._responseType === 'arraybuffer') { try { this._response = new Blob([this._response]); } catch (e) { // Fallback to use BlobBuilder if Blob constructor is not supported // Tested on Android 2.3 ~ 4.2 and iOS5 safari window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; if (e.name === 'TypeError' && window.BlobBuilder) { var builder = new BlobBuilder(); builder.append(this._response); this._response = builder.getBlob(); } } } this._clean(); this.dispatchEvent(new createjs.Event("complete")); }; /** * The XHR request has timed out. This is called by the XHR request directly, or via a setTimeout * callback. * @method _handleTimeout * @param {Object} [event] The XHR timeout event. This is occasionally null when called by the backup setTimeout. * @private */ p._handleTimeout = function (event) { this._clean(); this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT", null, event)); }; // Protected /** * Determine if there is an error in the current load. * Currently this checks the status of the request for problem codes, and not actual response content: *
      *
    • Status codes between 400 and 599 (HTTP error range)
    • *
    • A status of 0, but *only when the application is running on a server*. If the application is running * on `file:`, then it may incorrectly treat an error on local (or embedded applications) as a successful * load.
    • *
    * @method _checkError * @return {Error} An error with the status code in the `message` argument. * @private */ p._checkError = function () { var status = parseInt(this._request.status); if (status >= 400 && status <= 599) { return new Error(status); } else if (status == 0) { if ((/^https?:/).test(location.protocol)) { return new Error(0); } return null; // Likely an embedded app. } else { return null; } }; /** * Validate the response. Different browsers have different approaches, some of which throw errors when accessed * in other browsers. If there is no response, the _response property will remain null. * @method _getResponse * @private */ p._getResponse = function () { if (this._response != null) { return this._response; } if (this._request.response != null) { return this._request.response; } // Android 2.2 uses .responseText try { if (this._request.responseText != null) { return this._request.responseText; } } catch (e) { } // When loading XML, IE9 does not return .response, instead it returns responseXML.xml try { if (this._request.responseXML != null) { return this._request.responseXML; } } catch (e) { } return null; }; /** * Create an XHR request. Depending on a number of factors, we get totally different results. *
    1. Some browsers get an XDomainRequest when loading cross-domain.
    2. *
    3. XMLHttpRequest are created when available.
    4. *
    5. ActiveX.XMLHTTP objects are used in older IE browsers.
    6. *
    7. Text requests override the mime type if possible
    8. *
    9. Origin headers are sent for crossdomain requests in some browsers.
    10. *
    11. Binary loads set the response type to "arraybuffer"
    * @method _createXHR * @param {Object} item The requested item that is being loaded. * @return {Boolean} If an XHR request or equivalent was successfully created. * @private */ p._createXHR = function (item) { // Check for cross-domain loads. We can't fully support them, but we can try. var crossdomain = createjs.URLUtils.isCrossDomain(item); var headers = {}; // Create the request. Fallback to whatever support we have. var req = null; if (window.XMLHttpRequest) { req = new XMLHttpRequest(); // This is 8 or 9, so use XDomainRequest instead. if (crossdomain && req.withCredentials === undefined && window.XDomainRequest) { req = new XDomainRequest(); } } else { // Old IE versions use a different approach for (var i = 0, l = s.ACTIVEX_VERSIONS.length; i < l; i++) { var axVersion = s.ACTIVEX_VERSIONS[i]; try { req = new ActiveXObject(axVersion); break; } catch (e) { } } if (req == null) { return false; } } // Default to utf-8 for Text requests. if (item.mimeType == null && createjs.RequestUtils.isText(item.type)) { item.mimeType = "text/plain; charset=utf-8"; } // IE9 doesn't support overrideMimeType(), so we need to check for it. if (item.mimeType && req.overrideMimeType) { req.overrideMimeType(item.mimeType); } // Determine the XHR level this._xhrLevel = (typeof req.responseType === "string") ? 2 : 1; var src = null; if (item.method == createjs.Methods.GET) { src = createjs.URLUtils.buildURI(item.src, item.values); } else { src = item.src; } // Open the request. Set cross-domain flags if it is supported (XHR level 1 only) req.open(item.method || createjs.Methods.GET, src, true); if (crossdomain && req instanceof XMLHttpRequest && this._xhrLevel == 1) { headers["Origin"] = location.origin; } // To send data we need to set the Content-type header) if (item.values && item.method == createjs.Methods.POST) { headers["Content-Type"] = "application/x-www-form-urlencoded"; } if (!crossdomain && !headers["X-Requested-With"]) { headers["X-Requested-With"] = "XMLHttpRequest"; } if (item.headers) { for (var n in item.headers) { headers[n] = item.headers[n]; } } for (n in headers) { req.setRequestHeader(n, headers[n]) } if (req instanceof XMLHttpRequest && item.withCredentials !== undefined) { req.withCredentials = item.withCredentials; } this._request = req; return true; }; /** * A request has completed (or failed or canceled), and needs to be disposed. * @method _clean * @private */ p._clean = function () { clearTimeout(this._loadTimeout); if (this._request.removeEventListener != null) { this._request.removeEventListener("loadstart", this._handleLoadStartProxy); this._request.removeEventListener("progress", this._handleProgressProxy); this._request.removeEventListener("abort", this._handleAbortProxy); this._request.removeEventListener("error", this._handleErrorProxy); this._request.removeEventListener("timeout", this._handleTimeoutProxy); this._request.removeEventListener("load", this._handleLoadProxy); this._request.removeEventListener("readystatechange", this._handleReadyStateChangeProxy); } else { this._request.onloadstart = null; this._request.onprogress = null; this._request.onabort = null; this._request.onerror = null; this._request.ontimeout = null; this._request.onload = null; this._request.onreadystatechange = null; } }; p.toString = function () { return "[PreloadJS XHRRequest]"; }; createjs.XHRRequest = createjs.promote(XHRRequest, "AbstractRequest"); }()); //############################################################################## // LoadQueue.js //############################################################################## /* TODO: WINDOWS ISSUES * No error for HTML audio in IE 678 * SVG no failure error in IE 67 (maybe 8) TAGS AND XHR * No script complete handler in IE 67 TAGS (XHR is fine) * No XML/JSON in IE6 TAGS * Need to hide loading SVG in Opera TAGS * No CSS onload/readystatechange in Safari or Android TAGS (requires rule checking) * SVG no load or failure in Opera XHR * Reported issues with IE7/8 */ (function () { "use strict"; // constructor /** * The LoadQueue class is the main API for preloading content. LoadQueue is a load manager, which can preload either * a single file, or queue of files. * * Creating a Queue
    * To use LoadQueue, create a LoadQueue instance. If you want to force tag loading where possible, set the preferXHR * argument to false. * * var queue = new createjs.LoadQueue(true); * * Listening for Events
    * Add any listeners you want to the queue. Since PreloadJS 0.3.0, the {{#crossLink "EventDispatcher"}}{{/crossLink}} * lets you add as many listeners as you want for events. You can subscribe to the following events:
      *
    • {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}}: fired when a queue completes loading all * files
    • *
    • {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}}: fired when the queue encounters an error with * any file.
    • *
    • {{#crossLink "AbstractLoader/progress:event"}}{{/crossLink}}: Progress for the entire queue has * changed.
    • *
    • {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}}: A single file has completed loading.
    • *
    • {{#crossLink "LoadQueue/fileprogress:event"}}{{/crossLink}}: Progress for a single file has changes. Note * that only files loaded with XHR (or possibly by plugins) will fire progress events other than 0 or 100%.
    • *
    * * queue.on("fileload", handleFileLoad, this); * queue.on("complete", handleComplete, this); * * Adding files and manifests
    * Add files you want to load using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or add multiple files at a * time using a list or a manifest definition using {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. Files are * appended to the end of the active queue, so you can use these methods as many times as you like, whenever you * like. * * queue.loadFile("filePath/file.jpg"); * queue.loadFile({id:"image", src:"filePath/file.jpg"}); * queue.loadManifest(["filePath/file.jpg", {id:"image", src:"filePath/file.jpg"}]); * * // Use an external manifest * queue.loadManifest("path/to/manifest.json"); * queue.loadManifest({src:"manifest.json", type:"manifest"}); * * If you pass `false` as the `loadNow` parameter, the queue will not kick of the load of the files, but it will not * stop if it has already been started. Call the {{#crossLink "AbstractLoader/load"}}{{/crossLink}} method to begin * a paused queue. Note that a paused queue will automatically resume when new files are added to it with a * `loadNow` argument of `true`. * * queue.load(); * * File Types
    * The file type of a manifest item is auto-determined by the file extension. The pattern matching in PreloadJS * should handle the majority of standard file and url formats, and works with common file extensions. If you have * either a non-standard file extension, or are serving the file using a proxy script, then you can pass in a * type property with any manifest item. * * queue.loadFile({src:"path/to/myFile.mp3x", type:createjs.Types.SOUND}); * * // Note that PreloadJS will not read a file extension from the query string * queue.loadFile({src:"http://server.com/proxy?file=image.jpg", type:createjs.Types.IMAGE}); * * Supported types are defined on the {{#crossLink "AbstractLoader"}}{{/crossLink}} class, and include: *
      *
    • {{#crossLink "Types/BINARY:property"}}{{/crossLink}}: Raw binary data via XHR
    • *
    • {{#crossLink "Types/CSS:property"}}{{/crossLink}}: CSS files
    • *
    • {{#crossLink "Types/IMAGE:property"}}{{/crossLink}}: Common image formats
    • *
    • {{#crossLink "Types/JAVASCRIPT:property"}}{{/crossLink}}: JavaScript files
    • *
    • {{#crossLink "Types/JSON:property"}}{{/crossLink}}: JSON data
    • *
    • {{#crossLink "Types/JSONP:property"}}{{/crossLink}}: JSON files cross-domain
    • *
    • {{#crossLink "Types/MANIFEST:property"}}{{/crossLink}}: A list of files to load in JSON format, see * {{#crossLink "AbstractLoader/loadManifest"}}{{/crossLink}}
    • *
    • {{#crossLink "Types/SOUND:property"}}{{/crossLink}}: Audio file formats
    • *
    • {{#crossLink "Types/SPRITESHEET:property"}}{{/crossLink}}: JSON SpriteSheet definitions. This * will also load sub-images, and provide a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance.
    • *
    • {{#crossLink "Types/SVG:property"}}{{/crossLink}}: SVG files
    • *
    • {{#crossLink "Types/TEXT:property"}}{{/crossLink}}: Text files - XHR only
    • *
    • {{#crossLink "Types/VIDEO:property"}}{{/crossLink}}: Video objects
    • *
    • {{#crossLink "Types/XML:property"}}{{/crossLink}}: XML data
    • *
    * * Note: Loader types used to be defined on LoadQueue, but have been moved to the Types class * * Handling Results
    * When a file is finished downloading, a {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event is * dispatched. In an example above, there is an event listener snippet for fileload. Loaded files are usually a * formatted object that can be used immediately, including: *
      *
    • Binary: The binary loaded result
    • *
    • CSS: A <link /> tag
    • *
    • Image: An <img /> tag
    • *
    • JavaScript: A <script /> tag
    • *
    • JSON/JSONP: A formatted JavaScript Object
    • *
    • Manifest: A JavaScript object. *
    • Sound: An <audio /> tag *
    • SpriteSheet: A {{#crossLink "SpriteSheet"}}{{/crossLink}} instance, containing loaded images. *
    • SVG: An <object /> tag
    • *
    • Text: Raw text
    • *
    • Video: A Video DOM node
    • *
    • XML: An XML DOM node
    • *
    * * function handleFileLoad(event) { * var item = event.item; // A reference to the item that was passed in to the LoadQueue * var type = item.type; * * // Add any images to the page body. * if (type == createjs.Types.IMAGE) { * document.body.appendChild(event.result); * } * } * * At any time after the file has been loaded (usually after the queue has completed), any result can be looked up * via its "id" using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}. If no id was provided, then the * "src" or file path can be used instead, including the `path` defined by a manifest, but not including * a base path defined on the LoadQueue. It is recommended to always pass an id if you want to look up content. * * var image = queue.getResult("image"); * document.body.appendChild(image); * * Raw loaded content can be accessed using the rawResult property of the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} * event, or can be looked up using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}, passing `true` as the 2nd * argument. This is only applicable for content that has been parsed for the browser, specifically: JavaScript, * CSS, XML, SVG, and JSON objects, or anything loaded with XHR. * * var image = queue.getResult("image", true); // load the binary image data loaded with XHR. * * Plugins
    * LoadQueue has a simple plugin architecture to help process and preload content. For example, to preload audio, * make sure to install the SoundJS Sound class, which will help load HTML audio, * Flash audio, and WebAudio files. This should be installed before loading any audio files. * * queue.installPlugin(createjs.Sound); * *

    Known Browser Issues

    *
      *
    • Browsers without audio support can not load audio files.
    • *
    • Safari on Mac OS X can only play HTML audio if QuickTime is installed
    • *
    • HTML Audio tags will only download until their canPlayThrough event is fired. Browsers other * than Chrome will continue to download in the background.
    • *
    • When loading scripts using tags, they are automatically added to the document.
    • *
    • Scripts loaded via XHR may not be properly inspectable with browser tools.
    • *
    • IE6 and IE7 (and some other browsers) may not be able to load XML, Text, or JSON, since they require * XHR to work.
    • *
    • Content loaded via tags will not show progress, and will continue to download in the background when * canceled, although no events will be dispatched.
    • *
    * * @class LoadQueue * @param {Boolean} [preferXHR=true] Determines whether the preload instance will favor loading with XHR (XML HTTP * Requests), or HTML tags. When this is `false`, the queue will use tag loading when possible, and fall back on XHR * when necessary. * @param {String} [basePath=""] A path that will be prepended on to the source parameter of all items in the queue * before they are loaded. Sources beginning with a protocol such as `http://` or a relative path such as `../` * will not receive a base path. * @param {String|Boolean} [crossOrigin=""] An optional flag to support images loaded from a CORS-enabled server. To * use it, set this value to `true`, which will default the crossOrigin property on images to "Anonymous". Any * string value will be passed through, but only "" and "Anonymous" are recommended. Note: The crossOrigin * parameter is deprecated. Use LoadItem.crossOrigin instead * * @constructor * @extends AbstractLoader */ function LoadQueue (preferXHR, basePath, crossOrigin) { this.AbstractLoader_constructor(); /** * An array of the plugins registered using {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}}. * @property _plugins * @type {Array} * @private * @since 0.6.1 */ this._plugins = []; /** * An object hash of callbacks that are fired for each file type before the file is loaded, giving plugins the * ability to override properties of the load. Please see the {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}} * method for more information. * @property _typeCallbacks * @type {Object} * @private */ this._typeCallbacks = {}; /** * An object hash of callbacks that are fired for each file extension before the file is loaded, giving plugins the * ability to override properties of the load. Please see the {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}} * method for more information. * @property _extensionCallbacks * @type {null} * @private */ this._extensionCallbacks = {}; /** * The next preload queue to process when this one is complete. If an error is thrown in the current queue, and * {{#crossLink "LoadQueue/stopOnError:property"}}{{/crossLink}} is `true`, the next queue will not be processed. * @property next * @type {LoadQueue} * @default null */ this.next = null; /** * Ensure loaded scripts "complete" in the order they are specified. Loaded scripts are added to the document head * once they are loaded. Scripts loaded via tags will load one-at-a-time when this property is `true`, whereas * scripts loaded using XHR can load in any order, but will "finish" and be added to the document in the order * specified. * * Any items can be set to load in order by setting the {{#crossLink "maintainOrder:property"}}{{/crossLink}} * property on the load item, or by ensuring that only one connection can be open at a time using * {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}. Note that when the `maintainScriptOrder` property * is set to `true`, scripts items are automatically set to `maintainOrder=true`, and changing the * `maintainScriptOrder` to `false` during a load will not change items already in a queue. * *

    Example

    * * var queue = new createjs.LoadQueue(); * queue.setMaxConnections(3); // Set a higher number to load multiple items at once * queue.maintainScriptOrder = true; // Ensure scripts are loaded in order * queue.loadManifest([ * "script1.js", * "script2.js", * "image.png", // Load any time * {src: "image2.png", maintainOrder: true} // Will wait for script2.js * "image3.png", * "script3.js" // Will wait for image2.png before loading (or completing when loading with XHR) * ]); * * @property maintainScriptOrder * @type {Boolean} * @default true */ this.maintainScriptOrder = true; /** * Determines if the LoadQueue will stop processing the current queue when an error is encountered. * @property stopOnError * @type {Boolean} * @default false */ this.stopOnError = false; /** * The number of maximum open connections that a loadQueue tries to maintain. Please see * {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}} for more information. * @property _maxConnections * @type {Number} * @default 1 * @private */ this._maxConnections = 1; /** * An internal list of all the default Loaders that are included with PreloadJS. Before an item is loaded, the * available loader list is iterated, in the order they are included, and as soon as a loader indicates it can * handle the content, it will be selected. The default loader, ({{#crossLink "TextLoader"}}{{/crossLink}} is * last in the list, so it will be used if no other match is found. Typically, loaders will match based on the * {{#crossLink "LoadItem/type"}}{{/crossLink}}, which is automatically determined using the file extension of * the {{#crossLink "LoadItem/src:property"}}{{/crossLink}}. * * Loaders can be removed from PreloadJS by simply not including them. * * Custom loaders installed using {{#crossLink "registerLoader"}}{{/crossLink}} will be prepended to this list * so that they are checked first. * @property _availableLoaders * @type {Array} * @private * @since 0.6.0 */ this._availableLoaders = [ createjs.FontLoader, createjs.ImageLoader, createjs.JavaScriptLoader, createjs.CSSLoader, createjs.JSONLoader, createjs.JSONPLoader, createjs.SoundLoader, createjs.ManifestLoader, createjs.SpriteSheetLoader, createjs.XMLLoader, createjs.SVGLoader, createjs.BinaryLoader, createjs.VideoLoader, createjs.TextLoader ]; /** * The number of built in loaders, so they can't be removed by {{#crossLink "unregisterLoader"}}{{/crossLink}. * @property _defaultLoaderLength * @type {Number} * @private * @since 0.6.0 */ this._defaultLoaderLength = this._availableLoaders.length; this.init(preferXHR, basePath, crossOrigin); } var p = createjs.extend(LoadQueue, createjs.AbstractLoader); var s = LoadQueue; // Remove these @deprecated properties after 1.0 try { Object.defineProperties(s, { POST: { get: createjs.deprecate(function() { return createjs.Methods.POST; }, "AbstractLoader.POST") }, GET: { get: createjs.deprecate(function() { return createjs.Methods.GET; }, "AbstractLoader.GET") }, BINARY: { get: createjs.deprecate(function() { return createjs.Types.BINARY; }, "AbstractLoader.BINARY") }, CSS: { get: createjs.deprecate(function() { return createjs.Types.CSS; }, "AbstractLoader.CSS") }, FONT: { get: createjs.deprecate(function() { return createjs.Types.FONT; }, "AbstractLoader.FONT") }, FONTCSS: { get: createjs.deprecate(function() { return createjs.Types.FONTCSS; }, "AbstractLoader.FONTCSS") }, IMAGE: { get: createjs.deprecate(function() { return createjs.Types.IMAGE; }, "AbstractLoader.IMAGE") }, JAVASCRIPT: { get: createjs.deprecate(function() { return createjs.Types.JAVASCRIPT; }, "AbstractLoader.JAVASCRIPT") }, JSON: { get: createjs.deprecate(function() { return createjs.Types.JSON; }, "AbstractLoader.JSON") }, JSONP: { get: createjs.deprecate(function() { return createjs.Types.JSONP; }, "AbstractLoader.JSONP") }, MANIFEST: { get: createjs.deprecate(function() { return createjs.Types.MANIFEST; }, "AbstractLoader.MANIFEST") }, SOUND: { get: createjs.deprecate(function() { return createjs.Types.SOUND; }, "AbstractLoader.SOUND") }, VIDEO: { get: createjs.deprecate(function() { return createjs.Types.VIDEO; }, "AbstractLoader.VIDEO") }, SPRITESHEET: { get: createjs.deprecate(function() { return createjs.Types.SPRITESHEET; }, "AbstractLoader.SPRITESHEET") }, SVG: { get: createjs.deprecate(function() { return createjs.Types.SVG; }, "AbstractLoader.SVG") }, TEXT: { get: createjs.deprecate(function() { return createjs.Types.TEXT; }, "AbstractLoader.TEXT") }, XML: { get: createjs.deprecate(function() { return createjs.Types.XML; }, "AbstractLoader.XML") } }); } catch (e) {} /** * An internal initialization method, which is used for initial set up, but also to reset the LoadQueue. * @method init * @param preferXHR * @param basePath * @param crossOrigin * @private */ p.init = function (preferXHR, basePath, crossOrigin) { // public properties /** * Try and use XMLHttpRequest (XHR) when possible. Note that LoadQueue will default to tag loading or XHR * loading depending on the requirements for a media type. For example, HTML audio can not be loaded with XHR, * and plain text can not be loaded with tags, so it will default the the correct type instead of using the * user-defined type. * @type {Boolean} * @default true * @since 0.6.0 */ this.preferXHR = true; //TODO: Get/Set this._preferXHR = true; this.setPreferXHR(preferXHR); // protected properties /** * Whether the queue is currently paused or not. * @property _paused * @type {boolean} * @private */ this._paused = false; /** * A path that will be prepended on to the item's {{#crossLink "LoadItem/src:property"}}{{/crossLink}}. The * `_basePath` property will only be used if an item's source is relative, and does not include a protocol such * as `http://`, or a relative path such as `../`. * @property _basePath * @type {String} * @private * @since 0.3.1 */ this._basePath = basePath; /** * An optional flag to set on images that are loaded using PreloadJS, which enables CORS support. Images loaded * cross-domain by servers that support CORS require the crossOrigin flag to be loaded and interacted with by * a canvas. When loading locally, or with a server with no CORS support, this flag can cause other security issues, * so it is recommended to only set it if you are sure the server supports it. Currently, supported values are "" * and "Anonymous". * @property _crossOrigin * @type {String} * @default "" * @private * @since 0.4.1 */ this._crossOrigin = crossOrigin; /** * Determines if the loadStart event was dispatched already. This event is only fired one time, when the first * file is requested. * @property _loadStartWasDispatched * @type {Boolean} * @default false * @private */ this._loadStartWasDispatched = false; /** * Determines if there is currently a script loading. This helps ensure that only a single script loads at once when * using a script tag to do preloading. * @property _currentlyLoadingScript * @type {Boolean} * @private */ this._currentlyLoadingScript = null; /** * An array containing the currently downloading files. * @property _currentLoads * @type {Array} * @private */ this._currentLoads = []; /** * An array containing the queued items that have not yet started downloading. * @property _loadQueue * @type {Array} * @private */ this._loadQueue = []; /** * An array containing downloads that have not completed, so that the LoadQueue can be properly reset. * @property _loadQueueBackup * @type {Array} * @private */ this._loadQueueBackup = []; /** * An object hash of items that have finished downloading, indexed by the {{#crossLink "LoadItem"}}{{/crossLink}} * id. * @property _loadItemsById * @type {Object} * @private */ this._loadItemsById = {}; /** * An object hash of items that have finished downloading, indexed by {{#crossLink "LoadItem"}}{{/crossLink}} * source. * @property _loadItemsBySrc * @type {Object} * @private */ this._loadItemsBySrc = {}; /** * An object hash of loaded items, indexed by the ID of the {{#crossLink "LoadItem"}}{{/crossLink}}. * @property _loadedResults * @type {Object} * @private */ this._loadedResults = {}; /** * An object hash of un-parsed loaded items, indexed by the ID of the {{#crossLink "LoadItem"}}{{/crossLink}}. * @property _loadedRawResults * @type {Object} * @private */ this._loadedRawResults = {}; /** * The number of items that have been requested. This helps manage an overall progress without knowing how large * the files are before they are downloaded. This does not include items inside of loaders such as the * {{#crossLink "ManifestLoader"}}{{/crossLink}}. * @property _numItems * @type {Number} * @default 0 * @private */ this._numItems = 0; /** * The number of items that have completed loaded. This helps manage an overall progress without knowing how large * the files are before they are downloaded. * @property _numItemsLoaded * @type {Number} * @default 0 * @private */ this._numItemsLoaded = 0; /** * A list of scripts in the order they were requested. This helps ensure that scripts are "completed" in the right * order. * @property _scriptOrder * @type {Array} * @private */ this._scriptOrder = []; /** * A list of scripts that have been loaded. Items are added to this list as null when they are * requested, contain the loaded item if it has completed, but not been dispatched to the user, and true * once they are complete and have been dispatched. * @property _loadedScripts * @type {Array} * @private */ this._loadedScripts = []; /** * The last progress amount. This is used to suppress duplicate progress events. * @property _lastProgress * @type {Number} * @private * @since 0.6.0 */ this._lastProgress = NaN; }; // static properties // events /** * This event is fired when an individual file has loaded, and been processed. * @event fileload * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the * object will contain that value as a `src` property. * @param {Object} result The HTML tag or parsed result of the loaded item. * @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted * to a usable object. * @since 0.3.0 */ /** * This {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when an an individual file's progress changes. * @event fileprogress * @since 0.3.0 */ /** * This event is fired when an individual file starts to load. * @event filestart * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the * object will contain that value as a property. */ /** * Although it extends {{#crossLink "AbstractLoader"}}{{/crossLink}}, the `initialize` event is never fired from * a LoadQueue instance. * @event initialize * @private */ // public methods /** * Register a custom loaders class. New loaders are given precedence over loaders added earlier and default loaders. * It is recommended that loaders extend {{#crossLink "AbstractLoader"}}{{/crossLink}}. Loaders can only be added * once, and will be prepended to the list of available loaders. * @method registerLoader * @param {Function|AbstractLoader} loader The AbstractLoader class to add. * @since 0.6.0 */ p.registerLoader = function (loader) { if (!loader || !loader.canLoadItem) { throw new Error("loader is of an incorrect type."); } else if (this._availableLoaders.indexOf(loader) != -1) { throw new Error("loader already exists."); //LM: Maybe just silently fail here } this._availableLoaders.unshift(loader); }; /** * Remove a custom loader added using {{#crossLink "registerLoader"}}{{/crossLink}}. Only custom loaders can be * unregistered, the default loaders will always be available. * @method unregisterLoader * @param {Function|AbstractLoader} loader The AbstractLoader class to remove */ p.unregisterLoader = function (loader) { var idx = this._availableLoaders.indexOf(loader); if (idx != -1 && idx < this._defaultLoaderLength - 1) { this._availableLoaders.splice(idx, 1); } }; /** * Change the {{#crossLink "preferXHR:property"}}{{/crossLink}} value. Note that if this is set to `true`, it may * fail, or be ignored depending on the browser's capabilities and the load type. * @method setPreferXHR * @param {Boolean} value * @returns {Boolean} The value of {{#crossLink "preferXHR"}}{{/crossLink}} that was successfully set. * @since 0.6.0 */ p.setPreferXHR = function (value) { // Determine if we can use XHR. XHR defaults to TRUE, but the browser may not support it. //TODO: Should we be checking for the other XHR types? Might have to do a try/catch on the different types similar to createXHR. this.preferXHR = (value != false && window.XMLHttpRequest != null); return this.preferXHR; }; /** * Stops all queued and loading items, and clears the queue. This also removes all internal references to loaded * content, and allows the queue to be used again. * @method removeAll * @since 0.3.0 */ p.removeAll = function () { this.remove(); }; /** * Stops an item from being loaded, and removes it from the queue. If nothing is passed, all items are removed. * This also removes internal references to loaded item(s). * *

    Example

    * * queue.loadManifest([ * {src:"test.png", id:"png"}, * {src:"test.jpg", id:"jpg"}, * {src:"test.mp3", id:"mp3"} * ]); * queue.remove("png"); // Single item by ID * queue.remove("png", "test.jpg"); // Items as arguments. Mixed id and src. * queue.remove(["test.png", "jpg"]); // Items in an Array. Mixed id and src. * * @method remove * @param {String | Array} idsOrUrls* The id or ids to remove from this queue. You can pass an item, an array of * items, or multiple items as arguments. * @since 0.3.0 */ p.remove = function (idsOrUrls) { var args = null; if (idsOrUrls && !Array.isArray(idsOrUrls)) { args = [idsOrUrls]; } else if (idsOrUrls) { args = idsOrUrls; } else if (arguments.length > 0) { return; } var itemsWereRemoved = false; // Destroy everything if (!args) { this.close(); for (var n in this._loadItemsById) { this._disposeItem(this._loadItemsById[n]); } this.init(this.preferXHR, this._basePath); // Remove specific items } else { while (args.length) { var item = args.pop(); var r = this.getResult(item); //Remove from the main load Queue for (i = this._loadQueue.length - 1; i >= 0; i--) { loadItem = this._loadQueue[i].getItem(); if (loadItem.id == item || loadItem.src == item) { this._loadQueue.splice(i, 1)[0].cancel(); break; } } //Remove from the backup queue for (i = this._loadQueueBackup.length - 1; i >= 0; i--) { loadItem = this._loadQueueBackup[i].getItem(); if (loadItem.id == item || loadItem.src == item) { this._loadQueueBackup.splice(i, 1)[0].cancel(); break; } } if (r) { this._disposeItem(this.getItem(item)); } else { for (var i = this._currentLoads.length - 1; i >= 0; i--) { var loadItem = this._currentLoads[i].getItem(); if (loadItem.id == item || loadItem.src == item) { this._currentLoads.splice(i, 1)[0].cancel(); itemsWereRemoved = true; break; } } } } // If this was called during a load, try to load the next item. if (itemsWereRemoved) { this._loadNext(); } } }; /** * Stops all open loads, destroys any loaded items, and resets the queue, so all items can * be reloaded again by calling {{#crossLink "AbstractLoader/load"}}{{/crossLink}}. Items are not removed from the * queue. To remove items use the {{#crossLink "LoadQueue/remove"}}{{/crossLink}} or * {{#crossLink "LoadQueue/removeAll"}}{{/crossLink}} method. * @method reset * @since 0.3.0 */ p.reset = function () { this.close(); for (var n in this._loadItemsById) { this._disposeItem(this._loadItemsById[n]); } //Reset the queue to its start state var a = []; for (var i = 0, l = this._loadQueueBackup.length; i < l; i++) { a.push(this._loadQueueBackup[i].getItem()); } this.loadManifest(a, false); }; /** * Register a plugin. Plugins can map to load types (sound, image, etc), or specific extensions (png, mp3, etc). * Currently, only one plugin can exist per type/extension. * * When a plugin is installed, a getPreloadHandlers() method will be called on it. For more information * on this method, check out the {{#crossLink "SamplePlugin/getPreloadHandlers"}}{{/crossLink}} method in the * {{#crossLink "SamplePlugin"}}{{/crossLink}} class. * * Before a file is loaded, a matching plugin has an opportunity to modify the load. If a `callback` is returned * from the {{#crossLink "SamplePlugin/getPreloadHandlers"}}{{/crossLink}} method, it will be invoked first, and its * result may cancel or modify the item. The callback method can also return a `completeHandler` to be fired when * the file is loaded, or a `tag` object, which will manage the actual download. For more information on these * methods, check out the {{#crossLink "SamplePlugin/preloadHandler"}}{{/crossLink}} and {{#crossLink "SamplePlugin/fileLoadHandler"}}{{/crossLink}} * methods on the {{#crossLink "SamplePlugin"}}{{/crossLink}}. * * @method installPlugin * @param {Function} plugin The plugin class to install. */ p.installPlugin = function (plugin) { if (plugin == null) { return; } if (plugin.getPreloadHandlers != null) { this._plugins.push(plugin); var map = plugin.getPreloadHandlers(); map.scope = plugin; if (map.types != null) { for (var i = 0, l = map.types.length; i < l; i++) { this._typeCallbacks[map.types[i]] = map; } } if (map.extensions != null) { for (i = 0, l = map.extensions.length; i < l; i++) { this._extensionCallbacks[map.extensions[i]] = map; } } } }; /** * Set the maximum number of concurrent connections. Note that browsers and servers may have a built-in maximum * number of open connections, so any additional connections may remain in a pending state until the browser * opens the connection. When loading scripts using tags, and when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}} * is `true`, only one script is loaded at a time due to browser limitations. * *

    Example

    * * var queue = new createjs.LoadQueue(); * queue.setMaxConnections(10); // Allow 10 concurrent loads * * @method setMaxConnections * @param {Number} value The number of concurrent loads to allow. By default, only a single connection per LoadQueue * is open at any time. */ p.setMaxConnections = function (value) { this._maxConnections = value; if (!this._paused && this._loadQueue.length > 0) { this._loadNext(); } }; /** * Load a single file. To add multiple files at once, use the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} * method. * * Files are always appended to the current queue, so this method can be used multiple times to add files. * To clear the queue first, use the {{#crossLink "AbstractLoader/close"}}{{/crossLink}} method. * @method loadFile * @param {LoadItem|Object|String} file The file object or path to load. A file can be either *
      *
    • A {{#crossLink "LoadItem"}}{{/crossLink}} instance
    • *
    • An object containing properties defined by {{#crossLink "LoadItem"}}{{/crossLink}}
    • *
    • OR A string path to a resource. Note that this kind of load item will be converted to a {{#crossLink "LoadItem"}}{{/crossLink}} * in the background.
    • *
    * @param {Boolean} [loadNow=true] Kick off an immediate load (true) or wait for a load call (false). The default * value is true. If the queue is paused using {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}}, and the value is * `true`, the queue will resume automatically. * @param {String} [basePath] A base path that will be prepended to each file. The basePath argument overrides the * path specified in the constructor. Note that if you load a manifest using a file of type {{#crossLink "Types/MANIFEST:property"}}{{/crossLink}}, * its files will NOT use the basePath parameter. The basePath parameter is deprecated. * This parameter will be removed in a future version. Please either use the `basePath` parameter in the LoadQueue * constructor, or a `path` property in a manifest definition. */ p.loadFile = function (file, loadNow, basePath) { if (file == null) { var event = new createjs.ErrorEvent("PRELOAD_NO_FILE"); this._sendError(event); return; } this._addItem(file, null, basePath); if (loadNow !== false) { this.setPaused(false); } else { this.setPaused(true); } }; /** * Load an array of files. To load a single file, use the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} method. * The files in the manifest are requested in the same order, but may complete in a different order if the max * connections are set above 1 using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}. Scripts will load * in the right order as long as {{#crossLink "LoadQueue/maintainScriptOrder"}}{{/crossLink}} is true (which is * default). * * Files are always appended to the current queue, so this method can be used multiple times to add files. * To clear the queue first, use the {{#crossLink "AbstractLoader/close"}}{{/crossLink}} method. * @method loadManifest * @param {Array|String|Object} manifest An list of files to load. The loadManifest call supports four types of * manifests: *
      *
    1. A string path, which points to a manifest file, which is a JSON file that contains a "manifest" property, * which defines the list of files to load, and can optionally contain a "path" property, which will be * prepended to each file in the list.
    2. *
    3. An object which defines a "src", which is a JSON or JSONP file. A "callback" can be defined for JSONP * file. The JSON/JSONP file should contain a "manifest" property, which defines the list of files to load, * and can optionally contain a "path" property, which will be prepended to each file in the list.
    4. *
    5. An object which contains a "manifest" property, which defines the list of files to load, and can * optionally contain a "path" property, which will be prepended to each file in the list.
    6. *
    7. An Array of files to load.
    8. *
    * * Each "file" in a manifest can be either: *
      *
    • A {{#crossLink "LoadItem"}}{{/crossLink}} instance
    • *
    • An object containing properties defined by {{#crossLink "LoadItem"}}{{/crossLink}}
    • *
    • OR A string path to a resource. Note that this kind of load item will be converted to a {{#crossLink "LoadItem"}}{{/crossLink}} * in the background.
    • *
    * * @param {Boolean} [loadNow=true] Kick off an immediate load (true) or wait for a load call (false). The default * value is true. If the queue is paused using {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} and this value is * `true`, the queue will resume automatically. * @param {String} [basePath] A base path that will be prepended to each file. The basePath argument overrides the * path specified in the constructor. Note that if you load a manifest using a file of type {{#crossLink "LoadQueue/MANIFEST:property"}}{{/crossLink}}, * its files will NOT use the basePath parameter. The basePath parameter is deprecated. * This parameter will be removed in a future version. Please either use the `basePath` parameter in the LoadQueue * constructor, or a `path` property in a manifest definition. */ p.loadManifest = function (manifest, loadNow, basePath) { var fileList = null; var path = null; // Array-based list of items if (Array.isArray(manifest)) { if (manifest.length == 0) { var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_EMPTY"); this._sendError(event); return; } fileList = manifest; // String-based. Only file manifests can be specified this way. Any other types will cause an error when loaded. } else if (typeof(manifest) === "string") { fileList = [ { src: manifest, type: s.MANIFEST } ]; } else if (typeof(manifest) == "object") { // An object that defines a manifest path if (manifest.src !== undefined) { if (manifest.type == null) { manifest.type = s.MANIFEST; } else if (manifest.type != s.MANIFEST) { var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_TYPE"); this._sendError(event); } fileList = [manifest]; // An object that defines a manifest } else if (manifest.manifest !== undefined) { fileList = manifest.manifest; path = manifest.path; } // Unsupported. This will throw an error. } else { var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_NULL"); this._sendError(event); return; } for (var i = 0, l = fileList.length; i < l; i++) { this._addItem(fileList[i], path, basePath); } if (loadNow !== false) { this.setPaused(false); } else { this.setPaused(true); } }; /** * Start a LoadQueue that was created, but not automatically started. * @method load */ p.load = function () { this.setPaused(false); }; /** * Look up a {{#crossLink "LoadItem"}}{{/crossLink}} using either the "id" or "src" that was specified when loading it. Note that if no "id" was * supplied with the load item, the ID will be the "src", including a `path` property defined by a manifest. The * `basePath` will not be part of the ID. * @method getItem * @param {String} value The id or src of the load item. * @return {Object} The load item that was initially requested using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. This object is also returned via the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} * event as the `item` parameter. */ p.getItem = function (value) { return this._loadItemsById[value] || this._loadItemsBySrc[value]; }; /** * Look up a loaded result using either the "id" or "src" that was specified when loading it. Note that if no "id" * was supplied with the load item, the ID will be the "src", including a `path` property defined by a manifest. The * `basePath` will not be part of the ID. * @method getResult * @param {String} value The id or src of the load item. * @param {Boolean} [rawResult=false] Return a raw result instead of a formatted result. This applies to content * loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be * returned instead. * @return {Object} A result object containing the content that was loaded, such as: *
      *
    • An image tag (<image />) for images
    • *
    • A script tag for JavaScript (<script />). Note that scripts are automatically added to the HTML * DOM.
    • *
    • A style tag for CSS (<style /> or <link >)
    • *
    • Raw text for TEXT
    • *
    • A formatted JavaScript object defined by JSON
    • *
    • An XML document
    • *
    • A binary arraybuffer loaded by XHR
    • *
    • An audio tag (<audio >) for HTML audio. Note that it is recommended to use SoundJS APIs to play * loaded audio. Specifically, audio loaded by Flash and WebAudio will return a loader object using this method * which can not be used to play audio back.
    • *
    * This object is also returned via the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event as the 'item` * parameter. Note that if a raw result is requested, but not found, the result will be returned instead. */ p.getResult = function (value, rawResult) { var item = this._loadItemsById[value] || this._loadItemsBySrc[value]; if (item == null) { return null; } var id = item.id; if (rawResult && this._loadedRawResults[id]) { return this._loadedRawResults[id]; } return this._loadedResults[id]; }; /** * Generate an list of items loaded by this queue. * @method getItems * @param {Boolean} loaded Determines if only items that have been loaded should be returned. If false, in-progress * and failed load items will also be included. * @returns {Array} A list of objects that have been loaded. Each item includes the {{#crossLink "LoadItem"}}{{/crossLink}}, * result, and rawResult. * @since 0.6.0 */ p.getItems = function (loaded) { var arr = []; for (var n in this._loadItemsById) { var item = this._loadItemsById[n]; var result = this.getResult(n); if (loaded === true && result == null) { continue; } arr.push({ item: item, result: result, rawResult: this.getResult(n, true) }); } return arr; }; /** * Pause or resume the current load. Active loads will not be cancelled, but the next items in the queue will not * be processed when active loads complete. LoadQueues are not paused by default. * * Note that if new items are added to the queue using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or * {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}, a paused queue will be resumed, unless the `loadNow` * argument is `false`. * @method setPaused * @param {Boolean} value Whether the queue should be paused or not. */ p.setPaused = function (value) { this._paused = value; if (!this._paused) { this._loadNext(); } }; /** * Close the active queue. Closing a queue completely empties the queue, and prevents any remaining items from * starting to download. Note that currently any active loads will remain open, and events may be processed. * * To stop and restart a queue, use the {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} method instead. * @method close */ p.close = function () { while (this._currentLoads.length) { this._currentLoads.pop().cancel(); } this._scriptOrder.length = 0; this._loadedScripts.length = 0; this.loadStartWasDispatched = false; this._itemCount = 0; this._lastProgress = NaN; }; // protected methods /** * Add an item to the queue. Items are formatted into a usable object containing all the properties necessary to * load the content. The load queue is populated with the loader instance that handles preloading, and not the load * item that was passed in by the user. To look up the load item by id or src, use the {{#crossLink "LoadQueue.getItem"}}{{/crossLink}} * method. * @method _addItem * @param {String|Object} value The item to add to the queue. * @param {String} [path] An optional path prepended to the `src`. The path will only be prepended if the src is * relative, and does not start with a protocol such as `http://`, or a path like `../`. If the LoadQueue was * provided a {{#crossLink "_basePath"}}{{/crossLink}}, then it will optionally be prepended after. * @param {String} [basePath] DeprecatedAn optional basePath passed into a {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} call. This parameter will be removed in a future tagged * version. * @private */ p._addItem = function (value, path, basePath) { var item = this._createLoadItem(value, path, basePath); // basePath and manifest path are added to the src. if (item == null) { return; } // Sometimes plugins or types should be skipped. var loader = this._createLoader(item); if (loader != null) { if ("plugins" in loader) { loader.plugins = this._plugins; } item._loader = loader; this._loadQueue.push(loader); this._loadQueueBackup.push(loader); this._numItems++; this._updateProgress(); // Only worry about script order when using XHR to load scripts. Tags are only loading one at a time. if ((this.maintainScriptOrder && item.type == createjs.Types.JAVASCRIPT //&& loader instanceof createjs.XHRLoader //NOTE: Have to track all JS files this way ) || item.maintainOrder === true) { this._scriptOrder.push(item); this._loadedScripts.push(null); } } }; /** * Create a refined {{#crossLink "LoadItem"}}{{/crossLink}}, which contains all the required properties. The type of * item is determined by browser support, requirements based on the file type, and developer settings. For example, * XHR is only used for file types that support it in new browsers. * * Before the item is returned, any plugins registered to handle the type or extension will be fired, which may * alter the load item. * @method _createLoadItem * @param {String | Object | HTMLAudioElement | HTMLImageElement} value The item that needs to be preloaded. * @param {String} [path] A path to prepend to the item's source. Sources beginning with http:// or similar will * not receive a path. Since PreloadJS 0.4.1, the src will be modified to include the `path` and {{#crossLink "LoadQueue/_basePath:property"}}{{/crossLink}} * when it is added. * @param {String} [basePath] Deprectated A base path to prepend to the items source in addition to * the path argument. * @return {Object} The loader instance that will be used. * @private */ p._createLoadItem = function (value, path, basePath) { var item = createjs.LoadItem.create(value); if (item == null) { return null; } var bp = ""; // Store the generated basePath var useBasePath = basePath || this._basePath; if (item.src instanceof Object) { if (!item.type) { return null; } // the the src is an object, type is required to pass off to plugin if (path) { bp = path; var pathMatch = createjs.URLUtils.parseURI(path); // Also append basePath if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) { bp = useBasePath + bp; } } else if (useBasePath != null) { bp = useBasePath; } } else { // Determine Extension, etc. var match = createjs.URLUtils.parseURI(item.src); if (match.extension) { item.ext = match.extension; } if (item.type == null) { item.type = createjs.RequestUtils.getTypeByExtension(item.ext); } // Inject path & basePath var autoId = item.src; if (!match.absolute && !match.relative) { if (path) { bp = path; var pathMatch = createjs.URLUtils.parseURI(path); autoId = path + autoId; // Also append basePath if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) { bp = useBasePath + bp; } } else if (useBasePath != null) { bp = useBasePath; } } item.src = bp + item.src; } item.path = bp; // If there's no id, set one now. if (item.id === undefined || item.id === null || item.id === "") { item.id = autoId; } // Give plugins a chance to modify the loadItem: var customHandler = this._typeCallbacks[item.type] || this._extensionCallbacks[item.ext]; if (customHandler) { // Plugins are now passed both the full source, as well as a combined path+basePath (appropriately) var result = customHandler.callback.call(customHandler.scope, item, this); // The plugin will handle the load, or has canceled it. Ignore it. if (result === false) { return null; // Load as normal: } else if (result === true) { // Do Nothing // Result is a loader class: } else if (result != null) { item._loader = result; } // Update the extension in case the type changed: match = createjs.URLUtils.parseURI(item.src); if (match.extension != null) { item.ext = match.extension; } } // Store the item for lookup. This also helps clean-up later. this._loadItemsById[item.id] = item; this._loadItemsBySrc[item.src] = item; if (item.crossOrigin == null) { item.crossOrigin = this._crossOrigin; } return item; }; /** * Create a loader for a load item. * @method _createLoader * @param {Object} item A formatted load item that can be used to generate a loader. * @return {AbstractLoader} A loader that can be used to load content. * @private */ p._createLoader = function (item) { if (item._loader != null) { // A plugin already specified a loader return item._loader; } // Initially, try and use the provided/supported XHR mode: var preferXHR = this.preferXHR; for (var i = 0; i < this._availableLoaders.length; i++) { var loader = this._availableLoaders[i]; if (loader && loader.canLoadItem(item)) { return new loader(item, preferXHR); } } // TODO: Log error (requires createjs.log) return null; }; /** * Load the next item in the queue. If the queue is empty (all items have been loaded), then the complete event * is processed. The queue will "fill up" any empty slots, up to the max connection specified using * {{#crossLink "LoadQueue.setMaxConnections"}}{{/crossLink}} method. The only exception is scripts that are loaded * using tags, which have to be loaded one at a time to maintain load order. * @method _loadNext * @private */ p._loadNext = function () { if (this._paused) { return; } // Only dispatch loadstart event when the first file is loaded. if (!this._loadStartWasDispatched) { this._sendLoadStart(); this._loadStartWasDispatched = true; } // The queue has completed. if (this._numItems == this._numItemsLoaded) { this.loaded = true; this._sendComplete(); // Load the next queue, if it has been defined. if (this.next && this.next.load) { this.next.load(); } } else { this.loaded = false; } // Must iterate forwards to load in the right order. for (var i = 0; i < this._loadQueue.length; i++) { if (this._currentLoads.length >= this._maxConnections) { break; } var loader = this._loadQueue[i]; // Determine if we should be only loading one tag-script at a time: // Note: maintainOrder items don't do anything here because we can hold onto their loaded value if (!this._canStartLoad(loader)) { continue; } this._loadQueue.splice(i, 1); i--; this._loadItem(loader); } }; /** * Begin loading an item. Event listeners are not added to the loaders until the load starts. * @method _loadItem * @param {AbstractLoader} loader The loader instance to start. Currently, this will be an XHRLoader or TagLoader. * @private */ p._loadItem = function (loader) { loader.on("fileload", this._handleFileLoad, this); loader.on("progress", this._handleProgress, this); loader.on("complete", this._handleFileComplete, this); loader.on("error", this._handleError, this); loader.on("fileerror", this._handleFileError, this); this._currentLoads.push(loader); this._sendFileStart(loader.getItem()); loader.load(); }; /** * The callback that is fired when a loader loads a file. This enables loaders like {{#crossLink "ManifestLoader"}}{{/crossLink}} * to maintain internal queues, but for this queue to dispatch the {{#crossLink "fileload:event"}}{{/crossLink}} * events. * @param {Event} event The {{#crossLink "AbstractLoader/fileload:event"}}{{/crossLink}} event from the loader. * @private * @since 0.6.0 */ p._handleFileLoad = function (event) { event.target = null; this.dispatchEvent(event); }; /** * The callback that is fired when a loader encounters an error from an internal file load operation. This enables * loaders like M * @param event * @private */ p._handleFileError = function (event) { var newEvent = new createjs.ErrorEvent("FILE_LOAD_ERROR", null, event.item); this._sendError(newEvent); }; /** * The callback that is fired when a loader encounters an error. The queue will continue loading unless {{#crossLink "LoadQueue/stopOnError:property"}}{{/crossLink}} * is set to `true`. * @method _handleError * @param {ErrorEvent} event The error event, containing relevant error information. * @private */ p._handleError = function (event) { var loader = event.target; this._numItemsLoaded++; this._finishOrderedItem(loader, true); this._updateProgress(); var newEvent = new createjs.ErrorEvent("FILE_LOAD_ERROR", null, loader.getItem()); // TODO: Propagate actual error message. this._sendError(newEvent); if (!this.stopOnError) { this._removeLoadItem(loader); this._cleanLoadItem(loader); this._loadNext(); } else { this.setPaused(true); } }; /** * An item has finished loading. We can assume that it is totally loaded, has been parsed for immediate use, and * is available as the "result" property on the load item. The raw text result for a parsed item (such as JSON, XML, * CSS, JavaScript, etc) is available as the "rawResult" property, and can also be looked up using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}. * @method _handleFileComplete * @param {Event} event The event object from the loader. * @private */ p._handleFileComplete = function (event) { var loader = event.target; var item = loader.getItem(); var result = loader.getResult(); this._loadedResults[item.id] = result; var rawResult = loader.getResult(true); if (rawResult != null && rawResult !== result) { this._loadedRawResults[item.id] = rawResult; } this._saveLoadedItems(loader); // Remove the load item this._removeLoadItem(loader); if (!this._finishOrderedItem(loader)) { // The item was NOT managed, so process it now this._processFinishedLoad(item, loader); } // Clean up the load item this._cleanLoadItem(loader); }; /** * Some loaders might load additional content, other than the item they were passed (such as {{#crossLink "ManifestLoader"}}{{/crossLink}}). * Any items exposed by the loader using {{#crossLink "AbstractLoader/getLoadItems"}}{{/crossLink}} are added to the * LoadQueue's look-ups, including {{#crossLink "getItem"}}{{/crossLink}} and {{#crossLink "getResult"}}{{/crossLink}} * methods. * @method _saveLoadedItems * @param {AbstractLoader} loader * @protected * @since 0.6.0 */ p._saveLoadedItems = function (loader) { // TODO: Not sure how to handle this. Would be nice to expose the items. // Loaders may load sub-items. This adds them to this queue var list = loader.getLoadedItems(); if (list === null) { return; } for (var i = 0; i < list.length; i++) { var item = list[i].item; // Store item lookups this._loadItemsBySrc[item.src] = item; this._loadItemsById[item.id] = item; // Store loaded content this._loadedResults[item.id] = list[i].result; this._loadedRawResults[item.id] = list[i].rawResult; } }; /** * Flag an item as finished. If the item's order is being managed, then ensure that it is allowed to finish, and if * so, trigger prior items to trigger as well. * @method _finishOrderedItem * @param {AbstractLoader} loader * @param {Boolean} loadFailed * @return {Boolean} If the item's order is being managed. This allows the caller to take an alternate * behaviour if it is. * @private */ p._finishOrderedItem = function (loader, loadFailed) { var item = loader.getItem(); if ((this.maintainScriptOrder && item.type == createjs.Types.JAVASCRIPT) || item.maintainOrder) { //TODO: Evaluate removal of the _currentlyLoadingScript if (loader instanceof createjs.JavaScriptLoader) { this._currentlyLoadingScript = false; } var index = createjs.indexOf(this._scriptOrder, item); if (index == -1) { return false; } // This loader no longer exists this._loadedScripts[index] = (loadFailed === true) ? true : item; this._checkScriptLoadOrder(); return true; } return false; }; /** * Ensure the scripts load and dispatch in the correct order. When using XHR, scripts are stored in an array in the * order they were added, but with a "null" value. When they are completed, the value is set to the load item, * and then when they are processed and dispatched, the value is set to `true`. This method simply * iterates the array, and ensures that any loaded items that are not preceded by a `null` value are * dispatched. * @method _checkScriptLoadOrder * @private */ p._checkScriptLoadOrder = function () { var l = this._loadedScripts.length; for (var i = 0; i < l; i++) { var item = this._loadedScripts[i]; if (item === null) { break; } // This is still loading. Do not process further. if (item === true) { continue; } // This has completed, and been processed. Move on. var loadItem = this._loadedResults[item.id]; if (item.type == createjs.Types.JAVASCRIPT) { // Append script tags to the head automatically. createjs.DomUtils.appendToHead(loadItem); } var loader = item._loader; this._processFinishedLoad(item, loader); this._loadedScripts[i] = true; } }; /** * A file has completed loading, and the LoadQueue can move on. This triggers the complete event, and kick-starts * the next item. * @method _processFinishedLoad * @param {LoadItem|Object} item * @param {AbstractLoader} loader * @protected */ p._processFinishedLoad = function (item, loader) { this._numItemsLoaded++; // Since LoadQueue needs maintain order, we can't append scripts in the loader. // So we do it here instead. Or in _checkScriptLoadOrder(); if (!this.maintainScriptOrder && item.type == createjs.Types.JAVASCRIPT) { var tag = loader.getTag(); createjs.DomUtils.appendToHead(tag); } this._updateProgress(); this._sendFileComplete(item, loader); this._loadNext(); }; /** * Ensure items with `maintainOrder=true` that are before the specified item have loaded. This only applies to * JavaScript items that are being loaded with a TagLoader, since they have to be loaded and completed before * the script can even be started, since it exist in the DOM while loading. * @method _canStartLoad * @param {AbstractLoader} loader The loader for the item * @return {Boolean} Whether the item can start a load or not. * @private */ p._canStartLoad = function (loader) { if (!this.maintainScriptOrder || loader.preferXHR) { return true; } var item = loader.getItem(); if (item.type != createjs.Types.JAVASCRIPT) { return true; } if (this._currentlyLoadingScript) { return false; } var index = this._scriptOrder.indexOf(item); var i = 0; while (i < index) { var checkItem = this._loadedScripts[i]; if (checkItem == null) { return false; } i++; } this._currentlyLoadingScript = true; return true; }; /** * A load item is completed or was canceled, and needs to be removed from the LoadQueue. * @method _removeLoadItem * @param {AbstractLoader} loader A loader instance to remove. * @private */ p._removeLoadItem = function (loader) { var l = this._currentLoads.length; for (var i = 0; i < l; i++) { if (this._currentLoads[i] == loader) { this._currentLoads.splice(i, 1); break; } } }; /** * Remove unneeded references from a loader. * * @param loader * @private */ p._cleanLoadItem = function(loader) { var item = loader.getItem(); if (item) { delete item._loader; } } /** * An item has dispatched progress. Propagate that progress, and update the LoadQueue's overall progress. * @method _handleProgress * @param {ProgressEvent} event The progress event from the item. * @private */ p._handleProgress = function (event) { var loader = event.target; this._sendFileProgress(loader.getItem(), loader.progress); this._updateProgress(); }; /** * Overall progress has changed, so determine the new progress amount and dispatch it. This changes any time an * item dispatches progress or completes. Note that since we don't always know the actual filesize of items before * they are loaded. In this case, we define a "slot" for each item (1 item in 10 would get 10%), and then append * loaded progress on top of the already-loaded items. * * For example, if 5/10 items have loaded, and item 6 is 20% loaded, the total progress would be: *
      *
    • 5/10 of the items in the queue (50%)
    • *
    • plus 20% of item 6's slot (2%)
    • *
    • equals 52%
    • *
    * @method _updateProgress * @private */ p._updateProgress = function () { var loaded = this._numItemsLoaded / this._numItems; // Fully Loaded Progress var remaining = this._numItems - this._numItemsLoaded; if (remaining > 0) { var chunk = 0; for (var i = 0, l = this._currentLoads.length; i < l; i++) { chunk += this._currentLoads[i].progress; } loaded += (chunk / remaining) * (remaining / this._numItems); } if (this._lastProgress != loaded) { this._sendProgress(loaded); this._lastProgress = loaded; } }; /** * Clean out item results, to free them from memory. Mainly, the loaded item and results are cleared from internal * hashes. * @method _disposeItem * @param {LoadItem|Object} item The item that was passed in for preloading. * @private */ p._disposeItem = function (item) { delete this._loadedResults[item.id]; delete this._loadedRawResults[item.id]; delete this._loadItemsById[item.id]; delete this._loadItemsBySrc[item.src]; }; /** * Dispatch a "fileprogress" {{#crossLink "Event"}}{{/crossLink}}. Please see the LoadQueue {{#crossLink "LoadQueue/fileprogress:event"}}{{/crossLink}} * event for details on the event payload. * @method _sendFileProgress * @param {LoadItem|Object} item The item that is being loaded. * @param {Number} progress The amount the item has been loaded (between 0 and 1). * @protected */ p._sendFileProgress = function (item, progress) { if (this._isCanceled() || this._paused) { return; } if (!this.hasEventListener("fileprogress")) { return; } //LM: Rework ProgressEvent to support this? var event = new createjs.Event("fileprogress"); event.progress = progress; event.loaded = progress; event.total = 1; event.item = item; this.dispatchEvent(event); }; /** * Dispatch a fileload {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event for * details on the event payload. * @method _sendFileComplete * @param {LoadItemObject} item The item that is being loaded. * @param {AbstractLoader} loader * @protected */ p._sendFileComplete = function (item, loader) { if (this._isCanceled() || this._paused) { return; } var event = new createjs.Event("fileload"); event.loader = loader; event.item = item; event.result = this._loadedResults[item.id]; event.rawResult = this._loadedRawResults[item.id]; // This calls a handler specified on the actual load item. Currently, the SoundJS plugin uses this. if (item.completeHandler) { item.completeHandler(event); } this.hasEventListener("fileload") && this.dispatchEvent(event); }; /** * Dispatch a filestart {{#crossLink "Event"}}{{/crossLink}} immediately before a file starts to load. Please see * the {{#crossLink "LoadQueue/filestart:event"}}{{/crossLink}} event for details on the event payload. * @method _sendFileStart * @param {LoadItem|Object} item The item that is being loaded. * @protected */ p._sendFileStart = function (item) { var event = new createjs.Event("filestart"); event.item = item; this.hasEventListener("filestart") && this.dispatchEvent(event); }; p.toString = function () { return "[PreloadJS LoadQueue]"; }; createjs.LoadQueue = createjs.promote(LoadQueue, "AbstractLoader"); }()); //############################################################################## // TextLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for Text files. * @class TextLoader * @param {LoadItem|Object} loadItem * @extends AbstractLoader * @constructor */ function TextLoader(loadItem) { this.AbstractLoader_constructor(loadItem, true, createjs.Types.TEXT); }; var p = createjs.extend(TextLoader, createjs.AbstractLoader); var s = TextLoader; // static methods /** * Determines if the loader can load a specific item. This loader loads items that are of type {{#crossLink "Types/TEXT:property"}}{{/crossLink}}, * but is also the default loader if a file type can not be determined. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.TEXT; }; createjs.TextLoader = createjs.promote(TextLoader, "AbstractLoader"); }()); //############################################################################## // BinaryLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for binary files. This is useful for loading web audio, or content that requires an ArrayBuffer. * @class BinaryLoader * @param {LoadItem|Object} loadItem * @extends AbstractLoader * @constructor */ function BinaryLoader(loadItem) { this.AbstractLoader_constructor(loadItem, true, createjs.Types.BINARY); this.on("initialize", this._updateXHR, this); }; var p = createjs.extend(BinaryLoader, createjs.AbstractLoader); var s = BinaryLoader; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/BINARY:property"}}{{/crossLink}} * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.BINARY; }; // private methods /** * Before the item loads, set the response type to "arraybuffer" * @property _updateXHR * @param {Event} event * @private */ p._updateXHR = function (event) { event.loader.setResponseType("arraybuffer"); }; createjs.BinaryLoader = createjs.promote(BinaryLoader, "AbstractLoader"); }()); //############################################################################## // CSSLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for CSS files. * @class CSSLoader * @param {LoadItem|Object} loadItem * @param {Boolean} preferXHR * @extends AbstractLoader * @constructor */ function CSSLoader(loadItem, preferXHR) { this.AbstractLoader_constructor(loadItem, preferXHR, createjs.Types.CSS); // public properties this.resultFormatter = this._formatResult; // protected properties this._tagSrcAttribute = "href"; if (preferXHR) { this._tag = createjs.Elements.style(); } else { this._tag = createjs.Elements.link(); } this._tag.rel = "stylesheet"; this._tag.type = "text/css"; }; var p = createjs.extend(CSSLoader, createjs.AbstractLoader); var s = CSSLoader; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/CSS:property"}}{{/crossLink}}. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.CSS; }; // protected methods /** * The result formatter for CSS files. * @method _formatResult * @param {AbstractLoader} loader * @returns {HTMLLinkElement|HTMLStyleElement} * @private */ p._formatResult = function (loader) { if (this._preferXHR) { var tag = loader.getTag(); if (tag.styleSheet) { // IE tag.styleSheet.cssText = loader.getResult(true); } else { var textNode = createjs.Elements.text(loader.getResult(true)); tag.appendChild(textNode); } } else { tag = this._tag; } createjs.DomUtils.appendToHead(tag); return tag; }; createjs.CSSLoader = createjs.promote(CSSLoader, "AbstractLoader"); }()); //############################################################################## // FontLoader.js //############################################################################## (function () { "use strict"; // constructor: /** * A loader that handles font files, CSS definitions, and CSS paths. FontLoader doesn't actually preload fonts * themselves, but rather generates CSS definitions, and then tests the size changes on an HTML5 Canvas element. * * Note that FontLoader does not support tag-based loading due to the requirement that CSS be read to determine the * font definitions to test for. * @class FontLoader * @param {LoadItem|object|string} loadItem The item to be loaded. * @extends AbstractLoader * @constructor **/ function FontLoader(loadItem, preferXHR) { this.AbstractLoader_constructor(loadItem, preferXHR, loadItem.type); // private properties: /** * A lookup of font faces to load. * @property _faces * @protected * @type Object **/ this._faces = {}; /** * A list of font faces currently being "watched". Watched fonts will be tested on a regular interval, and be * removed from this list when they are complete. * @oroperty _watched * @type {Array} * @protected */ this._watched = []; /** * A count of the total font faces to load. * @property _count * @type {number} * @protected * @default 0 */ this._count = 0; /** * The interval for checking if fonts have been loaded. * @property _watchInterval * @type {Number} * @protected */ this._watchInterval = null; /** * The timeout for determining if a font can't be loaded. Uses the LoadItem {{#crossLink "LoadImte/timeout:property"}}{{/crossLink}} * value. * @property _loadTimeout * @type {Number} * @protected */ this._loadTimeout = null; /** * Determines if generated CSS should be injected into the document. * @property _injectCSS * @type {boolean} * @protected */ this._injectCSS = (loadItem.injectCSS === undefined) ? true : loadItem.injectCSS; this.dispatchEvent("initialize"); } var p = createjs.extend(FontLoader, createjs.AbstractLoader); /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/FONT:property"}}{{/crossLink}}. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ FontLoader.canLoadItem = function (item) { return item.type == createjs.Types.FONT || item.type == createjs.Types.FONTCSS; }; // static properties: /** * Sample text used by the FontLoader to determine if the font has been loaded. The sample text size is compared * to the loaded font size, and a change indicates that the font has completed. * @property sampleText * @type {String} * @default abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ * @static * @private */ FontLoader.sampleText = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /** * The canvas context used to test the font size. Note that this currently requires an HTML DOM. * @property _ctx * @type {CanvasRenderingContext2D} * @static * @private */ FontLoader._ctx = document.createElement("canvas").getContext("2d"); // TODO: Consider a method to do this like EaselJS Stage has. /** * A list of reference fonts to test. Multiple faces are tested to address the rare case of a loaded font being the * exact same dimensions as the test font. * @property _referenceFonts * @type {Array} * @default ["serif", "monospace"] * @private */ FontLoader._referenceFonts = ["serif","monospace"]; /** * A regular expression that pulls out possible style values from the font name. *
      *
    • This includes font names that include thin, normal, book, regular, medium, black, and heavy (such as * "Arial Black")
    • *
    • Weight modifiers including extra, ultra, semi, demi, light, and bold (such as "WorkSans SemiBold")
    • *
    * * Weight descriptions map to font weight values by default using the following (from * http://www.w3.org/TR/css3-fonts/#font-weight-numeric-values): *
      *
    • 100 - Thin
    • *
    • 200 - Extra Light, Ultra Light
    • *
    • 300 - Light, Semi Light, Demi Light
    • *
    • 400 - Normal, Book, Regular
    • *
    • 500 - Medium
    • *
    • 600 - Semi Bold, Demi Bold
    • *
    • 700 - Bold
    • *
    • 800 - Extra Bold, Ultra Bold
    • *
    • 900 - Black, Heavy
    • *
    * @property WEIGHT_REGEX * @type {RegExp} * @static */ FontLoader.WEIGHT_REGEX = /[- ._]*(thin|normal|book|regular|medium|black|heavy|[1-9]00|(?:extra|ultra|semi|demi)?[- ._]*(?:light|bold))[- ._]*/ig; /** * A regular expression that pulls out possible style values from the font name. These include "italic" * and "oblique". * @property STYLE_REGEX * @type {RegExp} * @static */ FontLoader.STYLE_REGEX = /[- ._]*(italic|oblique)[- ._]*/ig; /** * A lookup of font types for generating a CSS definition. For example, TTF fonts requires a "truetype" type. * @property FONT_FORMAT * @type {Object} * @static */ FontLoader.FONT_FORMAT = {woff2:"woff2", woff:"woff", ttf:"truetype", otf:"truetype"}; /** * A lookup of font weights based on a name. These values are from http://www.w3.org/TR/css3-fonts/#font-weight-numeric-values. * @property FONT_WEIGHT * @type {Object} * @static */ FontLoader.FONT_WEIGHT = {thin:100, extralight:200, ultralight:200, light:300, semilight:300, demilight:300, book:"normal", regular:"normal", semibold:600, demibold:600, extrabold:800, ultrabold:800, black:900, heavy:900}; /** * The frequency in milliseconds to check for loaded fonts. * @property WATCH_DURATION * @type {number} * @default 10 * @static */ FontLoader.WATCH_DURATION = 10; // public methods: p.load = function() { if (this.type == createjs.Types.FONTCSS) { var loaded = this._watchCSS(); // If the CSS is not ready, it will create a request, which AbstractLoader can handle. if (!loaded) { this.AbstractLoader_load(); return; } } else if (this._item.src instanceof Array) { this._watchFontArray(); } else { var def = this._defFromSrc(this._item.src); this._watchFont(def); this._injectStyleTag(this._cssFromDef(def)); } this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); this.dispatchEvent("loadstart"); }; /** * The font load has timed out. This is called via a setTimeout. * callback. * @method _handleTimeout * @protected */ p._handleTimeout = function () { this._stopWatching(); this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT")); }; // WatchCSS does the work for us, and provides a modified src. p._createRequest = function() { return this._request; }; // Events come from the internal XHR loader. p.handleEvent = function (event) { switch (event.type) { case "complete": this._rawResult = event.target._response; this._result = true; this._parseCSS(this._rawResult); break; case "error": this._stopWatching(); this.AbstractLoader_handleEvent(event); break; } }; // private methods: /** * Determine if the provided CSS is a string definition, CSS HTML element, or a CSS file URI. Depending on the * format, the CSS will be parsed, or loaded. * @method _watchCSS * @returns {boolean} Whether or not the CSS is ready * @protected */ p._watchCSS = function() { var src = this._item.src; // An HTMLElement was passed in. Just use it. if (src instanceof HTMLStyleElement) { if (this._injectCSS && !src.parentNode) { (document.head || document.getElementsByTagName('head')[0]).appendChild(src); } this._injectCSS = false; src = "\n"+src.textContent; } // A CSS string was passed in. Parse and use it if (src.search(/\n|\r|@font-face/i) !== -1) { // css string. this._parseCSS(src); return true; } // Load a CSS Path. Note that we CAN NOT load it without XHR because we need to read the CSS definition this._request = new createjs.XHRRequest(this._item); return false; }; /** * Parse a CSS string to determine the fonts to load. * @method _parseCSS * @param {String} css The CSS string to parse * @protected */ p._parseCSS = function(css) { var regex = /@font-face\s*\{([^}]+)}/g while (true) { var result = regex.exec(css); if (!result) { break; } this._watchFont(this._parseFontFace(result[1])); } this._injectStyleTag(css); }; /** * The provided fonts were an array of object or string definitions. Parse them, and inject any that are ready. * @method _watchFontArray * @protected */ p._watchFontArray = function() { var arr = this._item.src, css = "", def; for (var i=arr.length-1; i>=0; i--) { var o = arr[i]; if (typeof o === "string") { def = this._defFromSrc(o) } else { def = this._defFromObj(o); } this._watchFont(def); css += this._cssFromDef(def)+"\n"; } this._injectStyleTag(css); }; /** * Inject any style definitions into the document head. This is necessary when the definition is just a string or * object definition in order for the styles to be applied to the document. If the loaded fonts are already HTML CSS * elements, they don't need to be appended again. * @method _injectStyleTag * @param {String} css The CSS string content to be appended to the * @protected */ p._injectStyleTag = function(css) { if (!this._injectCSS) { return; } var head = document.head || document.getElementsByTagName('head')[0]; var styleTag = document.createElement("style"); styleTag.type = "text/css"; if (styleTag.styleSheet){ styleTag.styleSheet.cssText = css; } else { styleTag.appendChild(document.createTextNode(css)); } head.appendChild(styleTag); }; /** * Determine the font face from a CSS definition. * @method _parseFontFace * @param {String} str The CSS string definition * @protected * @return {String} A modified CSS object containing family name, src, style, and weight */ p._parseFontFace = function(str) { var family = this._getCSSValue(str, "font-family"), src = this._getCSSValue(str, "src"); if (!family || !src) { return null; } return this._defFromObj({ family: family, src: src, style: this._getCSSValue(str, "font-style"), weight: this._getCSSValue(str, "font-weight") }); }; /** * Add a font to the list of fonts currently being watched. If the font is already watched or loaded, it won't be * added again. * @method _watchFont * @param {Object} def The font definition * @protected */ p._watchFont = function(def) { if (!def || this._faces[def.id]) { return; } this._faces[def.id] = def; this._watched.push(def); this._count++; this._calculateReferenceSizes(def); this._startWatching(); }; /** * Create a interval to check for loaded fonts. Only one interval is used for all fonts. The fonts are checked based * on the {{#crossLink "FontLoader/WATCH_DURATION:property"}}{{/crossLink}}. * @method _startWatching * @protected */ p._startWatching = function() { if (this._watchInterval != null) { return; } this._watchInterval = setInterval(createjs.proxy(this._watch, this), FontLoader.WATCH_DURATION); }; /** * Clear the interval used to check fonts. This happens when all fonts are loaded, or an error occurs, such as a * CSS file error, or a load timeout. * @method _stopWatching * @protected */ p._stopWatching = function() { clearInterval(this._watchInterval); clearTimeout(this._loadTimeout); this._watchInterval = null; }; /** * Check all the fonts that have not been loaded. The fonts are drawn to a canvas in memory, and if their font size * varies from the default text size, then the font is considered loaded. * * A {{#crossLink "AbstractLoader/fileload"}}{{/crossLink}} event will be dispatched when each file is loaded, along * with the font family name as the `item` value. A {{#crossLink "ProgressEvent"}}{{/crossLink}} is dispatched a * maximum of one time per check when any fonts are loaded, with the {{#crossLink "ProgressEvent/progress:property"}}{{/crossLink}} * value showing the percentage of fonts that have loaded. * @method _watch * @protected */ p._watch = function() { var defs = this._watched, refFonts = FontLoader._referenceFonts, l = defs.length; for (var i = l - 1; i >= 0; i--) { var def = defs[i], refs = def.refs; for (var j = refs.length - 1; j >= 0; j--) { var w = this._getTextWidth(def.family + "," + refFonts[j], def.weight, def.style); if (w != refs[j]) { var event = new createjs.Event("fileload"); def.type = "font-family"; event.item = def; this.dispatchEvent(event); defs.splice(i, 1); break; } } } if (l !== defs.length) { var event = new createjs.ProgressEvent(this._count-defs.length, this._count); this.dispatchEvent(event); } if (l === 0) { this._stopWatching(); this._sendComplete(); } }; /** * Determine the default size of the reference fonts used to compare against loaded fonts. * @method _calculateReferenceSizes * @param {Object} def The font definition to get the size of. * @protected */ p._calculateReferenceSizes = function(def) { var refFonts = FontLoader._referenceFonts; var refs = def.refs = []; for (var i=0; iwithout requiring CORS. * JSONP files are loaded as JavaScript, and the "callback" is executed once they are loaded. The callback in the * JSONP must match the callback passed to the loadItem. * *

    Example JSONP

    * * callbackName({ * "name": "value", * "num": 3, * "obj": { "bool":true } * }); * *

    Example

    * * var loadItem = {id:"json", type:"jsonp", src:"http://server.com/text.json", callback:"callbackName"} * var queue = new createjs.LoadQueue(); * queue.on("complete", handleComplete); * queue.loadItem(loadItem); * * function handleComplete(event) } * var json = queue.getResult("json"); * console.log(json.obj.bool); // true * } * * JSONP files loaded concurrently require a unique callback. To ensure JSONP files are loaded in order, * either use the {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}} method (set to 1), or set * {{#crossLink "LoadItem/maintainOrder:property"}}{{/crossLink}} on items with the same callback. * * Important note: Some browsers will prevent JSONP from firing the callback if the file was loaded as JSON, and not * JavaScript. You may have to have your server give you a JavaScript mime-type for this to work. * * @class JSONPLoader * @param {LoadItem|Object} loadItem * @extends AbstractLoader * @constructor */ function JSONPLoader(loadItem) { this.AbstractLoader_constructor(loadItem, false, createjs.Types.JSONP); this.setTag(createjs.Elements.script()); this.getTag().type = "text/javascript"; }; var p = createjs.extend(JSONPLoader, createjs.AbstractLoader); var s = JSONPLoader; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/JSONP:property"}}{{/crossLink}}. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.JSONP; }; // public methods p.cancel = function () { this.AbstractLoader_cancel(); this._dispose(); }; /** * Loads the JSONp file. Because of the unique loading needs of JSONp * we don't use the AbstractLoader.load() method. * * @method load * */ p.load = function () { if (this._item.callback == null) { throw new Error('callback is required for loading JSONP requests.'); } // TODO: Look into creating our own iFrame to handle the load // In the first attempt, FF did not get the result // result instanceof Object did not work either // so we would need to clone the result. if (window[this._item.callback] != null) { throw new Error( "JSONP callback '" + this._item.callback + "' already exists on window. You need to specify a different callback or re-name the current one."); } window[this._item.callback] = createjs.proxy(this._handleLoad, this); createjs.DomUtils.appendToBody(this._tag); this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); // Load the tag this._tag.src = this._item.src; }; // private methods /** * Handle the JSONP callback, which is a public method defined on `window`. * @method _handleLoad * @param {Object} data The formatted JSON data. * @private */ p._handleLoad = function (data) { this._result = this._rawResult = data; this._sendComplete(); this._dispose(); }; /** * The tag request has not loaded within the time specfied in loadTimeout. * @method _handleError * @param {Object} event The XHR error event. * @private */ p._handleTimeout = function () { this._dispose(); this.dispatchEvent(new createjs.ErrorEvent("timeout")); }; /** * Clean up the JSONP load. This clears out the callback and script tag that this loader creates. * @method _dispose * @private */ p._dispose = function () { createjs.DomUtils.removeChild(this._tag); delete window[this._item.callback]; clearTimeout(this._loadTimeout); }; createjs.JSONPLoader = createjs.promote(JSONPLoader, "AbstractLoader"); }()); //############################################################################## // ManifestLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for JSON manifests. Items inside the manifest are loaded before the loader completes. To load manifests * using JSONP, specify a {{#crossLink "LoadItem/callback:property"}}{{/crossLink}} as part of the * {{#crossLink "LoadItem"}}{{/crossLink}}. * * The list of files in the manifest must be defined on the top-level JSON object in a `manifest` property. This * example shows a sample manifest definition, as well as how to to include a sub-manifest. * * { * "path": "assets/", * "manifest": [ * "image.png", * {"src": "image2.png", "id":"image2"}, * {"src": "sub-manifest.json", "type":"manifest", "callback":"jsonCallback"} * ] * } * * When a ManifestLoader has completed loading, the parent loader (usually a {{#crossLink "LoadQueue"}}{{/crossLink}}, * but could also be another ManifestLoader) will inherit all the loaded items, so you can access them directly. * * Note that the {{#crossLink "JSONLoader"}}{{/crossLink}} and {{#crossLink "JSONPLoader"}}{{/crossLink}} are * higher priority loaders, so manifests must set the {{#crossLink "LoadItem"}}{{/crossLink}} * {{#crossLink "LoadItem/type:property"}}{{/crossLink}} property to {{#crossLink "Types/MANIFEST:property"}}{{/crossLink}}. * * Additionally, some browsers require the server to serve a JavaScript mime-type for JSONP, so it may not work in * some conditions. * @class ManifestLoader * @param {LoadItem|Object} loadItem * @extends AbstractLoader * @constructor */ function ManifestLoader(loadItem, preferXHR) { this.AbstractLoader_constructor(loadItem, preferXHR, createjs.Types.MANIFEST); // Public Properties /** * An array of the plugins registered using {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}}, * used to pass plugins to new LoadQueues that may be created. * @property _plugins * @type {Array} * @private * @since 0.6.1 */ this.plugins = null; // Protected Properties /** * An internal {{#crossLink "LoadQueue"}}{{/crossLink}} that loads the contents of the manifest. * @property _manifestQueue * @type {LoadQueue} * @private */ this._manifestQueue = null; }; var p = createjs.extend(ManifestLoader, createjs.AbstractLoader); var s = ManifestLoader; // static properties /** * The amount of progress that the manifest itself takes up. * @property MANIFEST_PROGRESS * @type {number} * @default 0.25 (25%) * @private * @static */ s.MANIFEST_PROGRESS = 0.25; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/MANIFEST:property"}}{{/crossLink}} * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.MANIFEST; }; // public methods p.load = function () { this.AbstractLoader_load(); }; // protected methods p._createRequest = function() { var callback = this._item.callback; if (callback != null) { this._request = new createjs.JSONPLoader(this._item); } else { this._request = new createjs.JSONLoader(this._item); } }; p.handleEvent = function (event) { switch (event.type) { case "complete": this._rawResult = event.target.getResult(true); this._result = event.target.getResult(); this._sendProgress(s.MANIFEST_PROGRESS); this._loadManifest(this._result); return; case "progress": event.loaded *= s.MANIFEST_PROGRESS; this.progress = event.loaded / event.total; if (isNaN(this.progress) || this.progress == Infinity) { this.progress = 0; } this._sendProgress(event); return; } this.AbstractLoader_handleEvent(event); }; p.destroy = function() { this.AbstractLoader_destroy(); this._manifestQueue.close(); }; /** * Create and load the manifest items once the actual manifest has been loaded. * @method _loadManifest * @param {Object} json * @private */ p._loadManifest = function (json) { if (json && json.manifest) { var queue = this._manifestQueue = new createjs.LoadQueue(this._preferXHR); queue.on("fileload", this._handleManifestFileLoad, this); queue.on("progress", this._handleManifestProgress, this); queue.on("complete", this._handleManifestComplete, this, true); queue.on("error", this._handleManifestError, this, true); for(var i = 0, l = this.plugins.length; i < l; i++) { // conserve order of plugins queue.installPlugin(this.plugins[i]); } queue.loadManifest(json); } else { this._sendComplete(); } }; /** * An item from the {{#crossLink "_manifestQueue:property"}}{{/crossLink}} has completed. * @method _handleManifestFileLoad * @param {Event} event * @private */ p._handleManifestFileLoad = function (event) { event.target = null; this.dispatchEvent(event); }; /** * The manifest has completed loading. This triggers the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} * {{#crossLink "Event"}}{{/crossLink}} from the ManifestLoader. * @method _handleManifestComplete * @param {Event} event * @private */ p._handleManifestComplete = function (event) { this._loadedItems = this._manifestQueue.getItems(true); this._sendComplete(); }; /** * The manifest has reported progress. * @method _handleManifestProgress * @param {ProgressEvent} event * @private */ p._handleManifestProgress = function (event) { this.progress = event.progress * (1 - s.MANIFEST_PROGRESS) + s.MANIFEST_PROGRESS; this._sendProgress(this.progress); }; /** * The manifest has reported an error with one of the files. * @method _handleManifestError * @param {ErrorEvent} event * @private */ p._handleManifestError = function (event) { var newEvent = new createjs.Event("fileerror"); newEvent.item = event.data; this.dispatchEvent(newEvent); }; createjs.ManifestLoader = createjs.promote(ManifestLoader, "AbstractLoader"); }()); //############################################################################## // SoundLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for HTML audio files. PreloadJS can not load WebAudio files, as a WebAudio context is required, which * should be created by either a library playing the sound (such as SoundJS, or an * external framework that handles audio playback. To load content that can be played by WebAudio, use the * {{#crossLink "BinaryLoader"}}{{/crossLink}}, and handle the audio context decoding manually. * @class SoundLoader * @param {LoadItem|Object} loadItem * @param {Boolean} preferXHR * @extends AbstractMediaLoader * @constructor */ function SoundLoader(loadItem, preferXHR) { this.AbstractMediaLoader_constructor(loadItem, preferXHR, createjs.Types.SOUND); // protected properties if (createjs.DomUtils.isAudioTag(loadItem)) { this._tag = loadItem; } else if (createjs.DomUtils.isAudioTag(loadItem.src)) { this._tag = loadItem; } else if (createjs.DomUtils.isAudioTag(loadItem.tag)) { this._tag = createjs.DomUtils.isAudioTag(loadItem) ? loadItem : loadItem.src; } if (this._tag != null) { this._preferXHR = false; } }; var p = createjs.extend(SoundLoader, createjs.AbstractMediaLoader); var s = SoundLoader; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/SOUND:property"}}{{/crossLink}}. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.SOUND; }; // protected methods p._createTag = function (src) { var tag = createjs.Elements.audio(); tag.autoplay = false; tag.preload = "none"; //LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works. tag.src = src; return tag; }; createjs.SoundLoader = createjs.promote(SoundLoader, "AbstractMediaLoader"); }()); //############################################################################## // VideoLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for video files. * @class VideoLoader * @param {LoadItem|Object} loadItem * @param {Boolean} preferXHR * @extends AbstractMediaLoader * @constructor */ function VideoLoader(loadItem, preferXHR) { this.AbstractMediaLoader_constructor(loadItem, preferXHR, createjs.Types.VIDEO); if (createjs.DomUtils.isVideoTag(loadItem) || createjs.DomUtils.isVideoTag(loadItem.src)) { this.setTag(createjs.DomUtils.isVideoTag(loadItem)?loadItem:loadItem.src); // We can't use XHR for a tag that's passed in. this._preferXHR = false; } else { this.setTag(this._createTag()); } }; var p = createjs.extend(VideoLoader, createjs.AbstractMediaLoader); var s = VideoLoader; /** * Create a new video tag * * @returns {HTMLElement} * @private */ p._createTag = function () { return createjs.Elements.video(); }; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/VIDEO:property"}}{{/crossLink}}. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.VIDEO; }; createjs.VideoLoader = createjs.promote(VideoLoader, "AbstractMediaLoader"); }()); //############################################################################## // SpriteSheetLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for EaselJS SpriteSheets. Images inside the spritesheet definition are loaded before the loader * completes. To load SpriteSheets using JSONP, specify a {{#crossLink "LoadItem/callback:property"}}{{/crossLink}} * as part of the {{#crossLink "LoadItem"}}{{/crossLink}}. Note that the {{#crossLink "JSONLoader"}}{{/crossLink}} * and {{#crossLink "JSONPLoader"}}{{/crossLink}} are higher priority loaders, so SpriteSheets must * set the {{#crossLink "LoadItem"}}{{/crossLink}} {{#crossLink "LoadItem/type:property"}}{{/crossLink}} property * to {{#crossLink "Types/SPRITESHEET:property"}}{{/crossLink}}. * * The {{#crossLink "LoadItem"}}{{/crossLink}} {{#crossLink "LoadItem/crossOrigin:property"}}{{/crossLink}} as well * as the {{#crossLink "LoadQueue's"}}{{/crossLink}} `basePath` argument and {{#crossLink "LoadQueue/_preferXHR"}}{{/crossLink}} * property supplied to the {{#crossLink "LoadQueue"}}{{/crossLink}} are passed on to the sub-manifest that loads * the SpriteSheet images. * * Note that the SpriteSheet JSON does not respect the {{#crossLink "LoadQueue/_preferXHR:property"}}{{/crossLink}} * property, which should instead be determined by the presence of a {{#crossLink "LoadItem/callback:property"}}{{/crossLink}} * property on the SpriteSheet load item. This is because the JSON loaded will have a different format depending on * if it is loaded as JSON, so just changing `preferXHR` is not enough to change how it is loaded. * @class SpriteSheetLoader * @param {LoadItem|Object} loadItem * @extends AbstractLoader * @constructor */ function SpriteSheetLoader(loadItem, preferXHR) { this.AbstractLoader_constructor(loadItem, preferXHR, createjs.Types.SPRITESHEET); // protected properties /** * An internal queue which loads the SpriteSheet's images. * @method _manifestQueue * @type {LoadQueue} * @private */ this._manifestQueue = null; } var p = createjs.extend(SpriteSheetLoader, createjs.AbstractLoader); var s = SpriteSheetLoader; // static properties /** * The amount of progress that the manifest itself takes up. * @property SPRITESHEET_PROGRESS * @type {number} * @default 0.25 (25%) * @private * @static */ s.SPRITESHEET_PROGRESS = 0.25; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/SPRITESHEET:property"}}{{/crossLink}} * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.SPRITESHEET; }; // public methods p.destroy = function() { this.AbstractLoader_destroy(); this._manifestQueue.close(); }; // protected methods p._createRequest = function() { var callback = this._item.callback; if (callback != null) { this._request = new createjs.JSONPLoader(this._item); } else { this._request = new createjs.JSONLoader(this._item); } }; p.handleEvent = function (event) { switch (event.type) { case "complete": this._rawResult = event.target.getResult(true); this._result = event.target.getResult(); this._sendProgress(s.SPRITESHEET_PROGRESS); this._loadManifest(this._result); return; case "progress": event.loaded *= s.SPRITESHEET_PROGRESS; this.progress = event.loaded / event.total; if (isNaN(this.progress) || this.progress == Infinity) { this.progress = 0; } this._sendProgress(event); return; } this.AbstractLoader_handleEvent(event); }; /** * Create and load the images once the SpriteSheet JSON has been loaded. * @method _loadManifest * @param {Object} json * @private */ p._loadManifest = function (json) { if (json && json.images) { var queue = this._manifestQueue = new createjs.LoadQueue(this._preferXHR, this._item.path, this._item.crossOrigin); queue.on("complete", this._handleManifestComplete, this, true); queue.on("fileload", this._handleManifestFileLoad, this); queue.on("progress", this._handleManifestProgress, this); queue.on("error", this._handleManifestError, this, true); queue.loadManifest(json.images); } }; /** * An item from the {{#crossLink "_manifestQueue:property"}}{{/crossLink}} has completed. * @method _handleManifestFileLoad * @param {Event} event * @private */ p._handleManifestFileLoad = function (event) { var image = event.result; if (image != null) { var images = this.getResult().images; var pos = images.indexOf(event.item.src); images[pos] = image; } }; /** * The images have completed loading. This triggers the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} * {{#crossLink "Event"}}{{/crossLink}} from the SpriteSheetLoader. * @method _handleManifestComplete * @param {Event} event * @private */ p._handleManifestComplete = function (event) { this._result = new createjs.SpriteSheet(this._result); this._loadedItems = this._manifestQueue.getItems(true); this._sendComplete(); }; /** * The images {{#crossLink "LoadQueue"}}{{/crossLink}} has reported progress. * @method _handleManifestProgress * @param {ProgressEvent} event * @private */ p._handleManifestProgress = function (event) { this.progress = event.progress * (1 - s.SPRITESHEET_PROGRESS) + s.SPRITESHEET_PROGRESS; this._sendProgress(this.progress); }; /** * An image has reported an error. * @method _handleManifestError * @param {ErrorEvent} event * @private */ p._handleManifestError = function (event) { var newEvent = new createjs.Event("fileerror"); newEvent.item = event.data; this.dispatchEvent(newEvent); }; createjs.SpriteSheetLoader = createjs.promote(SpriteSheetLoader, "AbstractLoader"); }()); //############################################################################## // SVGLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for SVG files. * @class SVGLoader * @param {LoadItem|Object} loadItem * @param {Boolean} preferXHR * @extends AbstractLoader * @constructor */ function SVGLoader(loadItem, preferXHR) { this.AbstractLoader_constructor(loadItem, preferXHR, createjs.Types.SVG); // public properties this.resultFormatter = this._formatResult; // protected properties this._tagSrcAttribute = "data"; if (preferXHR) { this.setTag(createjs.Elements.svg()); } else { this.setTag(createjs.Elements.object()); this.getTag().type = "image/svg+xml"; } }; var p = createjs.extend(SVGLoader, createjs.AbstractLoader); var s = SVGLoader; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/SVG:property"}}{{/crossLink}} * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.SVG; }; // protected methods /** * The result formatter for SVG files. * @method _formatResult * @param {AbstractLoader} loader * @returns {Object} * @private */ p._formatResult = function (loader) { // mime should be image/svg+xml, but Opera requires text/xml var xml = createjs.DataUtils.parseXML(loader.getResult(true)); var tag = loader.getTag(); if (!this._preferXHR && document.body.contains(tag)) { document.body.removeChild(tag); } if (xml.documentElement != null) { var element = xml.documentElement; // Support loading an SVG from a different domain in ID if (document.importNode) { element = document.importNode(element, true); } tag.appendChild(element); return tag; } else { // For browsers that don't support SVG, just give them the XML. (IE 9-8) return xml; } }; createjs.SVGLoader = createjs.promote(SVGLoader, "AbstractLoader"); }()); //############################################################################## // XMLLoader.js //############################################################################## (function () { "use strict"; // constructor /** * A loader for CSS files. * @class XMLLoader * @param {LoadItem|Object} loadItem * @extends AbstractLoader * @constructor */ function XMLLoader(loadItem) { this.AbstractLoader_constructor(loadItem, true, createjs.Types.XML); // public properties this.resultFormatter = this._formatResult; }; var p = createjs.extend(XMLLoader, createjs.AbstractLoader); var s = XMLLoader; // static methods /** * Determines if the loader can load a specific item. This loader can only load items that are of type * {{#crossLink "Types/XML:property"}}{{/crossLink}}. * @method canLoadItem * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. * @returns {Boolean} Whether the loader can load the item. * @static */ s.canLoadItem = function (item) { return item.type == createjs.Types.XML; }; // protected methods /** * The result formatter for XML files. * @method _formatResult * @param {AbstractLoader} loader * @returns {XMLDocument} * @private */ p._formatResult = function (loader) { return createjs.DataUtils.parseXML(loader.getResult(true)); }; createjs.XMLLoader = createjs.promote(XMLLoader, "AbstractLoader"); }()); //############################################################################## // version.js //############################################################################## (function () { /** * Static class holding library specific information such as the version and buildDate of the library. * The SoundJS class has been renamed {{#crossLink "Sound"}}{{/crossLink}}. Please see {{#crossLink "Sound"}}{{/crossLink}} * for information on using sound. * @class SoundJS **/ var s = createjs.SoundJS = createjs.SoundJS || {}; /** * The version string for this release. * @property version * @type String * @static **/ s.version = /*=version*/"1.0.0"; // injected by build process /** * The build date for this release in UTC format. * @property buildDate * @type String * @static **/ s.buildDate = /*=date*/"Thu, 12 Oct 2017 16:34:05 GMT"; // injected by build process })(); //############################################################################## // BrowserDetect.js //############################################################################## /** * @class Utility Methods */ (function() { "use strict"; /** * An object that determines the current browser, version, operating system, and other environment * variables via user agent string. * * Used for audio because feature detection is unable to detect the many limitations of mobile devices. * *

    Example

    * * if (createjs.BrowserDetect.isIOS) { // do stuff } * * @property BrowserDetect * @type {Object} * @param {Boolean} isFirefox True if our browser is Firefox. * @param {Boolean} isOpera True if our browser is opera. * @param {Boolean} isChrome True if our browser is Chrome. Note that Chrome for Android returns true, but is a * completely different browser with different abilities. * @param {Boolean} isIOS True if our browser is safari for iOS devices (iPad, iPhone, and iPod). * @param {Boolean} isAndroid True if our browser is Android. * @param {Boolean} isBlackberry True if our browser is Blackberry. * @constructor * @static */ function BrowserDetect() { throw "BrowserDetect cannot be instantiated"; }; var agent = BrowserDetect.agent = window.navigator.userAgent; BrowserDetect.isWindowPhone = (agent.indexOf("IEMobile") > -1) || (agent.indexOf("Windows Phone") > -1); BrowserDetect.isFirefox = (agent.indexOf("Firefox") > -1); BrowserDetect.isOpera = (window.opera != null); BrowserDetect.isChrome = (agent.indexOf("Chrome") > -1); // NOTE that Chrome on Android returns true but is a completely different browser with different abilities BrowserDetect.isIOS = (agent.indexOf("iPod") > -1 || agent.indexOf("iPhone") > -1 || agent.indexOf("iPad") > -1) && !BrowserDetect.isWindowPhone; BrowserDetect.isAndroid = (agent.indexOf("Android") > -1) && !BrowserDetect.isWindowPhone; BrowserDetect.isBlackberry = (agent.indexOf("Blackberry") > -1); createjs.BrowserDetect = BrowserDetect; }()); //############################################################################## // AudioSprite.js //############################################################################## // NOTE this is "Class" is purely to document audioSprite Setup and usage. /** * Note: AudioSprite is not a class, but its usage is easily lost in the documentation, so it has been called * out here for quick reference. * * Audio sprites are much like CSS sprites or image sprite sheets: multiple audio assets grouped into a single file. * Audio sprites work around limitations in certain browsers, where only a single sound can be loaded and played at a * time. We recommend at least 300ms of silence between audio clips to deal with HTML audio tag inaccuracy, and to prevent * accidentally playing bits of the neighbouring clips. * * Benefits of Audio Sprites: *
      *
    • More robust support for older browsers and devices that only allow a single audio instance, such as iOS 5.
    • *
    • They provide a work around for the Internet Explorer 9 audio tag limit, which restricts how many different * sounds that could be loaded at once.
    • *
    • Faster loading by only requiring a single network request for several sounds, especially on mobile devices * where the network round trip for each file can add significant latency.
    • *
    * * Drawbacks of Audio Sprites *
      *
    • No guarantee of smooth looping when using HTML or Flash audio. If you have a track that needs to loop * smoothly and you are supporting non-web audio browsers, do not use audio sprites for that sound if you can avoid * it.
    • *
    • No guarantee that HTML audio will play back immediately, especially the first time. In some browsers * (Chrome!), HTML audio will only load enough to play through at the current download speed – so we rely on the * `canplaythrough` event to determine if the audio is loaded. Since audio sprites must jump ahead to play specific * sounds, the audio may not yet have downloaded fully.
    • *
    • Audio sprites share the same core source, so if you have a sprite with 5 sounds and are limited to 2 * concurrently playing instances, you can only play 2 of the sounds at the same time.
    • *
    * *

    Example

    * * createjs.Sound.initializeDefaultPlugins(); * var assetsPath = "./assets/"; * var sounds = [{ * src:"MyAudioSprite.ogg", data: { * audioSprite: [ * {id:"sound1", startTime:0, duration:500}, * {id:"sound2", startTime:1000, duration:400}, * {id:"sound3", startTime:1700, duration: 1000} * ]} * } * ]; * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.on("fileload", loadSound); * createjs.Sound.registerSounds(sounds, assetsPath); * // after load is complete * createjs.Sound.play("sound2"); * * You can also create audio sprites on the fly by setting the startTime and duration when creating an new AbstractSoundInstance. * * createjs.Sound.play("MyAudioSprite", {startTime: 1000, duration: 400}); * * The excellent CreateJS community has created a tool to create audio sprites, available at * https://github.com/tonistiigi/audiosprite, * as well as a jsfiddle to convert the output * to SoundJS format. * * @class AudioSprite * @since 0.6.0 */ //############################################################################## // PlayPropsConfig.js //############################################################################## (function () { "use strict"; /** * A class to store the optional play properties passed in {{#crossLink "Sound/play"}}{{/crossLink}} and * {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} calls. * * Optional Play Properties Include: *
      *
    • interrupt - How to interrupt any currently playing instances of audio with the same source, * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.
    • *
    • delay - The amount of time to delay the start of audio playback, in milliseconds.
    • *
    • offset - The offset from the start of the audio to begin playback, in milliseconds.
    • *
    • loop - How many times the audio loops when it reaches the end of playback. The default is 0 (no * loops), and -1 can be used for infinite playback.
    • *
    • volume - The volume of the sound, between 0 and 1. Note that the master volume is applied * against the individual volume.
    • *
    • pan - The left-right pan of the sound (if supported), between -1 (left) and 1 (right).
    • *
    • startTime - To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
    • *
    • duration - To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
    • *
    * *

    Example

    * * var props = new createjs.PlayPropsConfig().set({interrupt: createjs.Sound.INTERRUPT_ANY, loop: -1, volume: 0.5}) * createjs.Sound.play("mySound", props); * // OR * mySoundInstance.play(props); * * @class PlayPropsConfig * @constructor * @since 0.6.1 */ // TODO think of a better name for this class var PlayPropsConfig = function () { // Public Properties /** * How to interrupt any currently playing instances of audio with the same source, * if the maximum number of instances of the sound are already playing. Values are defined as * INTERRUPT_TYPE constants on the Sound class, with the default defined by * {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. * @property interrupt * @type {string} * @default null */ this.interrupt = null; /** * The amount of time to delay the start of audio playback, in milliseconds. * @property delay * @type {Number} * @default null */ this.delay = null; /** * The offset from the start of the audio to begin playback, in milliseconds. * @property offset * @type {number} * @default null */ this.offset = null; /** * How many times the audio loops when it reaches the end of playback. The default is 0 (no * loops), and -1 can be used for infinite playback. * @property loop * @type {number} * @default null */ this.loop = null; /** * The volume of the sound, between 0 and 1. Note that the master volume is applied * against the individual volume. * @property volume * @type {number} * @default null */ this.volume = null; /** * The left-right pan of the sound (if supported), between -1 (left) and 1 (right). * @property pan * @type {number} * @default null */ this.pan = null; /** * Used to create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. * @property startTime * @type {number} * @default null */ this.startTime = null; /** * Used to create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. * @property duration * @type {number} * @default null */ this.duration = null; }; var p = PlayPropsConfig.prototype = {}; var s = PlayPropsConfig; // Static Methods /** * Creates a PlayPropsConfig from another PlayPropsConfig or an Object. * * @method create * @param {PlayPropsConfig|Object} value The play properties * @returns {PlayPropsConfig} * @static */ s.create = function (value) { if (typeof(value) === "string") { // Handle the old API gracefully. console && (console.warn || console.log)("Deprecated behaviour. Sound.play takes a configuration object instead of individual arguments. See docs for info."); return new createjs.PlayPropsConfig().set({interrupt:value}); } else if (value == null || value instanceof s || value instanceof Object) { return new createjs.PlayPropsConfig().set(value); } else if (value == null) { throw new Error("PlayProps configuration not recognized."); } }; // Public Methods /** * Provides a chainable shortcut method for setting a number of properties on the instance. * *

    Example

    * * var PlayPropsConfig = new createjs.PlayPropsConfig().set({loop:-1, volume:0.7}); * * @method set * @param {Object} props A generic object containing properties to copy to the PlayPropsConfig instance. * @return {PlayPropsConfig} Returns the instance the method is called on (useful for chaining calls.) */ p.set = function(props) { if (props != null) { for (var n in props) { this[n] = props[n]; } } return this; }; p.toString = function() { return "[PlayPropsConfig]"; }; createjs.PlayPropsConfig = s; }()); //############################################################################## // Sound.js //############################################################################## (function () { "use strict"; /** * The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins. * All Sound APIs on this class are static. * * Registering and Preloading
    * Before you can play a sound, it must be registered. You can do this with {{#crossLink "Sound/registerSound"}}{{/crossLink}}, * or register multiple sounds using {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. If you don't register a * sound prior to attempting to play it using {{#crossLink "Sound/play"}}{{/crossLink}} or create it using {{#crossLink "Sound/createInstance"}}{{/crossLink}}, * the sound source will be automatically registered but playback will fail as the source will not be ready. If you use * PreloadJS, registration is handled for you when the sound is * preloaded. It is recommended to preload sounds either internally using the register functions or externally using * PreloadJS so they are ready when you want to use them. * * Playback
    * To play a sound once it's been registered and preloaded, use the {{#crossLink "Sound/play"}}{{/crossLink}} method. * This method returns a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} which can be paused, resumed, muted, etc. * Please see the {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} documentation for more on the instance control APIs. * * Plugins
    * By default, the {{#crossLink "WebAudioPlugin"}}{{/crossLink}} or the {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}} * are used (when available), although developers can change plugin priority or add new plugins (such as the * provided {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}). Please see the {{#crossLink "Sound"}}{{/crossLink}} API * methods for more on the playback and plugin APIs. To install plugins, or specify a different plugin order, see * {{#crossLink "Sound/installPlugins"}}{{/crossLink}}. * *

    Example

    * * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio"; * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashAudioPlugin]); * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.on("fileload", this.loadHandler, this); * createjs.Sound.registerSound("path/to/mySound.ogg", "sound"); * function loadHandler(event) { * // This is fired for each sound that is registered. * var instance = createjs.Sound.play("sound"); // play using id. Could also use full source path or event.src. * instance.on("complete", this.handleComplete, this); * instance.volume = 0.5; * } * * The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument * of {{#crossLink "Sound/registerSound"}}{{/crossLink}}. Note that if not specified, the active plugin will apply * a default limit. Currently HTMLAudioPlugin sets a default limit of 2, while WebAudioPlugin and FlashAudioPlugin set a * default limit of 100. * * createjs.Sound.registerSound("sound.mp3", "soundId", 4); * * Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is * automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal * load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use * the {{#crossLink "Sound/fileload:event"}}{{/crossLink}} event to determine when a sound has finished internally * preloading. It is recommended that all audio is preloaded before it is played. * * var queue = new createjs.LoadQueue(); * queue.installPlugin(createjs.Sound); * * Audio Sprites
    * SoundJS has added support for {{#crossLink "AudioSprite"}}{{/crossLink}}, available as of version 0.6.0. * For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets * grouped into a single file. * *

    Example

    * * var assetsPath = "./assets/"; * var sounds = [{ * src:"MyAudioSprite.ogg", data: { * audioSprite: [ * {id:"sound1", startTime:0, duration:500}, * {id:"sound2", startTime:1000, duration:400}, * {id:"sound3", startTime:1700, duration: 1000} * ]} * } * ]; * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.on("fileload", loadSound); * createjs.Sound.registerSounds(sounds, assetsPath); * // after load is complete * createjs.Sound.play("sound2"); * * Mobile Playback
    * Devices running iOS require the WebAudio context to be "unlocked" by playing at least one sound inside of a user- * initiated event (such as touch/click). Earlier versions of SoundJS included a "MobileSafe" sample, but this is no * longer necessary as of SoundJS 0.6.2. *
      *
    • * In SoundJS 0.4.1 and above, you can either initialize plugins or use the {{#crossLink "WebAudioPlugin/playEmptySound"}}{{/crossLink}} * method in the call stack of a user input event to manually unlock the audio context. *
    • *
    • * In SoundJS 0.6.2 and above, SoundJS will automatically listen for the first document-level "mousedown" * and "touchend" event, and unlock WebAudio. This will continue to check these events until the WebAudio * context becomes "unlocked" (changes from "suspended" to "running") *
    • *
    • * Both the "mousedown" and "touchend" events can be used to unlock audio in iOS9+, the "touchstart" event * will work in iOS8 and below. The "touchend" event will only work in iOS9 when the gesture is interpreted * as a "click", so if the user long-presses the button, it will no longer work. *
    • *
    • * When using the EaselJS Touch class, * the "mousedown" event will not fire when a canvas is clicked, since MouseEvents are prevented, to ensure * only touch events fire. To get around this, you can either rely on "touchend", or: *
        *
      1. Set the `allowDefault` property on the Touch class constructor to `true` (defaults to `false`).
      2. *
      3. Set the `preventSelection` property on the EaselJS `Stage` to `false`.
      4. *
      * These settings may change how your application behaves, and are not recommended. *
    • *
    * * Loading Alternate Paths and Extension-less Files
    * SoundJS supports loading alternate paths and extension-less files by passing an object instead of a string for * the `src` property, which is a hash using the format `{extension:"path", extension2:"path2"}`. These labels are * how SoundJS determines if the browser will support the sound. This also enables multiple formats to live in * different folders, or on CDNs, which often has completely different filenames for each file. * * Priority is determined by the property order (first property is tried first). This is supported by both internal loading * and loading with PreloadJS. * * Note: an id is required for playback. * *

    Example

    * * var sounds = {path:"./audioPath/", * manifest: [ * {id: "cool", src: {mp3:"mp3/awesome.mp3", ogg:"noExtensionOggFile"}} * ]}; * * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.addEventListener("fileload", handleLoad); * createjs.Sound.registerSounds(sounds); * *

    Known Browser and OS issues

    * IE 9 HTML Audio limitations
    *
    • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of * when or how you apply the volume change, as the tag seems to need to play to apply it.
    • *
    • MP3 encoding will not always work for audio tags, particularly in Internet Explorer. We've found default * encoding with 64kbps works.
    • *
    • Occasionally very short samples will get cut off.
    • *
    • There is a limit to how many audio tags you can load and play at once, which appears to be determined by * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe * estimate.
    * * Firefox 25 Web Audio limitations *
    • mp3 audio files do not load properly on all windows machines, reported * here.
      * For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if * possible.
    * Safari limitations
    *
    • Safari requires Quicktime to be installed for audio playback.
    * * iOS 6 Web Audio limitations
    *
    • Sound is initially locked, and must be unlocked via a user-initiated event. Please see the section on * Mobile Playback above.
    • *
    • A bug exists that will distort un-cached web audio when a video element is present in the DOM that has audio * at a different sampleRate.
    • *
    * * Android HTML Audio limitations
    *
    • We have no control over audio volume. Only the user can set volume on their device.
    • *
    • We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use * a delay.
    * * Web Audio and PreloadJS
    *
    • Web Audio must be loaded through XHR, therefore when used with PreloadJS, tag loading is not possible. * This means that tag loading can not be used to avoid cross domain issues.
      • * * @class Sound * @static * @uses EventDispatcher */ function Sound() { throw "Sound cannot be instantiated"; } var s = Sound; // Static Properties /** * The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of * instances of the sound are already playing. * @property INTERRUPT_ANY * @type {String} * @default any * @static */ s.INTERRUPT_ANY = "any"; /** * The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the * least distance in the audio track, if the maximum number of instances of the sound are already playing. * @property INTERRUPT_EARLY * @type {String} * @default early * @static */ s.INTERRUPT_EARLY = "early"; /** * The interrupt value to interrupt the currently playing instance with the same source that progressed the most * distance in the audio track, if the maximum number of instances of the sound are already playing. * @property INTERRUPT_LATE * @type {String} * @default late * @static */ s.INTERRUPT_LATE = "late"; /** * The interrupt value to not interrupt any currently playing instances with the same source, if the maximum number of * instances of the sound are already playing. * @property INTERRUPT_NONE * @type {String} * @default none * @static */ s.INTERRUPT_NONE = "none"; /** * Defines the playState of an instance that is still initializing. * @property PLAY_INITED * @type {String} * @default playInited * @static */ s.PLAY_INITED = "playInited"; /** * Defines the playState of an instance that is currently playing or paused. * @property PLAY_SUCCEEDED * @type {String} * @default playSucceeded * @static */ s.PLAY_SUCCEEDED = "playSucceeded"; /** * Defines the playState of an instance that was interrupted by another instance. * @property PLAY_INTERRUPTED * @type {String} * @default playInterrupted * @static */ s.PLAY_INTERRUPTED = "playInterrupted"; /** * Defines the playState of an instance that completed playback. * @property PLAY_FINISHED * @type {String} * @default playFinished * @static */ s.PLAY_FINISHED = "playFinished"; /** * Defines the playState of an instance that failed to play. This is usually caused by a lack of available channels * when the interrupt mode was "INTERRUPT_NONE", the playback stalled, or the sound could not be found. * @property PLAY_FAILED * @type {String} * @default playFailed * @static */ s.PLAY_FAILED = "playFailed"; /** * A list of the default supported extensions that Sound will try to play. Plugins will check if the browser * can play these types, so modifying this list before a plugin is initialized will allow the plugins to try to * support additional media types. * * NOTE this does not currently work for {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. * * More details on file formats can be found at http://en.wikipedia.org/wiki/Audio_file_format.
        * A very detailed list of file formats can be found at http://www.fileinfo.com/filetypes/audio. * @property SUPPORTED_EXTENSIONS * @type {Array[String]} * @default ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"] * @since 0.4.0 * @static */ s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]; /** * Some extensions use another type of extension support to play (one of them is a codex). This allows you to map * that support so plugins can accurately determine if an extension is supported. Adding to this list can help * plugins determine more accurately if an extension is supported. * * A useful list of extensions for each format can be found at http://html5doctor.com/html5-audio-the-state-of-play/. * @property EXTENSION_MAP * @type {Object} * @since 0.4.0 * @default {m4a:"mp4"} * @static */ s.EXTENSION_MAP = { m4a:"mp4" }; /** * The RegExp pattern used to parse file URIs. This supports simple file names, as well as full domain URIs with * query strings. The resulting match is: protocol:$1 domain:$2 path:$3 file:$4 extension:$5 query:$6. * @property FILE_PATTERN * @type {RegExp} * @static * @private */ s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/; // Class Public properties /** * Determines the default behavior for interrupting other currently playing instances with the same source, if the * maximum number of instances of the sound are already playing. Currently the default is {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}} * but this can be set and will change playback behavior accordingly. This is only used when {{#crossLink "Sound/play"}}{{/crossLink}} * is called without passing a value for interrupt. * @property defaultInterruptBehavior * @type {String} * @default Sound.INTERRUPT_NONE, or "none" * @static * @since 0.4.0 */ s.defaultInterruptBehavior = s.INTERRUPT_NONE; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense. /** * An array of extensions to attempt to use when loading sound, if the default is unsupported by the active plugin. * These are applied in order, so if you try to Load Thunder.ogg in a browser that does not support ogg, and your * extensions array is ["mp3", "m4a", "wav"] it will check mp3 support, then m4a, then wav. The audio files need * to exist in the same location, as only the extension is altered. * * Note that regardless of which file is loaded, you can call {{#crossLink "Sound/createInstance"}}{{/crossLink}} * and {{#crossLink "Sound/play"}}{{/crossLink}} using the same id or full source path passed for loading. * *

        Example

        * * var sounds = [ * {src:"myPath/mySound.ogg", id:"example"}, * ]; * createjs.Sound.alternateExtensions = ["mp3"]; // now if ogg is not supported, SoundJS will try asset0.mp3 * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads * createjs.Sound.registerSounds(sounds, assetPath); * // ... * createjs.Sound.play("myPath/mySound.ogg"); // works regardless of what extension is supported. Note calling with ID is a better approach * * @property alternateExtensions * @type {Array} * @since 0.5.2 * @static */ s.alternateExtensions = []; /** * The currently active plugin. If this is null, then no plugin could be initialized. If no plugin was specified, * Sound attempts to apply the default plugins: {{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. * @property activePlugin * @type {Object} * @static */ s.activePlugin = null; // class getter / setter properties /** * Set the master volume of Sound. The master volume is multiplied against each sound's individual volume. For * example, if master volume is 0.5 and a sound's volume is 0.5, the resulting volume is 0.25. To set individual * sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} * instead. * *

        Example

        * * createjs.Sound.volume = 0.5; * * @property volume * @type {Number} * @default 1 * @since 0.6.1 */ /** * The internal volume level. Use {{#crossLink "Sound/volume:property"}}{{/crossLink}} to adjust the master volume. * @property _masterVolume * @type {number} * @default 1 * @private */ s._masterVolume = 1; /** * Use the {{#crossLink "Sound/volume:property"}}{{/crossLink}} property instead. * @method _getMasterVolume * @private * @static * @return {Number} **/ s._getMasterVolume = function() { return this._masterVolume; }; // Sound.getMasterVolume is @deprecated. Remove for 1.1+ s.getVolume = createjs.deprecate(s._getMasterVolume, "Sound.getVolume"); /** * Use the {{#crossLink "Sound/volume:property"}}{{/crossLink}} property instead. * @method _setMasterVolume * @static * @private **/ s._setMasterVolume = function(value) { if (Number(value) == null) { return; } value = Math.max(0, Math.min(1, value)); s._masterVolume = value; if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) { var instances = this._instances; for (var i = 0, l = instances.length; i < l; i++) { instances[i].setMasterVolume(value); } } }; // Sound.stMasterVolume is @deprecated. Remove for 1.1+ s.setVolume = createjs.deprecate(s._setMasterVolume, "Sound.setVolume"); /** * Mute/Unmute all audio. Note that muted audio still plays at 0 volume. This global mute value is maintained * separately and when set will override, but not change the mute property of individual instances. To mute an individual * instance, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead. * *

        Example

        * * createjs.Sound.muted = true; * * * @property muted * @type {Boolean} * @default false * @since 0.6.1 */ s._masterMute = false; /** * Use the {{#crossLink "Sound/muted:property"}}{{/crossLink}} property instead. * @method _getMute * @returns {Boolean} * @static * @private */ s._getMute = function () { return this._masterMute; }; // Sound.getMute is @deprecated. Remove for 1.1+ s.getMute = createjs.deprecate(s._getMute, "Sound.getMute"); /** * Use the {{#crossLink "Sound/muted:property"}}{{/crossLink}} property instead. * @method _setMute * @param {Boolean} value The muted value * @static * @private */ s._setMute = function (value) { if (value == null) { return; } this._masterMute = value; if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { var instances = this._instances; for (var i = 0, l = instances.length; i < l; i++) { instances[i].setMasterMute(value); } } }; // Sound.setMute is @deprecated. Remove for 1.1+ s.setMute = createjs.deprecate(s._setMute, "Sound.setMute"); /** * Get the active plugins capabilities, which help determine if a plugin can be used in the current environment, * or if the plugin supports a specific feature. Capabilities include: *
          *
        • panning: If the plugin can pan audio from left to right
        • *
        • volume; If the plugin can control audio volume.
        • *
        • tracks: The maximum number of audio tracks that can be played back at a time. This will be -1 * if there is no known limit.
        • *
          An entry for each file type in {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}: *
        • mp3: If MP3 audio is supported.
        • *
        • ogg: If OGG audio is supported.
        • *
        • wav: If WAV audio is supported.
        • *
        • mpeg: If MPEG audio is supported.
        • *
        • m4a: If M4A audio is supported.
        • *
        • mp4: If MP4 audio is supported.
        • *
        • aiff: If aiff audio is supported.
        • *
        • wma: If wma audio is supported.
        • *
        • mid: If mid audio is supported.
        • *
        * * You can get a specific capability of the active plugin using standard object notation * *

        Example

        * * var mp3 = createjs.Sound.capabilities.mp3; * * Note this property is read only. * * @property capabilities * @type {Object} * @static * @readOnly * @since 0.6.1 */ /** * Use the {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} property instead. * @returns {null} * @private */ s._getCapabilities = function() { if (s.activePlugin == null) { return null; } return s.activePlugin._capabilities; }; // Sound.getCapabilities is @deprecated. Remove for 1.1+ s.getCapabilities = createjs.deprecate(s._getCapabilities, "Sound.getCapabilities"); Object.defineProperties(s, { volume: { get: s._getMasterVolume, set: s._setMasterVolume }, muted: { get: s._getMute, set: s._setMute }, capabilities: { get: s._getCapabilities } }); // Class Private properties /** * Determines if the plugins have been registered. If false, the first call to {{#crossLink "play"}}{{/crossLink}} will instantiate the default * plugins ({{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}). * If plugins have been registered, but none are applicable, then sound playback will fail. * @property _pluginsRegistered * @type {Boolean} * @default false * @static * @private */ s._pluginsRegistered = false; /** * Used internally to assign unique IDs to each AbstractSoundInstance. * @property _lastID * @type {Number} * @static * @private */ s._lastID = 0; /** * An array containing all currently playing instances. This allows Sound to control the volume, mute, and playback of * all instances when using static APIs like {{#crossLink "Sound/stop"}}{{/crossLink}} and {{#crossLink "Sound/volume:property"}}{{/crossLink}}. * When an instance has finished playback, it gets removed via the {{#crossLink "Sound/finishedPlaying"}}{{/crossLink}} * method. If the user replays an instance, it gets added back in via the {{#crossLink "Sound/_beginPlaying"}}{{/crossLink}} * method. * @property _instances * @type {Array} * @private * @static */ s._instances = []; /** * An object hash storing objects with sound sources, startTime, and duration via there corresponding ID. * @property _idHash * @type {Object} * @private * @static */ s._idHash = {}; /** * An object hash that stores preloading sound sources via the parsed source that is passed to the plugin. Contains the * source, id, and data that was passed in by the user. Parsed sources can contain multiple instances of source, id, * and data. * @property _preloadHash * @type {Object} * @private * @static */ s._preloadHash = {}; /** * An object hash storing {{#crossLink "PlayPropsConfig"}}{{/crossLink}} via the parsed source that is passed as defaultPlayProps in * {{#crossLink "Sound/registerSound"}}{{/crossLink}} and {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. * @property _defaultPlayPropsHash * @type {Object} * @private * @static * @since 0.6.1 */ s._defaultPlayPropsHash = {}; // EventDispatcher methods: s.addEventListener = null; s.removeEventListener = null; s.removeAllEventListeners = null; s.dispatchEvent = null; s.hasEventListener = null; s._listeners = null; createjs.EventDispatcher.initialize(s); // inject EventDispatcher methods. // Events /** * This event is fired when a file finishes loading internally. This event is fired for each loaded sound, * so any handler methods should look up the event.src to handle a particular sound. * @event fileload * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {String} src The source of the sound that was loaded. * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null. * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined. * @since 0.4.1 */ /** * This event is fired when a file fails loading internally. This event is fired for each loaded sound, * so any handler methods should look up the event.src to handle a particular sound. * @event fileerror * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {String} src The source of the sound that was loaded. * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null. * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined. * @since 0.6.0 */ // Class Public Methods /** * Get the preload rules to allow Sound to be used as a plugin by PreloadJS. * Any load calls that have the matching type or extension will fire the callback method, and use the resulting * object, which is potentially modified by Sound. This helps when determining the correct path, as well as * registering the audio instance(s) with Sound. This method should not be called, except by PreloadJS. * @method getPreloadHandlers * @return {Object} An object containing: *
        • callback: A preload callback that is fired when a file is added to PreloadJS, which provides * Sound a mechanism to modify the load parameters, select the correct file format, register the sound, etc.
        • *
        • types: A list of file types that are supported by Sound (currently supports "sound").
        • *
        • extensions: A list of file extensions that are supported by Sound (see {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}).
        * @static * @private */ s.getPreloadHandlers = function () { return { callback:createjs.proxy(s.initLoad, s), types:["sound"], extensions:s.SUPPORTED_EXTENSIONS }; }; /** * Used to dispatch fileload events from internal loading. * @method _handleLoadComplete * @param event A loader event. * @private * @static * @since 0.6.0 */ s._handleLoadComplete = function(event) { var src = event.target.getItem().src; if (!s._preloadHash[src]) {return;} for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { var item = s._preloadHash[src][i]; s._preloadHash[src][i] = true; if (!s.hasEventListener("fileload")) { continue; } var event = new createjs.Event("fileload"); event.src = item.src; event.id = item.id; event.data = item.data; event.sprite = item.sprite; s.dispatchEvent(event); } }; /** * Used to dispatch error events from internal preloading. * @param event * @private * @since 0.6.0 * @static */ s._handleLoadError = function(event) { var src = event.target.getItem().src; if (!s._preloadHash[src]) {return;} for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { var item = s._preloadHash[src][i]; s._preloadHash[src][i] = false; if (!s.hasEventListener("fileerror")) { continue; } var event = new createjs.Event("fileerror"); event.src = item.src; event.id = item.id; event.data = item.data; event.sprite = item.sprite; s.dispatchEvent(event); } }; /** * Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin. * * @method _registerPlugin * @param {Object} plugin The plugin class to install. * @return {Boolean} Whether the plugin was successfully initialized. * @static * @private */ s._registerPlugin = function (plugin) { // Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance if (plugin.isSupported()) { s.activePlugin = new plugin(); return true; } return false; }; /** * Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array. * *

        Example

        * * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); * * @method registerPlugins * @param {Array} plugins An array of plugins classes to install. * @return {Boolean} Whether a plugin was successfully initialized. * @static */ s.registerPlugins = function (plugins) { s._pluginsRegistered = true; for (var i = 0, l = plugins.length; i < l; i++) { if (s._registerPlugin(plugins[i])) { return true; } } return false; }; /** * Initialize the default plugins. This method is automatically called when any audio is played or registered before * the user has manually registered plugins, and enables Sound to work without manual plugin setup. Currently, the * default plugins are {{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. * *

        Example

        * * if (!createjs.initializeDefaultPlugins()) { return; } * * @method initializeDefaultPlugins * @returns {Boolean} True if a plugin was initialized, false otherwise. * @since 0.4.0 * @static */ s.initializeDefaultPlugins = function () { if (s.activePlugin != null) {return true;} if (s._pluginsRegistered) {return false;} if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;} return false; }; /** * Determines if Sound has been initialized, and a plugin has been activated. * *

        Example

        * This example sets up a Flash fallback, but only if there is no plugin specified yet. * * if (!createjs.Sound.isReady()) { * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); * } * * @method isReady * @return {Boolean} If Sound has initialized a plugin. * @static */ s.isReady = function () { return (s.activePlugin != null); }; /** * Process manifest items from PreloadJS. This method is intended * for usage by a plugin, and not for direct interaction. * @method initLoad * @param {Object} src The object to load. * @return {Object|AbstractLoader} An instance of AbstractLoader. * @private * @static */ s.initLoad = function (loadItem) { if (loadItem.type == "video") { return true; } // Don't handle video. PreloadJS's plugin model is really aggressive. return s._registerSound(loadItem); }; /** * Internal method for loading sounds. This should not be called directly. * * @method _registerSound * @param {Object} src The object to load, containing src property and optionally containing id and data. * @return {Object} An object with the modified values that were passed in, which defines the sound. * Returns false if the source cannot be parsed or no plugins can be initialized. * Returns true if the source is already loaded. * @static * @private * @since 0.6.0 */ s._registerSound = function (loadItem) { if (!s.initializeDefaultPlugins()) {return false;} var details; if (loadItem.src instanceof Object) { details = s._parseSrc(loadItem.src); details.src = loadItem.path + details.src; } else { details = s._parsePath(loadItem.src); } if (details == null) {return false;} loadItem.src = details.src; loadItem.type = "sound"; var data = loadItem.data; var numChannels = null; if (data != null) { if (!isNaN(data.channels)) { numChannels = parseInt(data.channels); } else if (!isNaN(data)) { numChannels = parseInt(data); } if(data.audioSprite) { var sp; for(var i = data.audioSprite.length; i--; ) { sp = data.audioSprite[i]; s._idHash[sp.id] = {src: loadItem.src, startTime: parseInt(sp.startTime), duration: parseInt(sp.duration)}; if (sp.defaultPlayProps) { s._defaultPlayPropsHash[sp.id] = createjs.PlayPropsConfig.create(sp.defaultPlayProps); } } } } if (loadItem.id != null) {s._idHash[loadItem.id] = {src: loadItem.src}}; var loader = s.activePlugin.register(loadItem); SoundChannel.create(loadItem.src, numChannels); // return the number of instances to the user. This will also be returned in the load event. if (data == null || !isNaN(data)) { loadItem.data = numChannels || SoundChannel.maxPerChannel(); } else { loadItem.data.channels = numChannels || SoundChannel.maxPerChannel(); } if (loader.type) {loadItem.type = loader.type;} if (loadItem.defaultPlayProps) { s._defaultPlayPropsHash[loadItem.src] = createjs.PlayPropsConfig.create(loadItem.defaultPlayProps); } return loader; }; /** * Register an audio file for loading and future playback in Sound. This is automatically called when using * PreloadJS. It is recommended to register all sounds that * need to be played back in order to properly prepare and preload them. Sound does internal preloading when required. * *

        Example

        * * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.on("fileload", handleLoad); // add an event listener for when load is completed * createjs.Sound.registerSound("myAudioPath/mySound.ogg", "myID", 3); * createjs.Sound.registerSound({ogg:"path1/mySound.ogg", mp3:"path2/mySoundNoExtension"}, "myID", 3); * * * @method registerSound * @param {String | Object} src The source or an Object with a "src" property or an Object with multiple extension labeled src properties. * @param {String} [id] An id specified by the user to play the sound later. Note id is required for when src is multiple extension labeled src properties. * @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of * channels for an audio instance, however a "channels" property can be appended to the data object if it is used * for other information. The audio channels will set a default based on plugin if no value is found. * Sound also uses the data property to hold an {{#crossLink "AudioSprite"}}{{/crossLink}} array of objects in the following format {id, startTime, duration}.
        * id used to play the sound later, in the same manner as a sound src with an id.
        * startTime is the initial offset to start playback and loop from, in milliseconds.
        * duration is the amount of time to play the clip for, in milliseconds.
        * This allows Sound to support audio sprites that are played back by id. * @param {string} basePath Set a path that will be prepended to src for loading. * @param {Object | PlayPropsConfig} defaultPlayProps Optional Playback properties that will be set as the defaults on any new AbstractSoundInstance. * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for options. * @return {Object} An object with the modified values that were passed in, which defines the sound. * Returns false if the source cannot be parsed or no plugins can be initialized. * Returns true if the source is already loaded. * @static * @since 0.4.0 */ s.registerSound = function (src, id, data, basePath, defaultPlayProps) { var loadItem = {src: src, id: id, data:data, defaultPlayProps:defaultPlayProps}; if (src instanceof Object && src.src) { basePath = id; loadItem = src; } loadItem = createjs.LoadItem.create(loadItem); loadItem.path = basePath; if (basePath != null && !(loadItem.src instanceof Object)) {loadItem.src = basePath + loadItem.src;} var loader = s._registerSound(loadItem); if(!loader) {return false;} if (!s._preloadHash[loadItem.src]) { s._preloadHash[loadItem.src] = [];} s._preloadHash[loadItem.src].push(loadItem); if (s._preloadHash[loadItem.src].length == 1) { // OJR note this will disallow reloading a sound if loading fails or the source changes loader.on("complete", this._handleLoadComplete, this); loader.on("error", this._handleLoadError, this); s.activePlugin.preload(loader); } else { if (s._preloadHash[loadItem.src][0] == true) {return true;} } return loadItem; }; /** * Register an array of audio files for loading and future playback in Sound. It is recommended to register all * sounds that need to be played back in order to properly prepare and preload them. Sound does internal preloading * when required. * *

        Example

        * * var assetPath = "./myAudioPath/"; * var sounds = [ * {src:"asset0.ogg", id:"example"}, * {src:"asset1.ogg", id:"1", data:6}, * {src:"asset2.mp3", id:"works"} * {src:{mp3:"path1/asset3.mp3", ogg:"path2/asset3NoExtension"}, id:"better"} * ]; * createjs.Sound.alternateExtensions = ["mp3"]; // if the passed extension is not supported, try this extension * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads * createjs.Sound.registerSounds(sounds, assetPath); * * @method registerSounds * @param {Array} sounds An array of objects to load. Objects are expected to be in the format needed for * {{#crossLink "Sound/registerSound"}}{{/crossLink}}: {src:srcURI, id:ID, data:Data} * with "id" and "data" being optional. * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to load. * Note id is required if src is an object with extension labeled src properties. * @param {string} basePath Set a path that will be prepended to each src when loading. When creating, playing, or removing * audio that was loaded with a basePath by src, the basePath must be included. * @return {Object} An array of objects with the modified values that were passed in, which defines each sound. * Like registerSound, it will return false for any values when the source cannot be parsed or if no plugins can be initialized. * Also, it will return true for any values when the source is already loaded. * @static * @since 0.6.0 */ s.registerSounds = function (sounds, basePath) { var returnValues = []; if (sounds.path) { if (!basePath) { basePath = sounds.path; } else { basePath = basePath + sounds.path; } sounds = sounds.manifest; // TODO document this feature } for (var i = 0, l = sounds.length; i < l; i++) { returnValues[i] = createjs.Sound.registerSound(sounds[i].src, sounds[i].id, sounds[i].data, basePath, sounds[i].defaultPlayProps); } return returnValues; }; /** * Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. *
        Note this will stop playback on active instances playing this sound before deleting them. *
        Note if you passed in a basePath, you need to pass it or prepend it to the src here. * *

        Example

        * * createjs.Sound.removeSound("myID"); * createjs.Sound.removeSound("myAudioBasePath/mySound.ogg"); * createjs.Sound.removeSound("myPath/myOtherSound.mp3", "myBasePath/"); * createjs.Sound.removeSound({mp3:"musicNoExtension", ogg:"music.ogg"}, "myBasePath/"); * * @method removeSound * @param {String | Object} src The src or ID of the audio, or an Object with a "src" property, or an Object with multiple extension labeled src properties. * @param {string} basePath Set a path that will be prepended to each src when removing. * @return {Boolean} True if sound is successfully removed. * @static * @since 0.4.1 */ s.removeSound = function(src, basePath) { if (s.activePlugin == null) {return false;} if (src instanceof Object && src.src) {src = src.src;} var details; if (src instanceof Object) { details = s._parseSrc(src); } else { src = s._getSrcById(src).src; details = s._parsePath(src); } if (details == null) {return false;} src = details.src; if (basePath != null) {src = basePath + src;} for(var prop in s._idHash){ if(s._idHash[prop].src == src) { delete(s._idHash[prop]); } } // clear from SoundChannel, which also stops and deletes all instances SoundChannel.removeSrc(src); delete(s._preloadHash[src]); s.activePlugin.removeSound(src); return true; }; /** * Remove an array of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. *
        Note this will stop playback on active instances playing this audio before deleting them. *
        Note if you passed in a basePath, you need to pass it or prepend it to the src here. * *

        Example

        * * assetPath = "./myPath/"; * var sounds = [ * {src:"asset0.ogg", id:"example"}, * {src:"asset1.ogg", id:"1", data:6}, * {src:"asset2.mp3", id:"works"} * ]; * createjs.Sound.removeSounds(sounds, assetPath); * * @method removeSounds * @param {Array} sounds An array of objects to remove. Objects are expected to be in the format needed for * {{#crossLink "Sound/removeSound"}}{{/crossLink}}: {srcOrID:srcURIorID}. * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to remove. * @param {string} basePath Set a path that will be prepended to each src when removing. * @return {Object} An array of Boolean values representing if the sounds with the same array index were * successfully removed. * @static * @since 0.4.1 */ s.removeSounds = function (sounds, basePath) { var returnValues = []; if (sounds.path) { if (!basePath) { basePath = sounds.path; } else { basePath = basePath + sounds.path; } sounds = sounds.manifest; } for (var i = 0, l = sounds.length; i < l; i++) { returnValues[i] = createjs.Sound.removeSound(sounds[i].src, basePath); } return returnValues; }; /** * Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. *
        Note this will stop playback on all active sound instances before deleting them. * *

        Example

        * * createjs.Sound.removeAllSounds(); * * @method removeAllSounds * @static * @since 0.4.1 */ s.removeAllSounds = function() { s._idHash = {}; s._preloadHash = {}; SoundChannel.removeAll(); if (s.activePlugin) {s.activePlugin.removeAllSounds();} }; /** * Check if a source has been loaded by internal preloaders. This is necessary to ensure that sounds that are * not completed preloading will not kick off a new internal preload if they are played. * *

        Example

        * * var mySound = "assetPath/asset0.ogg"; * if(createjs.Sound.loadComplete(mySound) { * createjs.Sound.play(mySound); * } * * @method loadComplete * @param {String} src The src or id that is being loaded. * @return {Boolean} If the src is already loaded. * @since 0.4.0 * @static */ s.loadComplete = function (src) { if (!s.isReady()) { return false; } var details = s._parsePath(src); if (details) { src = s._getSrcById(details.src).src; } else { src = s._getSrcById(src).src; } if(s._preloadHash[src] == undefined) {return false;} return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all }; /** * Parse the path of a sound. Alternate extensions will be attempted in order if the * current extension is not supported * @method _parsePath * @param {String} value The path to an audio source. * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} * and returned to a preloader like PreloadJS. * @private * @static */ s._parsePath = function (value) { if (typeof(value) != "string") {value = value.toString();} var match = value.match(s.FILE_PATTERN); if (match == null) {return false;} var name = match[4]; var ext = match[5]; var c = s.capabilities; var i = 0; while (!c[ext]) { ext = s.alternateExtensions[i++]; if (i > s.alternateExtensions.length) { return null;} // no extensions are supported } value = value.replace("."+match[5], "."+ext); var ret = {name:name, src:value, extension:ext}; return ret; }; /** * Parse the path of a sound based on properties of src matching with supported extensions. * Returns false if none of the properties are supported * @method _parseSrc * @param {Object} value The paths to an audio source, indexed by extension type. * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} * and returned to a preloader like PreloadJS. * @private * @static */ s._parseSrc = function (value) { var ret = {name:undefined, src:undefined, extension:undefined}; var c = s.capabilities; for (var prop in value) { if(value.hasOwnProperty(prop) && c[prop]) { ret.src = value[prop]; ret.extension = prop; break; } } if (!ret.src) {return false;} // no matches var i = ret.src.lastIndexOf("/"); if (i != -1) { ret.name = ret.src.slice(i+1); } else { ret.name = ret.src; } return ret; }; /* --------------- Static API. --------------- */ /** * Play a sound and get a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to control. If the sound fails to * play, an AbstractSoundInstance will still be returned, and have a playState of {{#crossLink "Sound/PLAY_FAILED:property"}}{{/crossLink}}. * Note that even on sounds with failed playback, you may still be able to call the {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}, * method, since the failure could be due to lack of available channels. If the src does not have a supported * extension or if there is no available plugin, a default AbstractSoundInstance will still be returned, which will * not play any audio, but will not generate errors. * *

        Example

        * * createjs.Sound.on("fileload", handleLoad); * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); * function handleLoad(event) { * createjs.Sound.play("myID"); * // store off AbstractSoundInstance for controlling * var myInstance = createjs.Sound.play("myID", {interrupt: createjs.Sound.INTERRUPT_ANY, loop:-1}); * } * * NOTE: To create an audio sprite that has not already been registered, both startTime and duration need to be set. * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. * * @method play * @param {String} src The src or ID of the audio. * @param {Object | PlayPropsConfig} props A PlayPropsConfig instance, or an object that contains the parameters to * play a sound. See the {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for more info. * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled * after it is created. * @static */ s.play = function (src, props) { var playProps = createjs.PlayPropsConfig.create(props); var instance = s.createInstance(src, playProps.startTime, playProps.duration); var ok = s._playInstance(instance, playProps); if (!ok) {instance._playFailed();} return instance; }; /** * Creates a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} using the passed in src. If the src does not have a * supported extension or if there is no available plugin, a default AbstractSoundInstance will be returned that can be * called safely but does nothing. * *

        Example

        * * var myInstance = null; * createjs.Sound.on("fileload", handleLoad); * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); * function handleLoad(event) { * myInstance = createjs.Sound.createInstance("myID"); * // alternately we could call the following * myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3"); * } * * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. * * @method createInstance * @param {String} src The src or ID of the audio. * @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. * @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created. * Unsupported extensions will return the default AbstractSoundInstance. * @since 0.4.0 * @static */ s.createInstance = function (src, startTime, duration) { if (!s.initializeDefaultPlugins()) { return new createjs.DefaultSoundInstance(src, startTime, duration); } var defaultPlayProps = s._defaultPlayPropsHash[src]; // for audio sprites, which create and store defaults by id src = s._getSrcById(src); var details = s._parsePath(src.src); var instance = null; if (details != null && details.src != null) { SoundChannel.create(details.src); if (startTime == null) { startTime = src.startTime; } instance = s.activePlugin.create(details.src, startTime, duration || src.duration); defaultPlayProps = defaultPlayProps || s._defaultPlayPropsHash[details.src]; if (defaultPlayProps) { instance.applyPlayProps(defaultPlayProps); } } else { instance = new createjs.DefaultSoundInstance(src, startTime, duration); } instance.uniqueId = s._lastID++; return instance; }; /** * Stop all audio (global stop). Stopped audio is reset, and not paused. To play audio that has been stopped, * call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. * *

        Example

        * * createjs.Sound.stop(); * * @method stop * @static */ s.stop = function () { var instances = this._instances; for (var i = instances.length; i--; ) { instances[i].stop(); // NOTE stop removes instance from this._instances } }; /** * Set the default playback properties for all new SoundInstances of the passed in src or ID. * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for available properties. * * @method setDefaultPlayProps * @param {String} src The src or ID used to register the audio. * @param {Object | PlayPropsConfig} playProps The playback properties you would like to set. * @since 0.6.1 */ s.setDefaultPlayProps = function(src, playProps) { src = s._getSrcById(src); s._defaultPlayPropsHash[s._parsePath(src.src).src] = createjs.PlayPropsConfig.create(playProps); }; /** * Get the default playback properties for the passed in src or ID. These properties are applied to all * new SoundInstances. Returns null if default does not exist. * * @method getDefaultPlayProps * @param {String} src The src or ID used to register the audio. * @returns {PlayPropsConfig} returns an existing PlayPropsConfig or null if one does not exist * @since 0.6.1 */ s.getDefaultPlayProps = function(src) { src = s._getSrcById(src); return s._defaultPlayPropsHash[s._parsePath(src.src).src]; }; /* --------------- Internal methods --------------- */ /** * Play an instance. This is called by the static API, as well as from plugins. This allows the core class to * control delays. * @method _playInstance * @param {AbstractSoundInstance} instance The {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to start playing. * @param {PlayPropsConfig} playProps A PlayPropsConfig object. * @return {Boolean} If the sound can start playing. Sounds that fail immediately will return false. Sounds that * have a delay will return true, but may still fail to play. * @private * @static */ s._playInstance = function (instance, playProps) { var defaultPlayProps = s._defaultPlayPropsHash[instance.src] || {}; if (playProps.interrupt == null) {playProps.interrupt = defaultPlayProps.interrupt || s.defaultInterruptBehavior}; if (playProps.delay == null) {playProps.delay = defaultPlayProps.delay || 0;} if (playProps.offset == null) {playProps.offset = instance.position;} if (playProps.loop == null) {playProps.loop = instance.loop;} if (playProps.volume == null) {playProps.volume = instance.volume;} if (playProps.pan == null) {playProps.pan = instance.pan;} if (playProps.delay == 0) { var ok = s._beginPlaying(instance, playProps); if (!ok) {return false;} } else { //Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call. // OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future var delayTimeoutId = setTimeout(function () { s._beginPlaying(instance, playProps); }, playProps.delay); instance.delayTimeoutId = delayTimeoutId; } this._instances.push(instance); return true; }; /** * Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}. * @method _beginPlaying * @param {AbstractSoundInstance} instance A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to begin playback. * @param {PlayPropsConfig} playProps A PlayPropsConfig object. * @return {Boolean} If the sound can start playing. If there are no available channels, or the instance fails to * start, this will return false. * @private * @static */ s._beginPlaying = function (instance, playProps) { if (!SoundChannel.add(instance, playProps.interrupt)) { return false; } var result = instance._beginPlaying(playProps); if (!result) { var index = createjs.indexOf(this._instances, instance); if (index > -1) {this._instances.splice(index, 1);} return false; } return true; }; /** * Get the source of a sound via the ID passed in with a register call. If no ID is found the value is returned * instead. * @method _getSrcById * @param {String} value The ID the sound was registered with. * @return {String} The source of the sound if it has been registered with this ID or the value that was passed in. * @private * @static */ s._getSrcById = function (value) { return s._idHash[value] || {src: value}; }; /** * A sound has completed playback, been interrupted, failed, or been stopped. This method removes the instance from * Sound management. It will be added again, if the sound re-plays. Note that this method is called from the * instances themselves. * @method _playFinished * @param {AbstractSoundInstance} instance The instance that finished playback. * @private * @static */ s._playFinished = function (instance) { SoundChannel.remove(instance); var index = createjs.indexOf(this._instances, instance); if (index > -1) {this._instances.splice(index, 1);} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances }; createjs.Sound = Sound; /** * An internal class that manages the number of active {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} instances for * each sound type. This method is only used internally by the {{#crossLink "Sound"}}{{/crossLink}} class. * * The number of sounds is artificially limited by Sound in order to prevent over-saturation of a * single sound, as well as to stay within hardware limitations, although the latter may disappear with better * browser support. * * When a sound is played, this class ensures that there is an available instance, or interrupts an appropriate * sound that is already playing. * #class SoundChannel * @param {String} src The source of the instances * @param {Number} [max=1] The number of instances allowed * @constructor * @protected */ function SoundChannel(src, max) { this.init(src, max); } /* ------------ Static API ------------ */ /** * A hash of channel instances indexed by source. * #property channels * @type {Object} * @static */ SoundChannel.channels = {}; /** * Create a sound channel. Note that if the sound channel already exists, this will fail. * #method create * @param {String} src The source for the channel * @param {Number} max The maximum amount this channel holds. The default is {{#crossLink "SoundChannel.maxDefault"}}{{/crossLink}}. * @return {Boolean} If the channels were created. * @static */ SoundChannel.create = function (src, max) { var channel = SoundChannel.get(src); if (channel == null) { SoundChannel.channels[src] = new SoundChannel(src, max); return true; } return false; }; /** * Delete a sound channel, stop and delete all related instances. Note that if the sound channel does not exist, this will fail. * #method remove * @param {String} src The source for the channel * @return {Boolean} If the channels were deleted. * @static */ SoundChannel.removeSrc = function (src) { var channel = SoundChannel.get(src); if (channel == null) {return false;} channel._removeAll(); // this stops and removes all active instances delete(SoundChannel.channels[src]); return true; }; /** * Delete all sound channels, stop and delete all related instances. * #method removeAll * @static */ SoundChannel.removeAll = function () { for(var channel in SoundChannel.channels) { SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances } SoundChannel.channels = {}; }; /** * Add an instance to a sound channel. * #method add * @param {AbstractSoundInstance} instance The instance to add to the channel * @param {String} interrupt The interrupt value to use. Please see the {{#crossLink "Sound/play"}}{{/crossLink}} * for details on interrupt modes. * @return {Boolean} The success of the method call. If the channel is full, it will return false. * @static */ SoundChannel.add = function (instance, interrupt) { var channel = SoundChannel.get(instance.src); if (channel == null) {return false;} return channel._add(instance, interrupt); }; /** * Remove an instance from the channel. * #method remove * @param {AbstractSoundInstance} instance The instance to remove from the channel * @return The success of the method call. If there is no channel, it will return false. * @static */ SoundChannel.remove = function (instance) { var channel = SoundChannel.get(instance.src); if (channel == null) {return false;} channel._remove(instance); return true; }; /** * Get the maximum number of sounds you can have in a channel. * #method maxPerChannel * @return {Number} The maximum number of sounds you can have in a channel. */ SoundChannel.maxPerChannel = function () { return p.maxDefault; }; /** * Get a channel instance by its src. * #method get * @param {String} src The src to use to look up the channel * @static */ SoundChannel.get = function (src) { return SoundChannel.channels[src]; }; var p = SoundChannel.prototype; p.constructor = SoundChannel; /** * The source of the channel. * #property src * @type {String} */ p.src = null; /** * The maximum number of instances in this channel. -1 indicates no limit * #property max * @type {Number} */ p.max = null; /** * The default value to set for max, if it isn't passed in. Also used if -1 is passed. * #property maxDefault * @type {Number} * @default 100 * @since 0.4.0 */ p.maxDefault = 100; /** * The current number of active instances. * #property length * @type {Number} */ p.length = 0; /** * Initialize the channel. * #method init * @param {String} src The source of the channel * @param {Number} max The maximum number of instances in the channel * @protected */ p.init = function (src, max) { this.src = src; this.max = max || this.maxDefault; if (this.max == -1) {this.max = this.maxDefault;} this._instances = []; }; /** * Get an instance by index. * #method get * @param {Number} index The index to return. * @return {AbstractSoundInstance} The AbstractSoundInstance at a specific instance. */ p._get = function (index) { return this._instances[index]; }; /** * Add a new instance to the channel. * #method add * @param {AbstractSoundInstance} instance The instance to add. * @return {Boolean} The success of the method call. If the channel is full, it will return false. */ p._add = function (instance, interrupt) { if (!this._getSlot(interrupt, instance)) {return false;} this._instances.push(instance); this.length++; return true; }; /** * Remove an instance from the channel, either when it has finished playing, or it has been interrupted. * #method remove * @param {AbstractSoundInstance} instance The instance to remove * @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will * return false. */ p._remove = function (instance) { var index = createjs.indexOf(this._instances, instance); if (index == -1) {return false;} this._instances.splice(index, 1); this.length--; return true; }; /** * Stop playback and remove all instances from the channel. Usually in response to a delete call. * #method removeAll */ p._removeAll = function () { // Note that stop() removes the item from the list for (var i=this.length-1; i>=0; i--) { this._instances[i].stop(); } }; /** * Get an available slot depending on interrupt value and if slots are available. * #method getSlot * @param {String} interrupt The interrupt value to use. * @param {AbstractSoundInstance} instance The sound instance that will go in the channel if successful. * @return {Boolean} Determines if there is an available slot. Depending on the interrupt mode, if there are no slots, * an existing AbstractSoundInstance may be interrupted. If there are no slots, this method returns false. */ p._getSlot = function (interrupt, instance) { var target, replacement; if (interrupt != Sound.INTERRUPT_NONE) { // First replacement candidate replacement = this._get(0); if (replacement == null) { return true; } } for (var i = 0, l = this.max; i < l; i++) { target = this._get(i); // Available Space if (target == null) { return true; } // Audio is complete or not playing if (target.playState == Sound.PLAY_FINISHED || target.playState == Sound.PLAY_INTERRUPTED || target.playState == Sound.PLAY_FAILED) { replacement = target; break; } if (interrupt == Sound.INTERRUPT_NONE) { continue; } // Audio is a better candidate than the current target, according to playhead if ((interrupt == Sound.INTERRUPT_EARLY && target.position < replacement.position) || (interrupt == Sound.INTERRUPT_LATE && target.position > replacement.position)) { replacement = target; } } if (replacement != null) { replacement._interrupt(); this._remove(replacement); return true; } return false; }; p.toString = function () { return "[Sound SoundChannel]"; }; // do not add SoundChannel to namespace }()); //############################################################################## // AbstractSoundInstance.js //############################################################################## /** * A AbstractSoundInstance is created when any calls to the Sound API method {{#crossLink "Sound/play"}}{{/crossLink}} or * {{#crossLink "Sound/createInstance"}}{{/crossLink}} are made. The AbstractSoundInstance is returned by the active plugin * for control by the user. * *

        Example

        * * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); * * A number of additional parameters provide a quick way to determine how a sound is played. Please see the Sound * API method {{#crossLink "Sound/play"}}{{/crossLink}} for a list of arguments. * * Once a AbstractSoundInstance is created, a reference can be stored that can be used to control the audio directly through * the AbstractSoundInstance. If the reference is not stored, the AbstractSoundInstance will play out its audio (and any loops), and * is then de-referenced from the {{#crossLink "Sound"}}{{/crossLink}} class so that it can be cleaned up. If audio * playback has completed, a simple call to the {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} instance method * will rebuild the references the Sound class need to control it. * * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3", {loop:2}); * myInstance.on("loop", handleLoop); * function handleLoop(event) { * myInstance.volume = myInstance.volume * 0.5; * } * * Events are dispatched from the instance to notify when the sound has completed, looped, or when playback fails * * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); * myInstance.on("complete", handleComplete); * myInstance.on("loop", handleLoop); * myInstance.on("failed", handleFailed); * * * @class AbstractSoundInstance * @param {String} src The path to and file name of the sound. * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @param {Object} playbackResource Any resource needed by plugin to support audio playback. * @extends EventDispatcher * @constructor */ (function () { "use strict"; // Constructor: var AbstractSoundInstance = function (src, startTime, duration, playbackResource) { this.EventDispatcher_constructor(); // public properties: /** * The source of the sound. * @property src * @type {String} * @default null */ this.src = src; /** * The unique ID of the instance. This is set by {{#crossLink "Sound"}}{{/crossLink}}. * @property uniqueId * @type {String} | Number * @default -1 */ this.uniqueId = -1; /** * The play state of the sound. Play states are defined as constants on {{#crossLink "Sound"}}{{/crossLink}}. * @property playState * @type {String} * @default null */ this.playState = null; /** * A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this AbstractSoundInstance is played with a delay. * This allows AbstractSoundInstance to remove the delay if stop, pause, or cleanup are called before playback begins. * @property delayTimeoutId * @type {timeoutVariable} * @default null * @protected * @since 0.4.0 */ this.delayTimeoutId = null; // TODO consider moving delay into AbstractSoundInstance so it can be handled by plugins // private properties // Getter / Setter Properties // OJR TODO find original reason that we didn't use defined functions. I think it was performance related /** * The volume of the sound, between 0 and 1. * * The actual output volume of a sound can be calculated using: * myInstance.volume * createjs.Sound._getVolume(); * * @property volume * @type {Number} * @default 1 */ this._volume = 1; Object.defineProperty(this, "volume", { get: this._getVolume, set: this._setVolume }); this.getVolume = createjs.deprecate(this._getVolume, "AbstractSoundInstance.getVolume"); this.setVolume = createjs.deprecate(this._setVolume, "AbstractSoundInstance.setVolume"); /** * The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio. * * Note in WebAudioPlugin this only gives us the "x" value of what is actually 3D audio * @property pan * @type {Number} * @default 0 */ this._pan = 0; Object.defineProperty(this, "pan", { get: this._getPan, set: this._setPan }); this.getPan = createjs.deprecate(this._getPan, "AbstractSoundInstance.getPan"); this.setPan = createjs.deprecate(this._setPan, "AbstractSoundInstance.setPan"); /** * Audio sprite property used to determine the starting offset. * @property startTime * @type {Number} * @default 0 * @since 0.6.1 */ this._startTime = Math.max(0, startTime || 0); Object.defineProperty(this, "startTime", { get: this._getStartTime, set: this._setStartTime }); this.getStartTime = createjs.deprecate(this._getStartTime, "AbstractSoundInstance.getStartTime"); this.setStartTime = createjs.deprecate(this._setStartTime, "AbstractSoundInstance.setStartTime"); /** * Sets or gets the length of the audio clip, value is in milliseconds. * * @property duration * @type {Number} * @default 0 * @since 0.6.0 */ this._duration = Math.max(0, duration || 0); Object.defineProperty(this, "duration", { get: this._getDuration, set: this._setDuration }); this.getDuration = createjs.deprecate(this._getDuration, "AbstractSoundInstance.getDuration"); this.setDuration = createjs.deprecate(this._setDuration, "AbstractSoundInstance.setDuration"); /** * Object that holds plugin specific resource need for audio playback. * This is set internally by the plugin. For example, WebAudioPlugin will set an array buffer, * HTMLAudioPlugin will set a tag, FlashAudioPlugin will set a flash reference. * * @property playbackResource * @type {Object} * @default null */ this._playbackResource = null; Object.defineProperty(this, "playbackResource", { get: this._getPlaybackResource, set: this._setPlaybackResource }); if(playbackResource !== false && playbackResource !== true) { this._setPlaybackResource(playbackResource); } this.getPlaybackResource = createjs.deprecate(this._getPlaybackResource, "AbstractSoundInstance.getPlaybackResource"); this.setPlaybackResource = createjs.deprecate(this._setPlaybackResource, "AbstractSoundInstance.setPlaybackResource"); /** * The position of the playhead in milliseconds. This can be set while a sound is playing, paused, or stopped. * * @property position * @type {Number} * @default 0 * @since 0.6.0 */ this._position = 0; Object.defineProperty(this, "position", { get: this._getPosition, set: this._setPosition }); this.getPosition = createjs.deprecate(this._getPosition, "AbstractSoundInstance.getPosition"); this.setPosition = createjs.deprecate(this._setPosition, "AbstractSoundInstance.setPosition"); /** * The number of play loops remaining. Negative values will loop infinitely. * * @property loop * @type {Number} * @default 0 * @public * @since 0.6.0 */ this._loop = 0; Object.defineProperty(this, "loop", { get: this._getLoop, set: this._setLoop }); this.getLoop = createjs.deprecate(this._getLoop, "AbstractSoundInstance.getLoop"); this.setLoop = createjs.deprecate(this._setLoop, "AbstractSoundInstance.setLoop"); /** * Mutes or unmutes the current audio instance. * * @property muted * @type {Boolean} * @default false * @since 0.6.0 */ this._muted = false; Object.defineProperty(this, "muted", { get: this._getMuted, set: this._setMuted }); this.getMuted = createjs.deprecate(this._getMuted, "AbstractSoundInstance.getMuted"); this.setMuted = createjs.deprecate(this._setMuted, "AbstractSoundInstance.setMuted"); /** * Pauses or resumes the current audio instance. * * @property paused * @type {Boolean} */ this._paused = false; Object.defineProperty(this, "paused", { get: this._getPaused, set: this._setPaused }); this.getPaused = createjs.deprecate(this._getPaused, "AbstractSoundInstance.getPaused"); this.setPaused = createjs.deprecate(this._setPaused, "AbstractSoundInstance.setPaused"); // Events /** * The event that is fired when playback has started successfully. * @event succeeded * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.4.0 */ /** * The event that is fired when playback is interrupted. This happens when another sound with the same * src property is played using an interrupt value that causes this instance to stop playing. * @event interrupted * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.4.0 */ /** * The event that is fired when playback has failed. This happens when there are too many channels with the same * src property already playing (and the interrupt value doesn't cause an interrupt of another instance), or * the sound could not be played, perhaps due to a 404 error. * @event failed * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.4.0 */ /** * The event that is fired when a sound has completed playing but has loops remaining. * @event loop * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.4.0 */ /** * The event that is fired when playback completes. This means that the sound has finished playing in its * entirety, including its loop iterations. * @event complete * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.4.0 */ }; var p = createjs.extend(AbstractSoundInstance, createjs.EventDispatcher); // Public Methods: /** * Play an instance. This method is intended to be called on SoundInstances that already exist (created * with the Sound API {{#crossLink "Sound/createInstance"}}{{/crossLink}} or {{#crossLink "Sound/play"}}{{/crossLink}}). * *

        Example

        * * var myInstance = createjs.Sound.createInstance(mySrc); * myInstance.play({interrupt:createjs.Sound.INTERRUPT_ANY, loop:2, pan:0.5}); * * Note that if this sound is already playing, this call will still set the passed in parameters. * Parameters Deprecated
        * The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}. * * @method play * @param {Object | PlayPropsConfig} props A PlayPropsConfig instance, or an object that contains the parameters to * play a sound. See the {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for more info. * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. */ p.play = function (props) { var playProps = createjs.PlayPropsConfig.create(props); if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { this.applyPlayProps(playProps); if (this._paused) { this._setPaused(false); } return; } this._cleanUp(); createjs.Sound._playInstance(this, playProps); // make this an event dispatch?? return this; }; /** * Stop playback of the instance. Stopped sounds will reset their position to 0, and calls to {{#crossLink "AbstractSoundInstance/resume"}}{{/crossLink}} * will fail. To start playback again, call {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. * * If you don't want to lose your position use yourSoundInstance.paused = true instead. {{#crossLink "AbstractSoundInstance/paused"}}{{/crossLink}}. * *

        Example

        * * myInstance.stop(); * * @method stop * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. */ p.stop = function () { this._position = 0; this._paused = false; this._handleStop(); this._cleanUp(); this.playState = createjs.Sound.PLAY_FINISHED; return this; }; /** * Remove all external references and resources from AbstractSoundInstance. Note this is irreversible and AbstractSoundInstance will no longer work * @method destroy * @since 0.6.0 */ p.destroy = function() { this._cleanUp(); this.src = null; this.playbackResource = null; this.removeAllEventListeners(); }; /** * Takes an PlayPropsConfig or Object with the same properties and sets them on this instance. * @method applyPlayProps * @param {PlayPropsConfig | Object} playProps A PlayPropsConfig or object containing the same properties. * @since 0.6.1 * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. */ p.applyPlayProps = function(playProps) { if (playProps.offset != null) { this._setPosition(playProps.offset) } if (playProps.loop != null) { this._setLoop(playProps.loop); } if (playProps.volume != null) { this._setVolume(playProps.volume); } if (playProps.pan != null) { this._setPan(playProps.pan); } if (playProps.startTime != null) { this._setStartTime(playProps.startTime); this._setDuration(playProps.duration); } return this; }; p.toString = function () { return "[AbstractSoundInstance]"; }; // get/set methods that allow support for IE8 /** * Please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property. * @method _getPaused * @protected * @return {boolean} If the instance is currently paused * @since 0.6.0 */ p._getPaused = function() { return this._paused; }; /** * Please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property * @method _setPaused * @protected * @param {boolean} value * @since 0.6.0 * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. */ p._setPaused = function (value) { if ((value !== true && value !== false) || this._paused == value) {return;} if (value == true && this.playState != createjs.Sound.PLAY_SUCCEEDED) {return;} this._paused = value; if(value) { this._pause(); } else { this._resume(); } clearTimeout(this.delayTimeoutId); return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property * @method _setVolume * @protected * @param {Number} value The volume to set, between 0 and 1. * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. */ p._setVolume = function (value) { if (value == this._volume) { return this; } this._volume = Math.max(0, Math.min(1, value)); if (!this._muted) { this._updateVolume(); } return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property * @method _getVolume * @protected * @return {Number} The current volume of the sound instance. */ p._getVolume = function () { return this._volume; }; /** * Please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property * @method _setMuted * @protected * @param {Boolean} value If the sound should be muted. * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. * @since 0.6.0 */ p._setMuted = function (value) { if (value !== true && value !== false) {return;} this._muted = value; this._updateVolume(); return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property * @method _getMuted * @protected * @return {Boolean} If the sound is muted. * @since 0.6.0 */ p._getMuted = function () { return this._muted; }; /** * Please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property * @method _setPan * @protected * @param {Number} value The pan value, between -1 (left) and 1 (right). * @return {AbstractSoundInstance} Returns reference to itself for chaining calls */ p._setPan = function (value) { if(value == this._pan) { return this; } this._pan = Math.max(-1, Math.min(1, value)); this._updatePan(); return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property * @method _getPan * @protected * @return {Number} The value of the pan, between -1 (left) and 1 (right). */ p._getPan = function () { return this._pan; }; /** * Please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property * @method _getPosition * @protected * @return {Number} The position of the playhead in the sound, in milliseconds. */ p._getPosition = function () { if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) { this._position = this._calculateCurrentPosition(); } return this._position; }; /** * Please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property * @method _setPosition * @protected * @param {Number} value The position to place the playhead, in milliseconds. * @return {AbstractSoundInstance} Returns reference to itself for chaining calls */ p._setPosition = function (value) { this._position = Math.max(0, value); if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { this._updatePosition(); } return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property * @method _getStartTime * @protected * @return {Number} The startTime of the sound instance in milliseconds. */ p._getStartTime = function () { return this._startTime; }; /** * Please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property * @method _setStartTime * @protected * @param {number} value The new startTime time in milli seconds. * @return {AbstractSoundInstance} Returns reference to itself for chaining calls */ p._setStartTime = function (value) { if (value == this._startTime) { return this; } this._startTime = Math.max(0, value || 0); this._updateStartTime(); return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property * @method _getDuration * @protected * @return {Number} The duration of the sound instance in milliseconds. */ p._getDuration = function () { return this._duration; }; /** * Please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property * @method _setDuration * @protected * @param {number} value The new duration time in milli seconds. * @return {AbstractSoundInstance} Returns reference to itself for chaining calls * @since 0.6.0 */ p._setDuration = function (value) { if (value == this._duration) { return this; } this._duration = Math.max(0, value || 0); this._updateDuration(); return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property * @method _setPlaybackResource * @protected * @param {Object} value The new playback resource. * @return {AbstractSoundInstance} Returns reference to itself for chaining calls * @since 0.6.0 **/ p._setPlaybackResource = function (value) { this._playbackResource = value; if (this._duration == 0 && this._playbackResource) { this._setDurationFromSource(); } return this; }; /** * Please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property * @method _getPlaybackResource * @protected * @param {Object} value The new playback resource. * @return {Object} playback resource used for playing audio * @since 0.6.0 **/ p._getPlaybackResource = function () { return this._playbackResource; }; /** * Please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property * @method _getLoop * @protected * @return {number} * @since 0.6.0 **/ p._getLoop = function () { return this._loop; }; /** * Please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property * @method _setLoop * @protected * @param {number} value The number of times to loop after play. * @since 0.6.0 */ p._setLoop = function (value) { if(this._playbackResource != null) { // remove looping if (this._loop != 0 && value == 0) { this._removeLooping(value); } // add looping else if (this._loop == 0 && value != 0) { this._addLooping(value); } } this._loop = value; }; // Private Methods: /** * A helper method that dispatches all events for AbstractSoundInstance. * @method _sendEvent * @param {String} type The event type * @protected */ p._sendEvent = function (type) { var event = new createjs.Event(type); this.dispatchEvent(event); }; /** * Clean up the instance. Remove references and clean up any additional properties such as timers. * @method _cleanUp * @protected */ p._cleanUp = function () { clearTimeout(this.delayTimeoutId); // clear timeout that plays delayed sound this._handleCleanUp(); this._paused = false; createjs.Sound._playFinished(this); // TODO change to an event }; /** * The sound has been interrupted. * @method _interrupt * @protected */ p._interrupt = function () { this._cleanUp(); this.playState = createjs.Sound.PLAY_INTERRUPTED; this._sendEvent("interrupted"); }; /** * Called by the Sound class when the audio is ready to play (delay has completed). Starts sound playing if the * src is loaded, otherwise playback will fail. * @method _beginPlaying * @param {PlayPropsConfig} playProps A PlayPropsConfig object. * @return {Boolean} If playback succeeded. * @protected */ // OJR FlashAudioSoundInstance overwrites p._beginPlaying = function (playProps) { this._setPosition(playProps.offset); this._setLoop(playProps.loop); this._setVolume(playProps.volume); this._setPan(playProps.pan); if (playProps.startTime != null) { this._setStartTime(playProps.startTime); this._setDuration(playProps.duration); } if (this._playbackResource != null && this._position < this._duration) { this._paused = false; this._handleSoundReady(); this.playState = createjs.Sound.PLAY_SUCCEEDED; this._sendEvent("succeeded"); return true; } else { this._playFailed(); return false; } }; /** * Play has failed, which can happen for a variety of reasons. * Cleans up instance and dispatches failed event * @method _playFailed * @private */ p._playFailed = function () { this._cleanUp(); this.playState = createjs.Sound.PLAY_FAILED; this._sendEvent("failed"); }; /** * Audio has finished playing. Manually loop it if required. * @method _handleSoundComplete * @param event * @protected */ p._handleSoundComplete = function (event) { this._position = 0; // have to set this as it can be set by pause during playback if (this._loop != 0) { this._loop--; // NOTE this introduces a theoretical limit on loops = float max size x 2 - 1 this._handleLoop(); this._sendEvent("loop"); return; } this._cleanUp(); this.playState = createjs.Sound.PLAY_FINISHED; this._sendEvent("complete"); }; // Plugin specific code /** * Handles starting playback when the sound is ready for playing. * @method _handleSoundReady * @protected */ p._handleSoundReady = function () { // plugin specific code }; /** * Internal function used to update the volume based on the instance volume, master volume, instance mute value, * and master mute value. * @method _updateVolume * @protected */ p._updateVolume = function () { // plugin specific code }; /** * Internal function used to update the pan * @method _updatePan * @protected * @since 0.6.0 */ p._updatePan = function () { // plugin specific code }; /** * Internal function used to update the startTime of the audio. * @method _updateStartTime * @protected * @since 0.6.1 */ p._updateStartTime = function () { // plugin specific code }; /** * Internal function used to update the duration of the audio. * @method _updateDuration * @protected * @since 0.6.0 */ p._updateDuration = function () { // plugin specific code }; /** * Internal function used to get the duration of the audio from the source we'll be playing. * @method _updateDuration * @protected * @since 0.6.0 */ p._setDurationFromSource = function () { // plugin specific code }; /** * Internal function that calculates the current position of the playhead and sets this._position to that value * @method _calculateCurrentPosition * @protected * @since 0.6.0 */ p._calculateCurrentPosition = function () { // plugin specific code that sets this.position }; /** * Internal function used to update the position of the playhead. * @method _updatePosition * @protected * @since 0.6.0 */ p._updatePosition = function () { // plugin specific code }; /** * Internal function called when looping is removed during playback. * @method _removeLooping * @param {number} value The number of times to loop after play. * @protected * @since 0.6.0 */ p._removeLooping = function (value) { // plugin specific code }; /** * Internal function called when looping is added during playback. * @method _addLooping * @param {number} value The number of times to loop after play. * @protected * @since 0.6.0 */ p._addLooping = function (value) { // plugin specific code }; /** * Internal function called when pausing playback * @method _pause * @protected * @since 0.6.0 */ p._pause = function () { // plugin specific code }; /** * Internal function called when resuming playback * @method _resume * @protected * @since 0.6.0 */ p._resume = function () { // plugin specific code }; /** * Internal function called when stopping playback * @method _handleStop * @protected * @since 0.6.0 */ p._handleStop = function() { // plugin specific code }; /** * Internal function called when AbstractSoundInstance is being cleaned up * @method _handleCleanUp * @protected * @since 0.6.0 */ p._handleCleanUp = function() { // plugin specific code }; /** * Internal function called when AbstractSoundInstance has played to end and is looping * @method _handleLoop * @protected * @since 0.6.0 */ p._handleLoop = function () { // plugin specific code }; createjs.AbstractSoundInstance = createjs.promote(AbstractSoundInstance, "EventDispatcher"); createjs.DefaultSoundInstance = createjs.AbstractSoundInstance; // used when no plugin is supported }()); //############################################################################## // AbstractPlugin.js //############################################################################## (function () { "use strict"; // constructor: /** * A default plugin class used as a base for all other plugins. * @class AbstractPlugin * @constructor * @since 0.6.0 */ var AbstractPlugin = function () { // private properties: /** * The capabilities of the plugin. * method and is used internally. * @property _capabilities * @type {Object} * @default null * @protected * @static */ this._capabilities = null; /** * Object hash indexed by the source URI of all created loaders, used to properly destroy them if sources are removed. * @type {Object} * @protected */ this._loaders = {}; /** * Object hash indexed by the source URI of each file to indicate if an audio source has begun loading, * is currently loading, or has completed loading. Can be used to store non boolean data after loading * is complete (for example arrayBuffers for web audio). * @property _audioSources * @type {Object} * @protected */ this._audioSources = {}; /** * Object hash indexed by the source URI of all created SoundInstances, updates the playbackResource if it loads after they are created, * and properly destroy them if sources are removed * @type {Object} * @protected */ this._soundInstances = {}; /** * The internal master volume value of the plugin. * @property _volume * @type {Number} * @default 1 * @protected */ this._volume = 1; /** * A reference to a loader class used by a plugin that must be set. * @type {Object} * @protected */ this._loaderClass; /** * A reference to an AbstractSoundInstance class used by a plugin that must be set. * @type {Object} * @protected; */ this._soundInstanceClass; }; var p = AbstractPlugin.prototype; // Static Properties: // NOTE THESE PROPERTIES NEED TO BE ADDED TO EACH PLUGIN /** * The capabilities of the plugin. This is generated via the _generateCapabilities method and is used internally. * @property _capabilities * @type {Object} * @default null * @private * @static */ AbstractPlugin._capabilities = null; /** * Determine if the plugin can be used in the current browser/OS. * @method isSupported * @return {Boolean} If the plugin can be initialized. * @static */ AbstractPlugin.isSupported = function () { return true; }; // public methods: /** * Pre-register a sound for preloading and setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}. * Note all plugins provide a Loader instance, which PreloadJS * can use to assist with preloading. * @method register * @param {String} loadItem An Object containing the source of the audio * Note that not every plugin will manage this value. * @return {Object} A result object, containing a "tag" for preloading purposes. */ p.register = function (loadItem) { var loader = this._loaders[loadItem.src]; if(loader && !loader.canceled) {return this._loaders[loadItem.src];} // already loading/loaded this, so don't load twice // OJR potential issue that we won't be firing loaded event, might need to trigger if this is already loaded? this._audioSources[loadItem.src] = true; this._soundInstances[loadItem.src] = []; loader = new this._loaderClass(loadItem); loader.on("complete", this._handlePreloadComplete, this); this._loaders[loadItem.src] = loader; return loader; }; // note sound calls register before calling preload /** * Internally preload a sound. * @method preload * @param {Loader} loader The sound URI to load. */ p.preload = function (loader) { loader.on("error", this._handlePreloadError, this); loader.load(); }; /** * Checks if preloading has started for a specific source. If the source is found, we can assume it is loading, * or has already finished loading. * @method isPreloadStarted * @param {String} src The sound URI to check. * @return {Boolean} */ p.isPreloadStarted = function (src) { return (this._audioSources[src] != null); }; /** * Checks if preloading has finished for a specific source. * @method isPreloadComplete * @param {String} src The sound URI to load. * @return {Boolean} */ p.isPreloadComplete = function (src) { return (!(this._audioSources[src] == null || this._audioSources[src] == true)); }; /** * Remove a sound added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. * @method removeSound * @param {String} src The sound URI to unload. */ p.removeSound = function (src) { if (!this._soundInstances[src]) { return; } for (var i = this._soundInstances[src].length; i--; ) { var item = this._soundInstances[src][i]; item.destroy(); } delete(this._soundInstances[src]); delete(this._audioSources[src]); if(this._loaders[src]) { this._loaders[src].destroy(); } delete(this._loaders[src]); }; /** * Remove all sounds added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. * @method removeAllSounds * @param {String} src The sound URI to unload. */ p.removeAllSounds = function () { for(var key in this._audioSources) { this.removeSound(key); } }; /** * Create a sound instance. If the sound has not been preloaded, it is internally preloaded here. * @method create * @param {String} src The sound source to use. * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @return {AbstractSoundInstance} A sound instance for playback and control. */ p.create = function (src, startTime, duration) { if (!this.isPreloadStarted(src)) { this.preload(this.register(src)); } var si = new this._soundInstanceClass(src, startTime, duration, this._audioSources[src]); if(this._soundInstances[src]){ this._soundInstances[src].push(si); } // Plugins that don't have a setVolume should implement a setMasterVolune/setMasterMute // So we have to check that here. si.setMasterVolume && si.setMasterVolume(createjs.Sound.volume); si.setMasterMute && si.setMasterMute(createjs.Sound.muted); return si; }; // if a plugin does not support volume and mute, it should set these to null /** * Set the master volume of the plugin, which affects all SoundInstances. * @method setVolume * @param {Number} value The volume to set, between 0 and 1. * @return {Boolean} If the plugin processes the setVolume call (true). The Sound class will affect all the * instances manually otherwise. */ p.setVolume = function (value) { this._volume = value; this._updateVolume(); return true; }; /** * Get the master volume of the plugin, which affects all SoundInstances. * @method getVolume * @return {Number} The volume level, between 0 and 1. */ p.getVolume = function () { return this._volume; }; /** * Mute all sounds via the plugin. * @method setMute * @param {Boolean} value If all sound should be muted or not. Note that plugin-level muting just looks up * the mute value of Sound {{#crossLink "Sound/muted:property"}}{{/crossLink}}, so this property is not used here. * @return {Boolean} If the mute call succeeds. */ p.setMute = function (value) { this._updateVolume(); return true; }; // plugins should overwrite this method p.toString = function () { return "[AbstractPlugin]"; }; // private methods: /** * Handles internal preload completion. * @method _handlePreloadComplete * @param event * @protected */ p._handlePreloadComplete = function (event) { var src = event.target.getItem().src; this._audioSources[src] = event.result; for (var i = 0, l = this._soundInstances[src].length; i < l; i++) { var item = this._soundInstances[src][i]; item.playbackResource = this._audioSources[src]; // ToDo consider adding play call here if playstate == playfailed this._soundInstances[src] = null; } }; /** * Handles internal preload errors * @method _handlePreloadError * @param event * @protected */ p._handlePreloadError = function(event) { //delete(this._audioSources[src]); }; /** * Set the gain value for master audio. Should not be called externally. * @method _updateVolume * @protected */ p._updateVolume = function () { // Plugin Specific code }; createjs.AbstractPlugin = AbstractPlugin; }()); //############################################################################## // WebAudioLoader.js //############################################################################## (function () { "use strict"; /** * Loader provides a mechanism to preload Web Audio content via PreloadJS or internally. Instances are returned to * the preloader, and the load method is called when the asset needs to be requested. * * @class WebAudioLoader * @param {String} loadItem The item to be loaded * @extends XHRRequest * @protected */ function Loader(loadItem) { this.AbstractLoader_constructor(loadItem, true, createjs.Types.SOUND); }; var p = createjs.extend(Loader, createjs.AbstractLoader); /** * web audio context required for decoding audio * @property context * @type {AudioContext} * @static */ Loader.context = null; // public methods p.toString = function () { return "[WebAudioLoader]"; }; // private methods p._createRequest = function() { this._request = new createjs.XHRRequest(this._item, false); this._request.setResponseType("arraybuffer"); }; p._sendComplete = function (event) { // OJR we leave this wrapped in Loader because we need to reference src and the handler only receives a single argument, the decodedAudio Loader.context.decodeAudioData(this._rawResult, createjs.proxy(this._handleAudioDecoded, this), createjs.proxy(this._sendError, this)); }; /** * The audio has been decoded. * @method handleAudioDecoded * @param decoded * @protected */ p._handleAudioDecoded = function (decodedAudio) { this._result = decodedAudio; this.AbstractLoader__sendComplete(); }; createjs.WebAudioLoader = createjs.promote(Loader, "AbstractLoader"); }()); //############################################################################## // WebAudioSoundInstance.js //############################################################################## /** * WebAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by * {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. * * WebAudioSoundInstance exposes audioNodes for advanced users. * * @param {String} src The path to and file name of the sound. * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @param {Object} playbackResource Any resource needed by plugin to support audio playback. * @class WebAudioSoundInstance * @extends AbstractSoundInstance * @constructor */ (function () { "use strict"; function WebAudioSoundInstance(src, startTime, duration, playbackResource) { this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); // public properties /** * NOTE this is only intended for use by advanced users. *
        GainNode for controlling WebAudioSoundInstance volume. Connected to the {{#crossLink "WebAudioSoundInstance/destinationNode:property"}}{{/crossLink}}. * @property gainNode * @type {AudioGainNode} * @since 0.4.0 * */ this.gainNode = s.context.createGain(); /** * NOTE this is only intended for use by advanced users. *
        A panNode allowing left and right audio channel panning only. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}. * @property panNode * @type {AudioPannerNode} * @since 0.4.0 */ this.panNode = s.context.createPanner(); this.panNode.panningModel = s._panningModel; this.panNode.connect(this.gainNode); this._updatePan(); /** * NOTE this is only intended for use by advanced users. *
        sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. * @property sourceNode * @type {AudioNode} * @since 0.4.0 * */ this.sourceNode = null; // private properties /** * Timeout that is created internally to handle sound playing to completion. * Stored so we can remove it when stop, pause, or cleanup are called * @property _soundCompleteTimeout * @type {timeoutVariable} * @default null * @protected * @since 0.4.0 */ this._soundCompleteTimeout = null; /** * NOTE this is only intended for use by very advanced users. * _sourceNodeNext is the audio source for the next loop, inserted in a look ahead approach to allow for smooth * looping. Connected to {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}. * @property _sourceNodeNext * @type {AudioNode} * @default null * @protected * @since 0.4.1 * */ this._sourceNodeNext = null; /** * Time audio started playback, in seconds. Used to handle set position, get position, and resuming from paused. * @property _playbackStartTime * @type {Number} * @default 0 * @protected * @since 0.4.0 */ this._playbackStartTime = 0; // Proxies, make removing listeners easier. this._endedHandler = createjs.proxy(this._handleSoundComplete, this); }; var p = createjs.extend(WebAudioSoundInstance, createjs.AbstractSoundInstance); var s = WebAudioSoundInstance; /** * Note this is only intended for use by advanced users. *
        Audio context used to create nodes. This is and needs to be the same context used by {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. * @property context * @type {AudioContext} * @static * @since 0.6.0 */ s.context = null; /** * Note this is only intended for use by advanced users. *
        The scratch buffer that will be assigned to the buffer property of a source node on close. * This is and should be the same scratch buffer referenced by {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. * @property _scratchBuffer * @type {AudioBufferSourceNode} * @static */ s._scratchBuffer = null; /** * Note this is only intended for use by advanced users. *
        Audio node from WebAudioPlugin that sequences to context.destination * @property destinationNode * @type {AudioNode} * @static * @since 0.6.0 */ s.destinationNode = null; /** * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. * @property _panningModel * @type {Number / String} * @protected * @static * @since 0.6.0 */ s._panningModel = "equalpower"; // Public methods p.destroy = function() { this.AbstractSoundInstance_destroy(); this.panNode.disconnect(0); this.panNode = null; this.gainNode.disconnect(0); this.gainNode = null; }; p.toString = function () { return "[WebAudioSoundInstance]"; }; // Private Methods p._updatePan = function() { this.panNode.setPosition(this._pan, 0, -0.5); // z need to be -0.5 otherwise the sound only plays in left, right, or center }; p._removeLooping = function(value) { this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); }; p._addLooping = function(value) { if (this.playState != createjs.Sound.PLAY_SUCCEEDED) { return; } this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); }; p._setDurationFromSource = function () { this._duration = this.playbackResource.duration * 1000; }; p._handleCleanUp = function () { if (this.sourceNode && this.playState == createjs.Sound.PLAY_SUCCEEDED) { this.sourceNode = this._cleanUpAudioNode(this.sourceNode); this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); } if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} // OJR there appears to be a bug that this doesn't always work in webkit (Chrome and Safari). According to the documentation, this should work. clearTimeout(this._soundCompleteTimeout); this._playbackStartTime = 0; // This is used by _getPosition }; /** * Turn off and disconnect an audioNode, then set reference to null to release it for garbage collection * @method _cleanUpAudioNode * @param audioNode * @return {audioNode} * @protected * @since 0.4.1 */ p._cleanUpAudioNode = function(audioNode) { if(audioNode) { audioNode.stop(0); audioNode.disconnect(0); // necessary to prevent leak on iOS Safari 7-9. will throw in almost all other // browser implementations. if ( createjs.BrowserDetect.isIOS ) { try { audioNode.buffer = s._scratchBuffer; } catch(e) {} } audioNode = null; } return audioNode; }; p._handleSoundReady = function (event) { this.gainNode.connect(s.destinationNode); // this line can cause a memory leak. Nodes need to be disconnected from the audioDestination or any sequence that leads to it. var dur = this._duration * 0.001, pos = Math.min(Math.max(0, this._position) * 0.001, dur); this.sourceNode = this._createAndPlayAudioNode((s.context.currentTime - dur), pos); this._playbackStartTime = this.sourceNode.startTime - pos; this._soundCompleteTimeout = setTimeout(this._endedHandler, (dur - pos) * 1000); if(this._loop != 0) { this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); } }; /** * Creates an audio node using the current src and context, connects it to the gain node, and starts playback. * @method _createAndPlayAudioNode * @param {Number} startTime The time to add this to the web audio context, in seconds. * @param {Number} offset The amount of time into the src audio to start playback, in seconds. * @return {audioNode} * @protected * @since 0.4.1 */ p._createAndPlayAudioNode = function(startTime, offset) { var audioNode = s.context.createBufferSource(); audioNode.buffer = this.playbackResource; audioNode.connect(this.panNode); var dur = this._duration * 0.001; audioNode.startTime = startTime + dur; audioNode.start(audioNode.startTime, offset+(this._startTime*0.001), dur - offset); return audioNode; }; p._pause = function () { this._position = (s.context.currentTime - this._playbackStartTime) * 1000; // * 1000 to give milliseconds, lets us restart at same point this.sourceNode = this._cleanUpAudioNode(this.sourceNode); this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} clearTimeout(this._soundCompleteTimeout); }; p._resume = function () { this._handleSoundReady(); }; /* p._handleStop = function () { // web audio does not need to do anything extra }; */ p._updateVolume = function () { var newVolume = this._muted ? 0 : this._volume; if (newVolume != this.gainNode.gain.value) { this.gainNode.gain.value = newVolume; } }; p._calculateCurrentPosition = function () { return ((s.context.currentTime - this._playbackStartTime) * 1000); // pos in seconds * 1000 to give milliseconds }; p._updatePosition = function () { this.sourceNode = this._cleanUpAudioNode(this.sourceNode); this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); clearTimeout(this._soundCompleteTimeout); if (!this._paused) {this._handleSoundReady();} }; // OJR we are using a look ahead approach to ensure smooth looping. // We add _sourceNodeNext to the audio context so that it starts playing even if this callback is delayed. // This technique is described here: http://www.html5rocks.com/en/tutorials/audio/scheduling/ // NOTE the cost of this is that our audio loop may not always match the loop event timing precisely. p._handleLoop = function () { this._cleanUpAudioNode(this.sourceNode); this.sourceNode = this._sourceNodeNext; this._playbackStartTime = this.sourceNode.startTime; this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); this._soundCompleteTimeout = setTimeout(this._endedHandler, this._duration); }; p._updateDuration = function () { if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { this._pause(); this._resume(); } }; createjs.WebAudioSoundInstance = createjs.promote(WebAudioSoundInstance, "AbstractSoundInstance"); }()); //############################################################################## // WebAudioPlugin.js //############################################################################## (function () { "use strict"; /** * Play sounds using Web Audio in the browser. The WebAudioPlugin is currently the default plugin, and will be used * anywhere that it is supported. To change plugin priority, check out the Sound API * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method. *

        Known Browser and OS issues for Web Audio

        * Firefox 25 *
      • * mp3 audio files do not load properly on all windows machines, reported here. *
        For this reason it is recommended to pass another FireFox-supported type (i.e. ogg) as the default * extension, until this bug is resolved *
      • * * Webkit (Chrome and Safari) *
      • * AudioNode.disconnect does not always seem to work. This can cause the file size to grow over time if you * are playing a lot of audio files. *
      • * * iOS 6 limitations *
          *
        • * Sound is initially muted and will only unmute through play being called inside a user initiated event * (touch/click). Please read the mobile playback notes in the the {{#crossLink "Sound"}}{{/crossLink}} * class for a full overview of the limitations, and how to get around them. *
        • *
        • * A bug exists that will distort un-cached audio when a video element is present in the DOM. You can avoid * this bug by ensuring the audio and video audio share the same sample rate. *
        • *
        * @class WebAudioPlugin * @extends AbstractPlugin * @constructor * @since 0.4.0 */ function WebAudioPlugin() { this.AbstractPlugin_constructor(); // Private Properties /** * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. * @property _panningModel * @type {Number / String} * @protected */ this._panningModel = s._panningModel;; /** * The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin * need to be created within this context. * @property context * @type {AudioContext} */ this.context = s.context; /** * A DynamicsCompressorNode, which is used to improve sound quality and prevent audio distortion. * It is connected to context.destination. * * Can be accessed by advanced users through createjs.Sound.activePlugin.dynamicsCompressorNode. * @property dynamicsCompressorNode * @type {AudioNode} */ this.dynamicsCompressorNode = this.context.createDynamicsCompressor(); this.dynamicsCompressorNode.connect(this.context.destination); /** * A GainNode for controlling master volume. It is connected to {{#crossLink "WebAudioPlugin/dynamicsCompressorNode:property"}}{{/crossLink}}. * * Can be accessed by advanced users through createjs.Sound.activePlugin.gainNode. * @property gainNode * @type {AudioGainNode} */ this.gainNode = this.context.createGain(); this.gainNode.connect(this.dynamicsCompressorNode); createjs.WebAudioSoundInstance.destinationNode = this.gainNode; this._capabilities = s._capabilities; this._loaderClass = createjs.WebAudioLoader; this._soundInstanceClass = createjs.WebAudioSoundInstance; this._addPropsToClasses(); } var p = createjs.extend(WebAudioPlugin, createjs.AbstractPlugin); // Static Properties var s = WebAudioPlugin; /** * The capabilities of the plugin. This is generated via the {{#crossLink "WebAudioPlugin/_generateCapabilities:method"}}{{/crossLink}} * method and is used internally. * @property _capabilities * @type {Object} * @default null * @private * @static */ s._capabilities = null; /** * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. * @property _panningModel * @type {Number / String} * @private * @static */ s._panningModel = "equalpower"; /** * The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin * need to be created within this context. * * Advanced users can set this to an existing context, but must do so before they call * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} or {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}. * * @property context * @type {AudioContext} * @static */ s.context = null; /** * The scratch buffer that will be assigned to the buffer property of a source node on close. * Works around an iOS Safari bug: https://github.com/CreateJS/SoundJS/issues/102 * * Advanced users can set this to an existing source node, but must do so before they call * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} or {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}. * * @property _scratchBuffer * @type {AudioBuffer} * @private * @static */ s._scratchBuffer = null; /** * Indicated whether audio on iOS has been unlocked, which requires a touchend/mousedown event that plays an * empty sound. * @property _unlocked * @type {boolean} * @since 0.6.2 * @private */ s._unlocked = false; /** * The default sample rate used when checking for iOS compatibility. See {{#crossLink "WebAudioPlugin/_createAudioContext"}}{{/crossLink}}. * @property DEFAULT_SAMPLE_REATE * @type {number} * @default 44100 * @static */ s.DEFAULT_SAMPLE_RATE = 44100; // Static Public Methods /** * Determine if the plugin can be used in the current browser/OS. * @method isSupported * @return {Boolean} If the plugin can be initialized. * @static */ s.isSupported = function () { // check if this is some kind of mobile device, Web Audio works with local protocol under PhoneGap and it is unlikely someone is trying to run a local file var isMobilePhoneGap = createjs.BrowserDetect.isIOS || createjs.BrowserDetect.isAndroid || createjs.BrowserDetect.isBlackberry; // OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing. if (location.protocol == "file:" && !isMobilePhoneGap && !this._isFileXHRSupported()) { return false; } // Web Audio requires XHR, which is not usually available locally s._generateCapabilities(); if (s.context == null) {return false;} return true; }; /** * Plays an empty sound in the web audio context. This is used to enable web audio on iOS devices, as they * require the first sound to be played inside of a user initiated event (touch/click). This is called when * {{#crossLink "WebAudioPlugin"}}{{/crossLink}} is initialized (by Sound {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}} * for example). * *

        Example

        * * function handleTouch(event) { * createjs.WebAudioPlugin.playEmptySound(); * } * * @method playEmptySound * @static * @since 0.4.1 */ s.playEmptySound = function() { if (s.context == null) {return;} var source = s.context.createBufferSource(); source.buffer = s._scratchBuffer; source.connect(s.context.destination); source.start(0, 0, 0); }; // Static Private Methods /** * Determine if XHR is supported, which is necessary for web audio. * @method _isFileXHRSupported * @return {Boolean} If XHR is supported. * @since 0.4.2 * @private * @static */ s._isFileXHRSupported = function() { // it's much easier to detect when something goes wrong, so let's start optimistically var supported = true; var xhr = new XMLHttpRequest(); try { xhr.open("GET", "WebAudioPluginTest.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call) } catch (error) { // catch errors in cases where the onerror is passed by supported = false; return supported; } xhr.onerror = function() { supported = false; }; // cause irrelevant // with security turned off, we can get empty success results, which is actually a failed read (status code 0?) xhr.onload = function() { supported = this.status == 404 || (this.status == 200 || (this.status == 0 && this.response != "")); }; try { xhr.send(); } catch (error) { // catch errors in cases where the onerror is passed by supported = false; } return supported; }; /** * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} * method for an overview of plugin capabilities. * @method _generateCapabilities * @static * @private */ s._generateCapabilities = function () { if (s._capabilities != null) {return;} // Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section var t = document.createElement("audio"); if (t.canPlayType == null) {return null;} if (s.context == null) { s.context = s._createAudioContext(); if (s.context == null) { return null; } } if (s._scratchBuffer == null) { s._scratchBuffer = s.context.createBuffer(1, 1, 22050); } s._compatibilitySetUp(); // Listen for document level clicks to unlock WebAudio on iOS. See the _unlock method. if ("ontouchstart" in window && s.context.state != "running") { s._unlock(); // When played inside of a touch event, this will enable audio on iOS immediately. document.addEventListener("mousedown", s._unlock, true); document.addEventListener("touchstart", s._unlock, true); document.addEventListener("touchend", s._unlock, true); } s._capabilities = { panning:true, volume:true, tracks:-1 }; // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS; var extensionMap = createjs.Sound.EXTENSION_MAP; for (var i = 0, l = supportedExtensions.length; i < l; i++) { var ext = supportedExtensions[i]; var playType = extensionMap[ext] || ext; s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != ""); } // OJR another way to do this might be canPlayType:"m4a", codex: mp4 // 0=no output, 1=mono, 2=stereo, 4=surround, 6=5.1 surround. // See http://www.w3.org/TR/webaudio/#AudioChannelSplitter for more details on channels. if (s.context.destination.numberOfChannels < 2) { s._capabilities.panning = false; } }; /** * Create an audio context for the sound. * * This method handles both vendor prefixes (specifically webkit support), as well as a case on iOS where * audio played with a different sample rate may play garbled when first started. The default sample rate is * 44,100, however it can be changed using the {{#crossLink "WebAudioPlugin/DEFAULT_SAMPLE_RATE:property"}}{{/crossLink}}. * @method _createAudioContext * @return {AudioContext | webkitAudioContext} * @private * @static * @since 1.0.0 */ s._createAudioContext = function() { // Slightly modified version of https://github.com/Jam3/ios-safe-audio-context // Resolves issues with first-run contexts playing garbled on iOS. var AudioCtor = (window.AudioContext || window.webkitAudioContext); if (AudioCtor == null) { return null; } var context = new AudioCtor(); // Check if hack is necessary. Only occurs in iOS6+ devices // and only when you first boot the iPhone, or play a audio/video // with a different sample rate if (/(iPhone|iPad)/i.test(navigator.userAgent) && context.sampleRate !== s.DEFAULT_SAMPLE_RATE) { var buffer = context.createBuffer(1, 1, s.DEFAULT_SAMPLE_RATE), dummy = context.createBufferSource(); dummy.buffer = buffer; dummy.connect(context.destination); dummy.start(0); dummy.disconnect(); context.close() // dispose old context context = new AudioCtor(); } return context; } /** * Set up compatibility if only deprecated web audio calls are supported. * See http://www.w3.org/TR/webaudio/#DeprecationNotes * Needed so we can support new browsers that don't support deprecated calls (Firefox) as well as old browsers that * don't support new calls. * * @method _compatibilitySetUp * @static * @private * @since 0.4.2 */ s._compatibilitySetUp = function() { s._panningModel = "equalpower"; //assume that if one new call is supported, they all are if (s.context.createGain) { return; } // simple name change, functionality the same s.context.createGain = s.context.createGainNode; // source node, add to prototype var audioNode = s.context.createBufferSource(); audioNode.__proto__.start = audioNode.__proto__.noteGrainOn; // note that noteGrainOn requires all 3 parameters audioNode.__proto__.stop = audioNode.__proto__.noteOff; // panningModel s._panningModel = 0; }; /** * Try to unlock audio on iOS. This is triggered from either WebAudio plugin setup (which will work if inside of * a `mousedown` or `touchend` event stack), or the first document touchend/mousedown event. If it fails (touchend * will fail if the user presses for too long, indicating a scroll event instead of a click event. * * Note that earlier versions of iOS supported `touchstart` for this, but iOS9 removed this functionality. Adding * a `touchstart` event to support older platforms may preclude a `mousedown` even from getting fired on iOS9, so we * stick with `mousedown` and `touchend`. * @method _unlock * @since 0.6.2 * @private */ s._unlock = function() { if (s._unlocked) { return; } s.playEmptySound(); if (s.context.state == "running") { document.removeEventListener("mousedown", s._unlock, true); document.removeEventListener("touchend", s._unlock, true); document.removeEventListener("touchstart", s._unlock, true); s._unlocked = true; } }; // Public Methods p.toString = function () { return "[WebAudioPlugin]"; }; // Private Methods /** * Set up needed properties on supported classes WebAudioSoundInstance and WebAudioLoader. * @method _addPropsToClasses * @static * @protected * @since 0.6.0 */ p._addPropsToClasses = function() { var c = this._soundInstanceClass; c.context = this.context; c._scratchBuffer = s._scratchBuffer; c.destinationNode = this.gainNode; c._panningModel = this._panningModel; this._loaderClass.context = this.context; }; /** * Set the gain value for master audio. Should not be called externally. * @method _updateVolume * @protected */ p._updateVolume = function () { var newVolume = createjs.Sound._masterMute ? 0 : this._volume; if (newVolume != this.gainNode.gain.value) { this.gainNode.gain.value = newVolume; } }; createjs.WebAudioPlugin = createjs.promote(WebAudioPlugin, "AbstractPlugin"); }()); //############################################################################## // HTMLAudioTagPool.js //############################################################################## (function () { "use strict"; /** * HTMLAudioTagPool is an object pool for HTMLAudio tag instances. * @class HTMLAudioTagPool * @param {String} src The source of the channel. * @protected */ function HTMLAudioTagPool() { throw "HTMLAudioTagPool cannot be instantiated"; } var s = HTMLAudioTagPool; // Static Properties /** * A hash lookup of each base audio tag, indexed by the audio source. * @property _tags * @type {{}} * @static * @private */ s._tags = {}; /** * An object pool for html audio tags * @property _tagPool * @type {TagPool} * @static * @private */ s._tagPool = new TagPool(); /** * A hash lookup of if a base audio tag is available, indexed by the audio source * @property _tagsUsed * @type {{}} * @private * @static */ s._tagUsed = {}; // Static Methods /** * Get an audio tag with the given source. * @method get * @param {String} src The source file used by the audio tag. * @static */ s.get = function (src) { var t = s._tags[src]; if (t == null) { // create new base tag t = s._tags[src] = s._tagPool.get(); t.src = src; } else { // get base or pool if (s._tagUsed[src]) { t = s._tagPool.get(); t.src = src; } else { s._tagUsed[src] = true; } } return t; }; /** * Return an audio tag to the pool. * @method set * @param {String} src The source file used by the audio tag. * @param {HTMLElement} tag Audio tag to set. * @static */ s.set = function (src, tag) { // check if this is base, if yes set boolean if not return to pool if(tag == s._tags[src]) { s._tagUsed[src] = false; } else { s._tagPool.set(tag); } }; /** * Delete stored tag reference and return them to pool. Note that if the tag reference does not exist, this will fail. * @method remove * @param {String} src The source for the tag * @return {Boolean} If the TagPool was deleted. * @static */ s.remove = function (src) { var tag = s._tags[src]; if (tag == null) {return false;} s._tagPool.set(tag); delete(s._tags[src]); delete(s._tagUsed[src]); return true; }; /** * Gets the duration of the src audio in milliseconds * @method getDuration * @param {String} src The source file used by the audio tag. * @return {Number} Duration of src in milliseconds * @static */ s.getDuration= function (src) { var t = s._tags[src]; if (t == null || !t.duration) {return 0;} // OJR duration is NaN if loading has not completed return t.duration * 1000; }; createjs.HTMLAudioTagPool = HTMLAudioTagPool; // ************************************************************************************************************ /** * The TagPool is an object pool for HTMLAudio tag instances. * #class TagPool * @param {String} src The source of the channel. * @protected */ function TagPool(src) { // Public Properties /** * A list of all available tags in the pool. * #property tags * @type {Array} * @protected */ this._tags = []; }; var p = TagPool.prototype; p.constructor = TagPool; // Public Methods /** * Get an HTMLAudioElement for immediate playback. This takes it out of the pool. * #method get * @return {HTMLAudioElement} An HTML audio tag. */ p.get = function () { var tag; if (this._tags.length == 0) { tag = this._createTag(); } else { tag = this._tags.pop(); } if (tag.parentNode == null) {document.body.appendChild(tag);} return tag; }; /** * Put an HTMLAudioElement back in the pool for use. * #method set * @param {HTMLAudioElement} tag HTML audio tag */ p.set = function (tag) { // OJR this first step seems unnecessary var index = createjs.indexOf(this._tags, tag); if (index == -1) { this._tags.src = null; this._tags.push(tag); } }; p.toString = function () { return "[TagPool]"; }; // Private Methods /** * Create an HTML audio tag. * #method _createTag * @param {String} src The source file to set for the audio tag. * @return {HTMLElement} Returns an HTML audio tag. * @protected */ p._createTag = function () { var tag = document.createElement("audio"); tag.autoplay = false; tag.preload = "none"; //LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works. return tag; }; }()); //############################################################################## // HTMLAudioSoundInstance.js //############################################################################## (function () { "use strict"; /** * HTMLAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. * * @param {String} src The path to and file name of the sound. * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @param {Object} playbackResource Any resource needed by plugin to support audio playback. * @class HTMLAudioSoundInstance * @extends AbstractSoundInstance * @constructor */ function HTMLAudioSoundInstance(src, startTime, duration, playbackResource) { this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); // Private Properties this._audioSpriteStopTime = null; this._delayTimeoutId = null; // Proxies, make removing listeners easier. this._endedHandler = createjs.proxy(this._handleSoundComplete, this); this._readyHandler = createjs.proxy(this._handleTagReady, this); this._stalledHandler = createjs.proxy(this._playFailed, this); this._audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteLoop, this); this._loopHandler = createjs.proxy(this._handleSoundComplete, this); if (duration) { this._audioSpriteStopTime = (startTime + duration) * 0.001; } else { this._duration = createjs.HTMLAudioTagPool.getDuration(this.src); } } var p = createjs.extend(HTMLAudioSoundInstance, createjs.AbstractSoundInstance); // Public Methods /** * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master volume. * undoc'd because it is not meant to be used outside of Sound * #method setMasterVolume * @param value */ p.setMasterVolume = function (value) { this._updateVolume(); }; /** * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master mute. * undoc'd because it is not meant to be used outside of Sound * #method setMasterMute * @param value */ p.setMasterMute = function (isMuted) { this._updateVolume(); }; p.toString = function () { return "[HTMLAudioSoundInstance]"; }; //Private Methods p._removeLooping = function() { if(this._playbackResource == null) {return;} this._playbackResource.loop = false; this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); }; p._addLooping = function() { if(this._playbackResource == null || this._audioSpriteStopTime) {return;} this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); this._playbackResource.loop = true; }; p._handleCleanUp = function () { var tag = this._playbackResource; if (tag != null) { tag.pause(); tag.loop = false; tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); tag.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); try { tag.currentTime = this._startTime; } catch (e) { } // Reset Position createjs.HTMLAudioTagPool.set(this.src, tag); this._playbackResource = null; } }; p._beginPlaying = function (playProps) { this._playbackResource = createjs.HTMLAudioTagPool.get(this.src); return this.AbstractSoundInstance__beginPlaying(playProps); }; p._handleSoundReady = function (event) { if (this._playbackResource.readyState !== 4) { var tag = this._playbackResource; tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); tag.preload = "auto"; // This is necessary for Firefox, as it won't ever "load" until this is set. tag.load(); return; } this._updateVolume(); this._playbackResource.currentTime = (this._startTime + this._position) * 0.001; if (this._audioSpriteStopTime) { this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); } else { this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); if(this._loop != 0) { this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); this._playbackResource.loop = true; } } this._playbackResource.play(); }; /** * Used to handle when a tag is not ready for immediate playback when it is returned from the HTMLAudioTagPool. * @method _handleTagReady * @param event * @protected */ p._handleTagReady = function (event) { this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); this._handleSoundReady(); }; p._pause = function () { this._playbackResource.pause(); }; p._resume = function () { this._playbackResource.play(); }; p._updateVolume = function () { if (this._playbackResource != null) { var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume; if (newVolume != this._playbackResource.volume) {this._playbackResource.volume = newVolume;} } }; p._calculateCurrentPosition = function() { return (this._playbackResource.currentTime * 1000) - this._startTime; }; p._updatePosition = function() { this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); try { this._playbackResource.currentTime = (this._position + this._startTime) * 0.001; } catch (error) { // Out of range this._handleSetPositionSeek(null); } }; /** * Used to enable setting position, as we need to wait for that seek to be done before we add back our loop handling seek listener * @method _handleSetPositionSeek * @param event * @protected */ p._handleSetPositionSeek = function(event) { if (this._playbackResource == null) { return; } this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); }; /** * Timer used to loop audio sprites. * NOTE because of the inaccuracies in the timeupdate event (15 - 250ms) and in setting the tag to the desired timed * (up to 300ms), it is strongly recommended not to loop audio sprites with HTML Audio if smooth looping is desired * * @method _handleAudioSpriteLoop * @param event * @private */ p._handleAudioSpriteLoop = function (event) { if(this._playbackResource.currentTime <= this._audioSpriteStopTime) {return;} this._playbackResource.pause(); if(this._loop == 0) { this._handleSoundComplete(null); } else { this._position = 0; this._loop--; this._playbackResource.currentTime = this._startTime * 0.001; if(!this._paused) {this._playbackResource.play();} this._sendEvent("loop"); } }; // NOTE with this approach audio will loop as reliably as the browser allows // but we could end up sending the loop event after next loop playback begins p._handleLoop = function (event) { if(this._loop == 0) { this._playbackResource.loop = false; this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); } }; p._updateStartTime = function () { this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001; if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); } }; p._updateDuration = function () { this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001; if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); } }; p._setDurationFromSource = function () { this._duration = createjs.HTMLAudioTagPool.getDuration(this.src); this._playbackResource = null; }; createjs.HTMLAudioSoundInstance = createjs.promote(HTMLAudioSoundInstance, "AbstractSoundInstance"); }()); //############################################################################## // HTMLAudioPlugin.js //############################################################################## (function () { "use strict"; /** * Play sounds using HTML <audio> tags in the browser. This plugin is the second priority plugin installed * by default, after the {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. For older browsers that do not support html * audio, include and install the {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. * *

        Known Browser and OS issues for HTML Audio

        * All browsers
        * Testing has shown in all browsers there is a limit to how many audio tag instances you are allowed. If you exceed * this limit, you can expect to see unpredictable results. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as * a guide to how many total audio tags you can safely use in all browsers. This issue is primarily limited to IE9. * * IE html limitations
        *
        • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of * when or how you apply the volume change, as the tag seems to need to play to apply it.
        • *
        • MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with * 64kbps works.
        • *
        • Occasionally very short samples will get cut off.
        • *
        • There is a limit to how many audio tags you can load or play at once, which appears to be determined by * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate. * Note that audio sprites can be used as a solution to this issue.
        * * Safari limitations
        *
        • Safari requires Quicktime to be installed for audio playback.
        * * iOS 6 limitations
        *
        • can only have one <audio> tag
        • *
        • can not preload or autoplay the audio
        • *
        • can not cache the audio
        • *
        • can not play the audio except inside a user initiated event.
        • *
        • Note it is recommended to use {{#crossLink "WebAudioPlugin"}}{{/crossLink}} for iOS (6+)
        • *
        • audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS
        • *
        * * Android Native Browser limitations
        *
        • We have no control over audio volume. Only the user can set volume on their device.
        • *
        • We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use a delay.
        * Android Chrome 26.0.1410.58 specific limitations
        *
        • Can only play 1 sound at a time.
        • *
        • Sound is not cached.
        • *
        • Sound can only be loaded in a user initiated touch/click event.
        • *
        • There is a delay before a sound is played, presumably while the src is loaded.
        • *
        * * See {{#crossLink "Sound"}}{{/crossLink}} for general notes on known issues. * * @class HTMLAudioPlugin * @extends AbstractPlugin * @constructor */ function HTMLAudioPlugin() { this.AbstractPlugin_constructor(); // Public Properties this._capabilities = s._capabilities; this._loaderClass = createjs.SoundLoader; this._soundInstanceClass = createjs.HTMLAudioSoundInstance; } var p = createjs.extend(HTMLAudioPlugin, createjs.AbstractPlugin); var s = HTMLAudioPlugin; // Static Properties /** * The maximum number of instances that can be loaded or played. This is a browser limitation, primarily limited to IE9. * The actual number varies from browser to browser (and is largely hardware dependant), but this is a safe estimate. * Audio sprites work around this limitation. * @property MAX_INSTANCES * @type {Number} * @default 30 * @static */ s.MAX_INSTANCES = 30; /** * Event constant for the "canPlayThrough" event for cleaner code. * @property _AUDIO_READY * @type {String} * @default canplaythrough * @static * @private */ s._AUDIO_READY = "canplaythrough"; /** * Event constant for the "ended" event for cleaner code. * @property _AUDIO_ENDED * @type {String} * @default ended * @static * @private */ s._AUDIO_ENDED = "ended"; /** * Event constant for the "seeked" event for cleaner code. We utilize this event for maintaining loop events. * @property _AUDIO_SEEKED * @type {String} * @default seeked * @static * @private */ s._AUDIO_SEEKED = "seeked"; /** * Event constant for the "stalled" event for cleaner code. * @property _AUDIO_STALLED * @type {String} * @default stalled * @static * @private */ s._AUDIO_STALLED = "stalled"; /** * Event constant for the "timeupdate" event for cleaner code. Utilized for looping audio sprites. * This event callsback ever 15 to 250ms and can be dropped by the browser for performance. * @property _TIME_UPDATE * @type {String} * @default timeupdate * @static * @private */ s._TIME_UPDATE = "timeupdate"; /** * The capabilities of the plugin. This is generated via the {{#crossLink "HTMLAudioPlugin/_generateCapabilities"}}{{/crossLink}} * method. Please see the Sound {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} method for an overview of all * of the available properties. * @property _capabilities * @type {Object} * @private * @static */ s._capabilities = null; // Static Methods /** * Determine if the plugin can be used in the current browser/OS. Note that HTML audio is available in most modern * browsers, but is disabled in iOS because of its limitations. * @method isSupported * @return {Boolean} If the plugin can be initialized. * @static */ s.isSupported = function () { s._generateCapabilities(); return (s._capabilities != null); }; /** * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} * method for an overview of plugin capabilities. * @method _generateCapabilities * @static * @private */ s._generateCapabilities = function () { if (s._capabilities != null) {return;} var t = document.createElement("audio"); if (t.canPlayType == null) {return null;} s._capabilities = { panning:false, volume:true, tracks:-1 }; // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS; var extensionMap = createjs.Sound.EXTENSION_MAP; for (var i = 0, l = supportedExtensions.length; i < l; i++) { var ext = supportedExtensions[i]; var playType = extensionMap[ext] || ext; s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != ""); } // OJR another way to do this might be canPlayType:"m4a", codex: mp4 }; // public methods p.register = function (loadItem) { var tag = createjs.HTMLAudioTagPool.get(loadItem.src); var loader = this.AbstractPlugin_register(loadItem); loader.setTag(tag); return loader; }; p.removeSound = function (src) { this.AbstractPlugin_removeSound(src); createjs.HTMLAudioTagPool.remove(src); }; p.create = function (src, startTime, duration) { var si = this.AbstractPlugin_create(src, startTime, duration); si.playbackResource = null; return si; }; p.toString = function () { return "[HTMLAudioPlugin]"; }; // plugin does not support these p.setVolume = p.getVolume = p.setMute = null; createjs.HTMLAudioPlugin = createjs.promote(HTMLAudioPlugin, "AbstractPlugin"); }()); //############################################################################## // AbstractTween.js //############################################################################## (function() { "use strict"; // constructor /** * Base class that both {{#crossLink "Tween"}}{{/crossLink}} and {{#crossLink "Timeline"}}{{/crossLink}} extend. Should not be instantiated directly. * @class AbstractTween * @param {Object} [props] The configuration properties to apply to this instance (ex. `{loop:-1, paused:true}`). * Supported props are listed below. These props are set on the corresponding instance properties except where * specified. * @param {boolean} [props.useTicks=false] See the {{#crossLink "AbstractTween/useTicks:property"}}{{/crossLink}} property for more information. * @param {boolean} [props.ignoreGlobalPause=false] See the {{#crossLink "AbstractTween/ignoreGlobalPause:property"}}{{/crossLink}} for more information. * @param {number|boolean} [props.loop=0] See the {{#crossLink "AbstractTween/loop:property"}}{{/crossLink}} for more information. * @param {boolean} [props.reversed=false] See the {{#crossLink "AbstractTween/reversed:property"}}{{/crossLink}} for more information. * @param {boolean} [props.bounce=false] See the {{#crossLink "AbstractTween/bounce:property"}}{{/crossLink}} for more information. * @param {number} [props.timeScale=1] See the {{#crossLink "AbstractTween/timeScale:property"}}{{/crossLink}} for more information. * @param {Function} [props.onChange] Adds the specified function as a listener to the {{#crossLink "AbstractTween/change:event"}}{{/crossLink}} event * @param {Function} [props.onComplete] Adds the specified function as a listener to the {{#crossLink "AbstractTween/complete:event"}}{{/crossLink}} event * @extends EventDispatcher * @constructor */ function AbstractTween(props) { this.EventDispatcher_constructor(); // public properties: /** * Causes this tween to continue playing when a global pause is active. For example, if TweenJS is using {{#crossLink "Ticker"}}{{/crossLink}}, * then setting this to false (the default) will cause this tween to be paused when `Ticker.paused` is set to * `true`. See the {{#crossLink "Tween/tick"}}{{/crossLink}} method for more info. Can be set via the `props` * parameter. * @property ignoreGlobalPause * @type Boolean * @default false */ this.ignoreGlobalPause = false; /** * Indicates the number of times to loop. If set to -1, the tween will loop continuously. * * Note that a tween must loop at _least_ once to see it play in both directions when `{{#crossLink "AbstractTween/bounce:property"}}{{/crossLink}}` * is set to `true`. * @property loop * @type {Number} * @default 0 */ this.loop = 0; /** * Uses ticks for all durations instead of milliseconds. This also changes the behaviour of some actions (such as `call`). * Changing this value on a running tween could have unexpected results. * @property useTicks * @type {Boolean} * @default false * @readonly */ this.useTicks = false; /** * Causes the tween to play in reverse. * @property reversed * @type {Boolean} * @default false */ this.reversed = false; /** * Causes the tween to reverse direction at the end of each loop. Each single-direction play-through of the * tween counts as a single bounce. For example, to play a tween once forward, and once back, set the * `{{#crossLink "AbstractTween/loop:property"}}{{/crossLink}}` to `1`. * @property bounce * @type {Boolean} * @default false */ this.bounce = false; /** * Changes the rate at which the tween advances. For example, a `timeScale` value of `2` will double the * playback speed, a value of `0.5` would halve it. * @property timeScale * @type {Number} * @default 1 */ this.timeScale = 1; /** * Indicates the duration of this tween in milliseconds (or ticks if `useTicks` is true), irrespective of `loops`. * This value is automatically updated as you modify the tween. Changing it directly could result in unexpected * behaviour. * @property duration * @type {Number} * @default 0 * @readonly */ this.duration = 0; /** * The current normalized position of the tween. This will always be a value between 0 and `duration`. * Changing this property directly will have unexpected results, use {{#crossLink "Tween/setPosition"}}{{/crossLink}}. * @property position * @type {Object} * @default 0 * @readonly */ this.position = 0; /** * The raw tween position. This value will be between `0` and `loops * duration` while the tween is active, or -1 before it activates. * @property rawPosition * @type {Number} * @default -1 * @readonly */ this.rawPosition = -1; // private properties: /** * @property _paused * @type {Boolean} * @default false * @protected */ this._paused = true; /** * @property _next * @type {Tween} * @default null * @protected */ this._next = null; /** * @property _prev * @type {Tween} * @default null * @protected */ this._prev = null; /** * @property _parent * @type {Object} * @default null * @protected */ this._parent = null; /** * @property _labels * @type Object * @protected **/ this._labels = null; /** * @property _labelList * @type Array[Object] * @protected **/ this._labelList = null; if (props) { this.useTicks = !!props.useTicks; this.ignoreGlobalPause = !!props.ignoreGlobalPause; this.loop = props.loop === true ? -1 : (props.loop||0); this.reversed = !!props.reversed; this.bounce = !!props.bounce; this.timeScale = props.timeScale||1; props.onChange && this.addEventListener("change", props.onChange); props.onComplete && this.addEventListener("complete", props.onComplete); } // while `position` is shared, it needs to happen after ALL props are set, so it's handled in _init() }; var p = createjs.extend(AbstractTween, createjs.EventDispatcher); // events: /** * Dispatched whenever the tween's position changes. It occurs after all tweened properties are updated and actions * are executed. * @event change **/ /** * Dispatched when the tween reaches its end and has paused itself. This does not fire until all loops are complete; * tweens that loop continuously will never fire a complete event. * @event complete **/ // getter / setters: /** * Use the {{#crossLink "AbstractTween/paused:property"}}{{/crossLink}} property instead. * @method _setPaused * @param {Boolean} [value=true] Indicates whether the tween should be paused (`true`) or played (`false`). * @return {AbstractTween} This tween instance (for chaining calls) * @protected * @chainable */ p._setPaused = function(value) { createjs.Tween._register(this, value); return this; }; p.setPaused = createjs.deprecate(p._setPaused, "AbstractTween.setPaused"); /** * Use the {{#crossLink "AbstractTween/paused:property"}}{{/crossLink}} property instead. * @method _getPaused * @protected */ p._getPaused = function() { return this._paused; }; p.getPaused = createjs.deprecate(p._getPaused, "AbstactTween.getPaused"); /** * Use the {{#crossLink "AbstractTween/currentLabel:property"}}{{/crossLink}} property instead. * @method _getCurrentLabel * @protected * @return {String} The name of the current label or null if there is no label **/ p._getCurrentLabel = function(pos) { var labels = this.getLabels(); if (pos == null) { pos = this.position; } for (var i = 0, l = labels.length; i *
      • null if the current position is 2.
      • *
      • "first" if the current position is 4.
      • *
      • "first" if the current position is 7.
      • *
      • "second" if the current position is 15.
      • *
      * @property currentLabel * @type String * @readonly **/ try { Object.defineProperties(p, { paused: { set: p._setPaused, get: p._getPaused }, currentLabel: { get: p._getCurrentLabel } }); } catch (e) {} // public methods: /** * Advances the tween by a specified amount. * @method advance * @param {Number} delta The amount to advance in milliseconds (or ticks if useTicks is true). Negative values are supported. * @param {Number} [ignoreActions=false] If true, actions will not be executed due to this change in position. */ p.advance = function(delta, ignoreActions) { this.setPosition(this.rawPosition+delta*this.timeScale, ignoreActions); }; /** * Advances the tween to a specified position. * @method setPosition * @param {Number} rawPosition The raw position to seek to in milliseconds (or ticks if useTicks is true). * @param {Boolean} [ignoreActions=false] If true, do not run any actions that would be triggered by this operation. * @param {Boolean} [jump=false] If true, only actions at the new position will be run. If false, actions between the old and new position are run. * @param {Function} [callback] Primarily for use with MovieClip, this callback is called after properties are updated, but before actions are run. */ p.setPosition = function(rawPosition, ignoreActions, jump, callback) { var d=this.duration, loopCount=this.loop, prevRawPos = this.rawPosition; var loop=0, t=0, end=false; // normalize position: if (rawPosition < 0) { rawPosition = 0; } if (d === 0) { // deal with 0 length tweens. end = true; if (prevRawPos !== -1) { return end; } // we can avoid doing anything else if we're already at 0. } else { loop = rawPosition/d|0; t = rawPosition-loop*d; end = (loopCount !== -1 && rawPosition >= loopCount*d+d); if (end) { rawPosition = (t=d)*(loop=loopCount)+d; } if (rawPosition === prevRawPos) { return end; } // no need to update var rev = !this.reversed !== !(this.bounce && loop%2); // current loop is reversed if (rev) { t = d-t; } } // set this in advance in case an action modifies position: this.position = t; this.rawPosition = rawPosition; this._updatePosition(jump, end); if (end) { this.paused = true; } callback&&callback(this); if (!ignoreActions) { this._runActions(prevRawPos, rawPosition, jump, !jump && prevRawPos === -1); } this.dispatchEvent("change"); if (end) { this.dispatchEvent("complete"); } }; /** * Calculates a normalized position based on a raw position. For example, given a tween with a duration of 3000ms set to loop: * console.log(myTween.calculatePosition(3700); // 700 * @method calculatePosition * @param {Number} rawPosition A raw position. */ p.calculatePosition = function(rawPosition) { // largely duplicated from setPosition, but necessary to avoid having to instantiate generic objects to pass values (end, loop, position) back. var d=this.duration, loopCount=this.loop, loop=0, t=0; if (d===0) { return 0; } if (loopCount !== -1 && rawPosition >= loopCount*d+d) { t = d; loop = loopCount } // end else if (rawPosition < 0) { t = 0; } else { loop = rawPosition/d|0; t = rawPosition-loop*d; } var rev = !this.reversed !== !(this.bounce && loop%2); // current loop is reversed return rev ? d-t : t; }; /** * Returns a list of the labels defined on this tween sorted by position. * @method getLabels * @return {Array[Object]} A sorted array of objects with label and position properties. **/ p.getLabels = function() { var list = this._labelList; if (!list) { list = this._labelList = []; var labels = this._labels; for (var n in labels) { list.push({label:n, position:labels[n]}); } list.sort(function (a,b) { return a.position- b.position; }); } return list; }; /** * Defines labels for use with gotoAndPlay/Stop. Overwrites any previously set labels. * @method setLabels * @param {Object} labels An object defining labels for using {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}} * in the form `{myLabelName:time}` where time is in milliseconds (or ticks if `useTicks` is `true`). **/ p.setLabels = function(labels) { this._labels = labels; this._labelList = null; }; /** * Adds a label that can be used with {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}}. * @method addLabel * @param {String} label The label name. * @param {Number} position The position this label represents. **/ p.addLabel = function(label, position) { if (!this._labels) { this._labels = {}; } this._labels[label] = position; var list = this._labelList; if (list) { for (var i= 0,l=list.length; i Tween" : "Timeline", "run", startRawPos, endRawPos, jump, includeStart); // if we don't have any actions, and we're not a Timeline, then return: // TODO: a cleaner way to handle this would be to override this method in Tween, but I'm not sure it's worth the overhead. if (!this._actionHead && !this.tweens) { return; } var d=this.duration, reversed=this.reversed, bounce=this.bounce, loopCount=this.loop; var loop0, loop1, t0, t1; if (d === 0) { // deal with 0 length tweens: loop0 = loop1 = t0 = t1 = 0; reversed = bounce = false; } else { loop0=startRawPos/d|0; loop1=endRawPos/d|0; t0=startRawPos-loop0*d; t1=endRawPos-loop1*d; } // catch positions that are past the end: if (loopCount !== -1) { if (loop1 > loopCount) { t1=d; loop1=loopCount; } if (loop0 > loopCount) { t0=d; loop0=loopCount; } } // special cases: if (jump) { return this._runActionsRange(t1, t1, jump, includeStart); } // jump. else if (loop0 === loop1 && t0 === t1 && !jump && !includeStart) { return; } // no actions if the position is identical and we aren't including the start else if (loop0 === -1) { loop0 = t0 = 0; } // correct the -1 value for first advance, important with useTicks. var dir = (startRawPos <= endRawPos), loop = loop0; do { var rev = !reversed !== !(bounce && loop % 2); var start = (loop === loop0) ? t0 : dir ? 0 : d; var end = (loop === loop1) ? t1 : dir ? d : 0; if (rev) { start = d - start; end = d - end; } if (bounce && loop !== loop0 && start === end) { /* bounced onto the same time/frame, don't re-execute end actions */ } else if (this._runActionsRange(start, end, jump, includeStart || (loop !== loop0 && !bounce))) { return true; } includeStart = false; } while ((dir && ++loop <= loop1) || (!dir && --loop >= loop1)); }; p._runActionsRange = function(startPos, endPos, jump, includeStart) { // abstract }; createjs.AbstractTween = createjs.promote(AbstractTween, "EventDispatcher"); }()); //############################################################################## // Tween.js //############################################################################## (function() { "use strict"; // constructor /** * Tweens properties for a single target. Methods can be chained to create complex animation sequences: * *

      Example

      * * createjs.Tween.get(target) * .wait(500) * .to({alpha:0, visible:false}, 1000) * .call(handleComplete); * * Multiple tweens can share a target, however if they affect the same properties there could be unexpected * behaviour. To stop all tweens on an object, use {{#crossLink "Tween/removeTweens"}}{{/crossLink}} or pass `override:true` * in the props argument. * * createjs.Tween.get(target, {override:true}).to({x:100}); * * Subscribe to the {{#crossLink "Tween/change:event"}}{{/crossLink}} event to be notified when the tween position changes. * * createjs.Tween.get(target, {override:true}).to({x:100}).addEventListener("change", handleChange); * function handleChange(event) { * // The tween changed. * } * * See the {{#crossLink "Tween/get"}}{{/crossLink}} method also. * @class Tween * @param {Object} target The target object that will have its properties tweened. * @param {Object} [props] The configuration properties to apply to this instance (ex. `{loop:-1, paused:true}`). * Supported props are listed below. These props are set on the corresponding instance properties except where * specified. * @param {boolean} [props.useTicks=false] See the {{#crossLink "AbstractTween/useTicks:property"}}{{/crossLink}} property for more information. * @param {boolean} [props.ignoreGlobalPause=false] See the {{#crossLink "AbstractTween/ignoreGlobalPause:property"}}{{/crossLink}} for more information. * @param {number|boolean} [props.loop=0] See the {{#crossLink "AbstractTween/loop:property"}}{{/crossLink}} for more information. * @param {boolean} [props.reversed=false] See the {{#crossLink "AbstractTween/reversed:property"}}{{/crossLink}} for more information. * @param {boolean} [props.bounce=false] See the {{#crossLink "AbstractTween/bounce:property"}}{{/crossLink}} for more information. * @param {number} [props.timeScale=1] See the {{#crossLink "AbstractTween/timeScale:property"}}{{/crossLink}} for more information. * @param {object} [props.pluginData] See the {{#crossLink "Tween/pluginData:property"}}{{/crossLink}} for more information. * @param {boolean} [props.paused=false] See the {{#crossLink "AbstractTween/paused:property"}}{{/crossLink}} for more information. * @param {number} [props.position=0] The initial position for this tween. See {{#crossLink "AbstractTween/position:property"}}{{/crossLink}} * @param {Function} [props.onChange] Adds the specified function as a listener to the {{#crossLink "AbstractTween/change:event"}}{{/crossLink}} event * @param {Function} [props.onComplete] Adds the specified function as a listener to the {{#crossLink "AbstractTween/complete:event"}}{{/crossLink}} event * @param {boolean} [props.override=false] Removes all existing tweens for the target when set to `true`. *
    * @extends AbstractTween * @constructor */ function Tween(target, props) { this.AbstractTween_constructor(props); // public properties: /** * Allows you to specify data that will be used by installed plugins. Each plugin uses this differently, but in general * you specify data by assigning it to a property of `pluginData` with the same name as the plugin. * Note that in many cases, this data is used as soon as the plugin initializes itself for the tween. * As such, this data should be set before the first `to` call in most cases. * @example * myTween.pluginData.SmartRotation = data; * * Most plugins also support a property to disable them for a specific tween. This is typically the plugin name followed by "_disabled". * @example * myTween.pluginData.SmartRotation_disabled = true; * * Some plugins also store working data in this object, usually in a property named `_PluginClassName`. * See the documentation for individual plugins for more details. * @property pluginData * @type {Object} */ this.pluginData = null; /** * The target of this tween. This is the object on which the tweened properties will be changed. * @property target * @type {Object} * @readonly */ this.target = target; /** * Indicates the tween's current position is within a passive wait. * @property passive * @type {Boolean} * @default false * @readonly **/ this.passive = false; // private properties: /** * @property _stepHead * @type {TweenStep} * @protected */ this._stepHead = new TweenStep(null, 0, 0, {}, null, true); /** * @property _stepTail * @type {TweenStep} * @protected */ this._stepTail = this._stepHead; /** * The position within the current step. Used by MovieClip. * @property _stepPosition * @type {Number} * @default 0 * @protected */ this._stepPosition = 0; /** * @property _actionHead * @type {TweenAction} * @protected */ this._actionHead = null; /** * @property _actionTail * @type {TweenAction} * @protected */ this._actionTail = null; /** * Plugins added to this tween instance. * @property _plugins * @type Array[Object] * @default null * @protected */ this._plugins = null; /** * Hash for quickly looking up added plugins. Null until a plugin is added. * @property _plugins * @type Object * @default null * @protected */ this._pluginIds = null; /** * Used by plugins to inject new properties. * @property _injected * @type {Object} * @default null * @protected */ this._injected = null; if (props) { this.pluginData = props.pluginData; if (props.override) { Tween.removeTweens(target); } } if (!this.pluginData) { this.pluginData = {}; } this._init(props); }; var p = createjs.extend(Tween, createjs.AbstractTween); // static properties /** * Constant returned by plugins to tell the tween not to use default assignment. * @property IGNORE * @type Object * @static */ Tween.IGNORE = {}; /** * @property _listeners * @type Array[Tween] * @static * @protected */ Tween._tweens = []; /** * @property _plugins * @type Object * @static * @protected */ Tween._plugins = null; /** * @property _tweenHead * @type Tween * @static * @protected */ Tween._tweenHead = null; /** * @property _tweenTail * @type Tween * @static * @protected */ Tween._tweenTail = null; // static methods /** * Returns a new tween instance. This is functionally identical to using `new Tween(...)`, but may look cleaner * with the chained syntax of TweenJS. *

    Example

    * * var tween = createjs.Tween.get(target).to({x:100}, 500); * // equivalent to: * var tween = new createjs.Tween(target).to({x:100}, 500); * * @method get * @param {Object} target The target object that will have its properties tweened. * @param {Object} [props] The configuration properties to apply to this instance (ex. `{loop:-1, paused:true}`). * Supported props are listed below. These props are set on the corresponding instance properties except where * specified. * @param {boolean} [props.useTicks=false] See the {{#crossLink "AbstractTween/useTicks:property"}}{{/crossLink}} property for more information. * @param {boolean} [props.ignoreGlobalPause=false] See the {{#crossLink "AbstractTween/ignoreGlobalPause:property"}}{{/crossLink}} for more information. * @param {number|boolean} [props.loop=0] See the {{#crossLink "AbstractTween/loop:property"}}{{/crossLink}} for more information. * @param {boolean} [props.reversed=false] See the {{#crossLink "AbstractTween/reversed:property"}}{{/crossLink}} for more information. * @param {boolean} [props.bounce=false] See the {{#crossLink "AbstractTween/bounce:property"}}{{/crossLink}} for more information. * @param {number} [props.timeScale=1] See the {{#crossLink "AbstractTween/timeScale:property"}}{{/crossLink}} for more information. * @param {object} [props.pluginData] See the {{#crossLink "Tween/pluginData:property"}}{{/crossLink}} for more information. * @param {boolean} [props.paused=false] See the {{#crossLink "AbstractTween/paused:property"}}{{/crossLink}} for more information. * @param {number} [props.position=0] The initial position for this tween. See {{#crossLink "AbstractTween/position:property"}}{{/crossLink}} * @param {Function} [props.onChange] Adds the specified function as a listener to the {{#crossLink "AbstractTween/change:event"}}{{/crossLink}} event * @param {Function} [props.onComplete] Adds the specified function as a listener to the {{#crossLink "AbstractTween/complete:event"}}{{/crossLink}} event * @param {boolean} [props.override=false] Removes all existing tweens for the target when set to `true`. * @return {Tween} A reference to the created tween. * @static */ Tween.get = function(target, props) { return new Tween(target, props); }; /** * Advances all tweens. This typically uses the {{#crossLink "Ticker"}}{{/crossLink}} class, but you can call it * manually if you prefer to use your own "heartbeat" implementation. * @method tick * @param {Number} delta The change in time in milliseconds since the last tick. Required unless all tweens have * `useTicks` set to true. * @param {Boolean} paused Indicates whether a global pause is in effect. Tweens with {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} * will ignore this, but all others will pause if this is `true`. * @static */ Tween.tick = function(delta, paused) { var tween = Tween._tweenHead; while (tween) { var next = tween._next; // in case it completes and wipes its _next property if ((paused && !tween.ignoreGlobalPause) || tween._paused) { /* paused */ } else { tween.advance(tween.useTicks?1:delta); } tween = next; } }; /** * Handle events that result from Tween being used as an event handler. This is included to allow Tween to handle * {{#crossLink "Ticker/tick:event"}}{{/crossLink}} events from the createjs {{#crossLink "Ticker"}}{{/crossLink}}. * No other events are handled in Tween. * @method handleEvent * @param {Object} event An event object passed in by the {{#crossLink "EventDispatcher"}}{{/crossLink}}. Will * usually be of type "tick". * @private * @static * @since 0.4.2 */ Tween.handleEvent = function(event) { if (event.type === "tick") { this.tick(event.delta, event.paused); } }; /** * Removes all existing tweens for a target. This is called automatically by new tweens if the `override` * property is `true`. * @method removeTweens * @param {Object} target The target object to remove existing tweens from. * @static */ Tween.removeTweens = function(target) { if (!target.tweenjs_count) { return; } var tween = Tween._tweenHead; while (tween) { var next = tween._next; if (tween.target === target) { Tween._register(tween, true); } tween = next; } target.tweenjs_count = 0; }; /** * Stop and remove all existing tweens. * @method removeAllTweens * @static * @since 0.4.1 */ Tween.removeAllTweens = function() { var tween = Tween._tweenHead; while (tween) { var next = tween._next; tween._paused = true; tween.target&&(tween.target.tweenjs_count = 0); tween._next = tween._prev = null; tween = next; } Tween._tweenHead = Tween._tweenTail = null; }; /** * Indicates whether there are any active tweens on the target object (if specified) or in general. * @method hasActiveTweens * @param {Object} [target] The target to check for active tweens. If not specified, the return value will indicate * if there are any active tweens on any target. * @return {Boolean} Indicates if there are active tweens. * @static */ Tween.hasActiveTweens = function(target) { if (target) { return !!target.tweenjs_count; } return !!Tween._tweenHead; }; /** * Installs a plugin, which can modify how certain properties are handled when tweened. See the {{#crossLink "SamplePlugin"}}{{/crossLink}} * for an example of how to write TweenJS plugins. Plugins should generally be installed via their own `install` method, in order to provide * the plugin with an opportunity to configure itself. * @method _installPlugin * @param {Object} plugin The plugin to install * @static * @protected */ Tween._installPlugin = function(plugin) { var priority = (plugin.priority = plugin.priority||0), arr = (Tween._plugins = Tween._plugins || []); for (var i=0,l=arr.length;iExample * * //This tween will wait 1s before alpha is faded to 0. * createjs.Tween.get(target).wait(1000).to({alpha:0}, 1000); * * @method wait * @param {Number} duration The duration of the wait in milliseconds (or in ticks if `useTicks` is true). * @param {Boolean} [passive=false] Tween properties will not be updated during a passive wait. This * is mostly useful for use with {{#crossLink "Timeline"}}{{/crossLink}} instances that contain multiple tweens * affecting the same target at different times. * @return {Tween} This tween instance (for chaining calls). * @chainable **/ p.wait = function(duration, passive) { if (duration > 0) { this._addStep(+duration, this._stepTail.props, null, passive); } return this; }; /** * Adds a tween from the current values to the specified properties. Set duration to 0 to jump to these value. * Numeric properties will be tweened from their current value in the tween to the target value. Non-numeric * properties will be set at the end of the specified duration. *

    Example

    * * createjs.Tween.get(target).to({alpha:0, visible:false}, 1000); * * @method to * @param {Object} props An object specifying property target values for this tween (Ex. `{x:300}` would tween the x * property of the target to 300). * @param {Number} [duration=0] The duration of the tween in milliseconds (or in ticks if `useTicks` is true). * @param {Function} [ease="linear"] The easing function to use for this tween. See the {{#crossLink "Ease"}}{{/crossLink}} * class for a list of built-in ease functions. * @return {Tween} This tween instance (for chaining calls). * @chainable */ p.to = function(props, duration, ease) { if (duration == null || duration < 0) { duration = 0; } var step = this._addStep(+duration, null, ease); this._appendProps(props, step); return this; }; /** * Adds a label that can be used with {{#crossLink "Tween/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Tween/gotoAndStop"}}{{/crossLink}} * at the current point in the tween. For example: * * var tween = createjs.Tween.get(foo) * .to({x:100}, 1000) * .label("myLabel") * .to({x:200}, 1000); * // ... * tween.gotoAndPlay("myLabel"); // would play from 1000ms in. * * @method label * @param {String} label The label name. * @return {Tween} This tween instance (for chaining calls). * @chainable **/ p.label = function(name) { this.addLabel(name, this.duration); return this; }; /** * Adds an action to call the specified function. *

    Example

    * * //would call myFunction() after 1 second. * createjs.Tween.get().wait(1000).call(myFunction); * * @method call * @param {Function} callback The function to call. * @param {Array} [params]. The parameters to call the function with. If this is omitted, then the function * will be called with a single param pointing to this tween. * @param {Object} [scope]. The scope to call the function in. If omitted, it will be called in the target's scope. * @return {Tween} This tween instance (for chaining calls). * @chainable */ p.call = function(callback, params, scope) { return this._addAction(scope||this.target, callback, params||[this]); }; /** * Adds an action to set the specified props on the specified target. If `target` is null, it will use this tween's * target. Note that for properties on the target object, you should consider using a zero duration {{#crossLink "Tween/to"}}{{/crossLink}} * operation instead so the values are registered as tweened props. *

    Example

    * * myTween.wait(1000).set({visible:false}, foo); * * @method set * @param {Object} props The properties to set (ex. `{visible:false}`). * @param {Object} [target] The target to set the properties on. If omitted, they will be set on the tween's target. * @return {Tween} This tween instance (for chaining calls). * @chainable */ p.set = function(props, target) { return this._addAction(target||this.target, this._set, [props]); }; /** * Adds an action to play (unpause) the specified tween. This enables you to sequence multiple tweens. *

    Example

    * * myTween.to({x:100}, 500).play(otherTween); * * @method play * @param {Tween} [tween] The tween to play. Defaults to this tween. * @return {Tween} This tween instance (for chaining calls). * @chainable */ p.play = function(tween) { return this._addAction(tween||this, this._set, [{paused:false}]); }; /** * Adds an action to pause the specified tween. * * myTween.pause(otherTween).to({alpha:1}, 1000).play(otherTween); * * Note that this executes at the end of a tween update, so the tween may advance beyond the time the pause * action was inserted at. For example: * * myTween.to({foo:0}, 1000).pause().to({foo:1}, 1000); * * At 60fps the tween will advance by ~16ms per tick, if the tween above was at 999ms prior to the current tick, it * will advance to 1015ms (15ms into the second "step") and then pause. * * @method pause * @param {Tween} [tween] The tween to pause. Defaults to this tween. * @return {Tween} This tween instance (for chaining calls) * @chainable */ p.pause = function(tween) { return this._addAction(tween||this, this._set, [{paused:true}]); }; // tiny api (primarily for tool output): p.w = p.wait; p.t = p.to; p.c = p.call; p.s = p.set; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. */ p.toString = function() { return "[Tween]"; }; /** * @method clone * @protected */ p.clone = function() { throw("Tween can not be cloned.") }; // private methods: /** * Adds a plugin to this tween. * @method _addPlugin * @param {Object} plugin * @protected */ p._addPlugin = function(plugin) { var ids = this._pluginIds || (this._pluginIds = {}), id = plugin.ID; if (!id || ids[id]) { return; } // already added ids[id] = true; var plugins = this._plugins || (this._plugins = []), priority = plugin.priority || 0; for (var i=0,l=plugins.length; i= 1 ? v1 : v0; } if (plugins) { for (var i=0,l=plugins.length;i endPos; var action = rev ? this._actionTail : this._actionHead; var ePos = endPos, sPos = startPos; if (rev) { ePos=startPos; sPos=endPos; } var t = this.position; while (action) { var pos = action.t; if (pos === endPos || (pos > sPos && pos < ePos) || (includeStart && pos === startPos)) { action.funct.apply(action.scope, action.params); if (t !== this.position) { return true; } } action = rev ? action.prev : action.next; } }; /** * @method _appendProps * @param {Object} props * @protected */ p._appendProps = function(props, step, stepPlugins) { var initProps = this._stepHead.props, target = this.target, plugins = Tween._plugins; var n, i, value, initValue, inject; var oldStep = step.prev, oldProps = oldStep.props; var stepProps = step.props || (step.props = this._cloneProps(oldProps)); var cleanProps = {}; // TODO: is there some way to avoid this additional object? for (n in props) { if (!props.hasOwnProperty(n)) { continue; } cleanProps[n] = stepProps[n] = props[n]; if (initProps[n] !== undefined) { continue; } initValue = undefined; // accessing missing properties on DOMElements when using CSSPlugin is INSANELY expensive, so we let the plugin take a first swing at it. if (plugins) { for (i = plugins.length-1; i >= 0; i--) { value = plugins[i].init(this, n, initValue); if (value !== undefined) { initValue = value; } if (initValue === Tween.IGNORE) { delete(stepProps[n]); delete(cleanProps[n]); break; } } } if (initValue !== Tween.IGNORE) { if (initValue === undefined) { initValue = target[n]; } oldProps[n] = (initValue === undefined) ? null : initValue; } } for (n in cleanProps) { value = props[n]; // propagate old value to previous steps: var o, prev=oldStep; while ((o = prev) && (prev = o.prev)) { if (prev.props === o.props) { continue; } // wait step if (prev.props[n] !== undefined) { break; } // already has a value, we're done. prev.props[n] = oldProps[n]; } } if (stepPlugins !== false && (plugins = this._plugins)) { for (i = plugins.length-1; i >= 0; i--) { plugins[i].step(this, step, cleanProps); } } if (inject = this._injected) { this._injected = null; this._appendProps(inject, step, false); } }; /** * Used by plugins to inject properties onto the current step. Called from within `Plugin.step` calls. * For example, a plugin dealing with color, could read a hex color, and inject red, green, and blue props into the tween. * See the SamplePlugin for more info. * @method _injectProp * @param {String} name * @param {Object} value * @protected */ p._injectProp = function(name, value) { var o = this._injected || (this._injected = {}); o[name] = value; }; /** * @method _addStep * @param {Number} duration * @param {Object} props * @param {Function} ease * @param {Boolean} passive * @protected */ p._addStep = function(duration, props, ease, passive) { var step = new TweenStep(this._stepTail, this.duration, duration, props, ease, passive||false); this.duration += duration; return this._stepTail = (this._stepTail.next = step); }; /** * @method _addAction * @param {Object} scope * @param {Function} funct * @param {Array} params * @protected */ p._addAction = function(scope, funct, params) { var action = new TweenAction(this._actionTail, this.duration, scope, funct, params); if (this._actionTail) { this._actionTail.next = action; } else { this._actionHead = action; } this._actionTail = action; return this; }; /** * @method _set * @param {Object} props * @protected */ p._set = function(props) { for (var n in props) { this[n] = props[n]; } }; /** * @method _cloneProps * @param {Object} props * @protected */ p._cloneProps = function(props) { var o = {}; for (var n in props) { o[n] = props[n]; } return o; }; createjs.Tween = createjs.promote(Tween, "AbstractTween"); function TweenStep(prev, t, d, props, ease, passive) { this.next = null; this.prev = prev; this.t = t; this.d = d; this.props = props; this.ease = ease; this.passive = passive; this.index = prev ? prev.index+1 : 0; }; function TweenAction(prev, t, scope, funct, params) { this.next = null; this.prev = prev; this.t = t; this.d = 0; this.scope = scope; this.funct = funct; this.params = params; }; }()); //############################################################################## // Timeline.js //############################################################################## (function() { "use strict"; // constructor /** * The Timeline class synchronizes multiple tweens and allows them to be controlled as a group. Please note that if a * timeline is looping, the tweens on it may appear to loop even if the "loop" property of the tween is false. * * NOTE: Timeline currently also accepts a param list in the form: `tweens, labels, props`. This is for backwards * compatibility only and will be removed in the future. Include tweens and labels as properties on the props object. * @class Timeline * @param {Object} [props] The configuration properties to apply to this instance (ex. `{loop:-1, paused:true}`). * Supported props are listed below. These props are set on the corresponding instance properties except where * specified.
      *
    • `useTicks`
    • *
    • `ignoreGlobalPause`
    • *
    • `loop`
    • *
    • `reversed`
    • *
    • `bounce`
    • *
    • `timeScale`
    • *
    • `paused`
    • *
    • `position`: indicates the initial position for this tween.
    • *
    • `onChange`: adds the specified function as a listener to the `change` event
    • *
    • `onComplete`: adds the specified function as a listener to the `complete` event
    • *
    * @extends AbstractTween * @constructor **/ function Timeline(props) { var tweens, labels; // handle old params (tweens, labels, props): // TODO: deprecated. if (props instanceof Array || (props == null && arguments.length > 1)) { tweens = props; labels = arguments[1]; props = arguments[2]; } else if (props) { tweens = props.tweens; labels = props.labels; } this.AbstractTween_constructor(props); // private properties: /** * The array of tweens in the timeline. It is *strongly* recommended that you use * {{#crossLink "Tween/addTween"}}{{/crossLink}} and {{#crossLink "Tween/removeTween"}}{{/crossLink}}, * rather than accessing this directly, but it is included for advanced uses. * @property tweens * @type Array **/ this.tweens = []; if (tweens) { this.addTween.apply(this, tweens); } this.setLabels(labels); this._init(props); }; var p = createjs.extend(Timeline, createjs.AbstractTween); // events: // docced in AbstractTween. // public methods: /** * Adds one or more tweens (or timelines) to this timeline. The tweens will be paused (to remove them from the * normal ticking system) and managed by this timeline. Adding a tween to multiple timelines will result in * unexpected behaviour. * @method addTween * @param {Tween} ...tween The tween(s) to add. Accepts multiple arguments. * @return {Tween} The first tween that was passed in. **/ p.addTween = function(tween) { if (tween._parent) { tween._parent.removeTween(tween); } var l = arguments.length; if (l > 1) { for (var i=0; i 0) { d *= tween.loop+1; } if (d > this.duration) { this.duration = d; } if (this.rawPosition >= 0) { tween.setPosition(this.rawPosition); } return tween; }; /** * Removes one or more tweens from this timeline. * @method removeTween * @param {Tween} ...tween The tween(s) to remove. Accepts multiple arguments. * @return Boolean Returns `true` if all of the tweens were successfully removed. **/ p.removeTween = function(tween) { var l = arguments.length; if (l > 1) { var good = true; for (var i=0; i= this.duration) { this.updateDuration(); } return true; } } return false; }; /** * Recalculates the duration of the timeline. The duration is automatically updated when tweens are added or removed, * but this method is useful if you modify a tween after it was added to the timeline. * @method updateDuration **/ p.updateDuration = function() { this.duration = 0; for (var i=0,l=this.tweens.length; i 0) { d *= tween.loop+1; } if (d > this.duration) { this.duration = d; } } }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Timeline]"; }; /** * @method clone * @protected **/ p.clone = function() { throw("Timeline can not be cloned.") }; // private methods: // Docced in AbstractTween p._updatePosition = function(jump, end) { var t = this.position; for (var i=0, l=this.tweens.length; ispark table demo for an * overview of the different ease types on TweenJS.com. * * Equations derived from work by Robert Penner. * @class Ease * @static **/ function Ease() { throw "Ease cannot be instantiated."; } // static methods and properties /** * @method linear * @param {Number} t * @static * @return {Number} **/ Ease.linear = function(t) { return t; }; /** * Identical to linear. * @method none * @param {Number} t * @static * @return {Number} **/ Ease.none = Ease.linear; /** * Mimics the simple -100 to 100 easing in Adobe Flash/Animate. * @method get * @param {Number} amount A value from -1 (ease in) to 1 (ease out) indicating the strength and direction of the ease. * @static * @return {Function} **/ Ease.get = function(amount) { if (amount < -1) { amount = -1; } else if (amount > 1) { amount = 1; } return function(t) { if (amount==0) { return t; } if (amount<0) { return t*(t*-amount+1+amount); } return t*((2-t)*amount+(1-amount)); }; }; /** * Configurable exponential ease. * @method getPowIn * @param {Number} pow The exponent to use (ex. 3 would return a cubic ease). * @static * @return {Function} **/ Ease.getPowIn = function(pow) { return function(t) { return Math.pow(t,pow); }; }; /** * Configurable exponential ease. * @method getPowOut * @param {Number} pow The exponent to use (ex. 3 would return a cubic ease). * @static * @return {Function} **/ Ease.getPowOut = function(pow) { return function(t) { return 1-Math.pow(1-t,pow); }; }; /** * Configurable exponential ease. * @method getPowInOut * @param {Number} pow The exponent to use (ex. 3 would return a cubic ease). * @static * @return {Function} **/ Ease.getPowInOut = function(pow) { return function(t) { if ((t*=2)<1) return 0.5*Math.pow(t,pow); return 1-0.5*Math.abs(Math.pow(2-t,pow)); }; }; /** * @method quadIn * @param {Number} t * @static * @return {Number} **/ Ease.quadIn = Ease.getPowIn(2); /** * @method quadOut * @param {Number} t * @static * @return {Number} **/ Ease.quadOut = Ease.getPowOut(2); /** * @method quadInOut * @param {Number} t * @static * @return {Number} **/ Ease.quadInOut = Ease.getPowInOut(2); /** * @method cubicIn * @param {Number} t * @static * @return {Number} **/ Ease.cubicIn = Ease.getPowIn(3); /** * @method cubicOut * @param {Number} t * @static * @return {Number} **/ Ease.cubicOut = Ease.getPowOut(3); /** * @method cubicInOut * @param {Number} t * @static * @return {Number} **/ Ease.cubicInOut = Ease.getPowInOut(3); /** * @method quartIn * @param {Number} t * @static * @return {Number} **/ Ease.quartIn = Ease.getPowIn(4); /** * @method quartOut * @param {Number} t * @static * @return {Number} **/ Ease.quartOut = Ease.getPowOut(4); /** * @method quartInOut * @param {Number} t * @static * @return {Number} **/ Ease.quartInOut = Ease.getPowInOut(4); /** * @method quintIn * @param {Number} t * @static * @return {Number} **/ Ease.quintIn = Ease.getPowIn(5); /** * @method quintOut * @param {Number} t * @static * @return {Number} **/ Ease.quintOut = Ease.getPowOut(5); /** * @method quintInOut * @param {Number} t * @static * @return {Number} **/ Ease.quintInOut = Ease.getPowInOut(5); /** * @method sineIn * @param {Number} t * @static * @return {Number} **/ Ease.sineIn = function(t) { return 1-Math.cos(t*Math.PI/2); }; /** * @method sineOut * @param {Number} t * @static * @return {Number} **/ Ease.sineOut = function(t) { return Math.sin(t*Math.PI/2); }; /** * @method sineInOut * @param {Number} t * @static * @return {Number} **/ Ease.sineInOut = function(t) { return -0.5*(Math.cos(Math.PI*t) - 1); }; /** * Configurable "back in" ease. * @method getBackIn * @param {Number} amount The strength of the ease. * @static * @return {Function} **/ Ease.getBackIn = function(amount) { return function(t) { return t*t*((amount+1)*t-amount); }; }; /** * @method backIn * @param {Number} t * @static * @return {Number} **/ Ease.backIn = Ease.getBackIn(1.7); /** * Configurable "back out" ease. * @method getBackOut * @param {Number} amount The strength of the ease. * @static * @return {Function} **/ Ease.getBackOut = function(amount) { return function(t) { return (--t*t*((amount+1)*t + amount) + 1); }; }; /** * @method backOut * @param {Number} t * @static * @return {Number} **/ Ease.backOut = Ease.getBackOut(1.7); /** * Configurable "back in out" ease. * @method getBackInOut * @param {Number} amount The strength of the ease. * @static * @return {Function} **/ Ease.getBackInOut = function(amount) { amount*=1.525; return function(t) { if ((t*=2)<1) return 0.5*(t*t*((amount+1)*t-amount)); return 0.5*((t-=2)*t*((amount+1)*t+amount)+2); }; }; /** * @method backInOut * @param {Number} t * @static * @return {Number} **/ Ease.backInOut = Ease.getBackInOut(1.7); /** * @method circIn * @param {Number} t * @static * @return {Number} **/ Ease.circIn = function(t) { return -(Math.sqrt(1-t*t)- 1); }; /** * @method circOut * @param {Number} t * @static * @return {Number} **/ Ease.circOut = function(t) { return Math.sqrt(1-(--t)*t); }; /** * @method circInOut * @param {Number} t * @static * @return {Number} **/ Ease.circInOut = function(t) { if ((t*=2) < 1) return -0.5*(Math.sqrt(1-t*t)-1); return 0.5*(Math.sqrt(1-(t-=2)*t)+1); }; /** * @method bounceIn * @param {Number} t * @static * @return {Number} **/ Ease.bounceIn = function(t) { return 1-Ease.bounceOut(1-t); }; /** * @method bounceOut * @param {Number} t * @static * @return {Number} **/ Ease.bounceOut = function(t) { if (t < 1/2.75) { return (7.5625*t*t); } else if (t < 2/2.75) { return (7.5625*(t-=1.5/2.75)*t+0.75); } else if (t < 2.5/2.75) { return (7.5625*(t-=2.25/2.75)*t+0.9375); } else { return (7.5625*(t-=2.625/2.75)*t +0.984375); } }; /** * @method bounceInOut * @param {Number} t * @static * @return {Number} **/ Ease.bounceInOut = function(t) { if (t<0.5) return Ease.bounceIn (t*2) * .5; return Ease.bounceOut(t*2-1)*0.5+0.5; }; /** * Configurable elastic ease. * @method getElasticIn * @param {Number} amplitude * @param {Number} period * @static * @return {Function} **/ Ease.getElasticIn = function(amplitude,period) { var pi2 = Math.PI*2; return function(t) { if (t==0 || t==1) return t; var s = period/pi2*Math.asin(1/amplitude); return -(amplitude*Math.pow(2,10*(t-=1))*Math.sin((t-s)*pi2/period)); }; }; /** * @method elasticIn * @param {Number} t * @static * @return {Number} **/ Ease.elasticIn = Ease.getElasticIn(1,0.3); /** * Configurable elastic ease. * @method getElasticOut * @param {Number} amplitude * @param {Number} period * @static * @return {Function} **/ Ease.getElasticOut = function(amplitude,period) { var pi2 = Math.PI*2; return function(t) { if (t==0 || t==1) return t; var s = period/pi2 * Math.asin(1/amplitude); return (amplitude*Math.pow(2,-10*t)*Math.sin((t-s)*pi2/period )+1); }; }; /** * @method elasticOut * @param {Number} t * @static * @return {Number} **/ Ease.elasticOut = Ease.getElasticOut(1,0.3); /** * Configurable elastic ease. * @method getElasticInOut * @param {Number} amplitude * @param {Number} period * @static * @return {Function} **/ Ease.getElasticInOut = function(amplitude,period) { var pi2 = Math.PI*2; return function(t) { var s = period/pi2 * Math.asin(1/amplitude); if ((t*=2)<1) return -0.5*(amplitude*Math.pow(2,10*(t-=1))*Math.sin( (t-s)*pi2/period )); return amplitude*Math.pow(2,-10*(t-=1))*Math.sin((t-s)*pi2/period)*0.5+1; }; }; /** * @method elasticInOut * @param {Number} t * @static * @return {Number} **/ Ease.elasticInOut = Ease.getElasticInOut(1,0.3*1.5); createjs.Ease = Ease; }()); //############################################################################## // MotionGuidePlugin.js //############################################################################## (function() { "use strict"; /** * A TweenJS plugin for working with motion guides. Defined paths which objects can follow or orient along. * * To use the plugin, install the plugin after TweenJS has loaded. To define a path, add * * createjs.MotionGuidePlugin.install(); * *

    Example

    * * // Using a Motion Guide * createjs.Tween.get(target).to({guide:{ path:[0,0, 0,200,200,200, 200,0,0,0] }},7000); * // Visualizing the line * graphics.moveTo(0,0).curveTo(0,200,200,200).curveTo(200,0,0,0); * * Each path needs pre-computation to ensure there's fast performance. Because of the pre-computation there's no * built in support for path changes mid tween. These are the Guide Object's properties:
      *
    • path: Required, Array : The x/y points used to draw the path with a moveTo and 1 to n curveTo calls.
    • *
    • start: Optional, 0-1 : Initial position, default 0 except for when continuing along the same path.
    • *
    • end: Optional, 0-1 : Final position, default 1 if not specified.
    • *
    • orient: Optional, string : "fixed"/"auto"/"cw"/"ccw"
        *
      • "fixed" forces the object to face down the path all movement (relative to start rotation),
      • *
      • "auto" rotates the object along the path relative to the line.
      • *
      • "cw"/"ccw" force clockwise or counter clockwise rotations including Adobe Flash/Animate-like * behaviour. This may override your end rotation value.
      • *
    • *
    * Guide objects should not be shared between tweens even if all properties are identical, the library stores * information on these objects in the background and sharing them can cause unexpected behaviour. Values * outside 0-1 range of tweens will be a "best guess" from the appropriate part of the defined curve. * * @class MotionGuidePlugin * @constructor */ function MotionGuidePlugin() { throw("MotionGuidePlugin cannot be instantiated.") } var s = MotionGuidePlugin; // static properties: /** * @property priority * @protected * @static */ s.priority = 0; // high priority, should run sooner /** * READ-ONLY. A unique identifying string for this plugin. Used by TweenJS to ensure duplicate plugins are not installed on a tween. * @property ID * @type {String} * @static * @readonly */ s.ID = "MotionGuide"; // static methods /** * Installs this plugin for use with TweenJS. Call this once after TweenJS is loaded to enable this plugin. * @method install * @static */ s.install = function() { createjs.Tween._installPlugin(MotionGuidePlugin); return createjs.Tween.IGNORE; }; /** * Called by TweenJS when a new property initializes on a tween. * See {{#crossLink "SamplePlugin/init"}}{{/crossLink}} for more info. * @method init * @param {Tween} tween * @param {String} prop * @param {any} value * @return {any} * @static */ s.init = function(tween, prop, value) { if(prop == "guide") { tween._addPlugin(s); } }; /** * Called when a new step is added to a tween (ie. a new "to" action is added to a tween). * See {{#crossLink "SamplePlugin/step"}}{{/crossLink}} for more info. * @method step * @param {Tween} tween * @param {TweenStep} step * @param {Object} props * @static */ s.step = function(tween, step, props) { for (var n in props) { if(n !== "guide") { continue; } var guideData = step.props.guide; var error = s._solveGuideData(props.guide, guideData); guideData.valid = !error; var end = guideData.endData; tween._injectProp("x", end.x); tween._injectProp("y", end.y); if(error || !guideData.orient) { break; } var initRot = step.prev.props.rotation === undefined ? (tween.target.rotation || 0) : step.prev.props.rotation; guideData.startOffsetRot = initRot - guideData.startData.rotation; if(guideData.orient == "fixed") { // controlled rotation guideData.endAbsRot = end.rotation + guideData.startOffsetRot; guideData.deltaRotation = 0; } else { // interpreted rotation var finalRot = props.rotation === undefined ? (tween.target.rotation || 0) : props.rotation; var deltaRot = (finalRot - guideData.endData.rotation) - guideData.startOffsetRot; var modRot = deltaRot % 360; guideData.endAbsRot = finalRot; switch(guideData.orient) { case "auto": guideData.deltaRotation = deltaRot; break; case "cw": guideData.deltaRotation = ((modRot + 360) % 360) + (360 * Math.abs((deltaRot/360) |0)); break; case "ccw": guideData.deltaRotation = ((modRot - 360) % 360) + (-360 * Math.abs((deltaRot/360) |0)); break; } } tween._injectProp("rotation", guideData.endAbsRot); } }; /** * Called before a property is updated by the tween. * See {{#crossLink "SamplePlugin/change"}}{{/crossLink}} for more info. * @method change * @param {Tween} tween * @param {TweenStep} step * @param {String} prop * @param {any} value * @param {Number} ratio * @param {Boolean} end * @return {any} * @static */ s.change = function(tween, step, prop, value, ratio, end) { var guideData = step.props.guide; if( !guideData || // Missing data (step.props === step.prev.props) || // In a wait() (guideData === step.prev.props.guide) // Guide hasn't changed ) { return; // have no business making decisions } if( (prop === "guide" && !guideData.valid) || // this data is broken (prop == "x" || prop == "y") || // these always get over-written (prop === "rotation" && guideData.orient) // currently over-written ){ return createjs.Tween.IGNORE; } s._ratioToPositionData(ratio, guideData, tween.target); }; // public methods /** * Provide potentially useful debugging information, like running the error detection system, and rendering the path * defined in the guide data. * * NOTE: you will need to transform your context 2D to the local space of the guide if you wish to line it up. * @param {Object} guideData All the information describing the guide to be followed. * @param {DrawingContext2D} [ctx=undefined] The context to draw the object into. * @param {Array} [higlight=undefined] Array of ratio positions to highlight * @returns {undefined|String} */ s.debug = function(guideData, ctx, higlight) { guideData = guideData.guide || guideData; // errors var err = s._findPathProblems(guideData); if(err) { console.error("MotionGuidePlugin Error found: \n" + err); } // drawing if(!ctx){ return err; } var i; var path = guideData.path; var pathLength = path.length; var width = 3; var length = 9; ctx.save(); //ctx.resetTransform(); ctx.lineCap = "round"; ctx.lineJoin = "miter"; ctx.beginPath(); // curve ctx.moveTo(path[0], path[1]); for(i=2; i < pathLength; i+=4) { ctx.quadraticCurveTo( path[i], path[i+1], path[i+2], path[i+3] ); } ctx.strokeStyle = "black"; ctx.lineWidth = width*1.5; ctx.stroke(); ctx.strokeStyle = "white"; ctx.lineWidth = width; ctx.stroke(); ctx.closePath(); // highlights var hiCount = higlight.length; if(higlight && hiCount) { var tempStore = {}; var tempLook = {}; s._solveGuideData(guideData, tempStore); for(var i=0; i= effRatio){ target = i; break; } look += test; } if(target === undefined) { target = l-1; look -= test; } // find midline weighting var subLines = lineSegments[target].weightings; var portion = test; l = subLines.length; for(i=0; i= effRatio){ break; } look += test; } // translate the subline index into a position in the path data target = (target*4) + 2; // take the distance we've covered in our ratio, and scale it to distance into the weightings t = (i/precision) + (((effRatio-look) / test) * (1/precision)); // position var pathData = guideData.path; s._getParamsForCurve( pathData[target-2], pathData[target-1], pathData[target], pathData[target+1], pathData[target+2], pathData[target+3], t, guideData.orient, output ); if(guideData.orient) { if(ratio >= 0.99999 && ratio <= 1.00001 && guideData.endAbsRot !== undefined) { output.rotation = guideData.endAbsRot; } else { output.rotation += guideData.startOffsetRot + (ratio * guideData.deltaRotation); } } return output; }; /** * For a given quadratic bezier t-value, what is the position and rotation. Save it onto the output object. * @param {Number} sx Start x. * @param {Number} sy Start y. * @param {Number} cx Control x. * @param {Number} cy Control y. * @param {Number} ex End x. * @param {Number} ey End y. * @param {Number} t T value (parametric distance into curve). * @param {Boolean} orient Save rotation data. * @param {Object} output Object to save output properties of x,y, and rotation onto. * @private */ s._getParamsForCurve = function(sx,sy, cx,cy, ex,ey, t, orient, output) { var inv = 1 - t; // finding a point on a bezier curve output.x = inv*inv * sx + 2 * inv * t * cx + t*t * ex; output.y = inv*inv * sy + 2 * inv * t * cy + t*t * ey; // finding an angle on a bezier curve if(orient) { // convert from radians back to degrees output.rotation = 57.2957795 * Math.atan2( (cy - sy)*inv + (ey - cy)*t, (cx - sx)*inv + (ex - cx)*t ); } }; /** * Perform a check to validate path information so plugin can avoid later error checking. * @param {Object} guideData All the information describing the guide to be followed. * @returns {undefined|String} The problem found, or undefined if no problems. * @private */ s._findPathProblems = function(guideData) { var path = guideData.path; var valueCount = (path && path.length) || 0; // ensure this is a number to simplify later logic if(valueCount < 6 || (valueCount-2) % 4) { var message = "\tCannot parse 'path' array due to invalid number of entries in path. "; message += "There should be an odd number of points, at least 3 points, and 2 entries per point (x & y). "; message += "See 'CanvasRenderingContext2D.quadraticCurveTo' for details as 'path' models a quadratic bezier.\n\n"; message += "Only [ "+ valueCount +" ] values found. Expected: "+ Math.max(Math.ceil((valueCount-2)/4)*4+2, 6); //6, 10, 14,... return message; } for(var i=0; i 1*/) { // outside 0-1 is unpredictable, but not breaking return "'start' out of bounds. Expected 0 to 1, got: "+ start; } var end = guideData.end; if(isNaN(end) && (end !== undefined)/* || end < 0 || end > 1*/) { // outside 0-1 is unpredictable, but not breaking return "'end' out of bounds. Expected 0 to 1, got: "+ end; } var orient = guideData.orient; if(orient) { // mirror the check used elsewhere if(orient != "fixed" && orient != "auto" && orient != "cw" && orient != "ccw") { return 'Invalid orientation value. Expected ["fixed", "auto", "cw", "ccw", undefined], got: '+ orient; } } return undefined; }; createjs.MotionGuidePlugin = MotionGuidePlugin; }()); //############################################################################## // version.js //############################################################################## (function() { "use strict"; /** * Static class holding library specific information such as the version and buildDate of * the library. * @class TweenJS **/ var s = createjs.TweenJS = createjs.TweenJS || {}; /** * The version string for this release. * @property version * @type String * @static **/ s.version = /*=version*/"1.0.0"; // injected by build process /** * The build date for this release in UTC format. * @property buildDate * @type String * @static **/ s.buildDate = /*=date*/"Thu, 12 Oct 2017 16:34:05 GMT"; // injected by build process })();