/*!
* 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:
* - capture phase: starting from the top parent to the target
* - at target phase: currently being dispatched from the target
* - bubbling phase: from the target to the top parent
*
* @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:
*
* - The source image or images to use.
* - The positions of individual image frames.
* - Sequences of frames that form named animations. Optional.
* - The target playback framerate. Optional.
*
* 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:
*
* - for a single frame animation, you can simply specify the frame index
*
* animations: {
* sit: 7
* }
*
*
* -
* 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]
* }
*
*
* -
* 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
* }
* }
*
*
*
* 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.
*
*
* Tiny | Method | Tiny | Method |
* 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:
* - when the mouse enters shapeA (target=shapeA)
* - when the mouse enters shapeB (target=shapeB)
*
* 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:
* - when the mouse leaves shapeA (target=shapeA)
* - when the mouse leaves shapeB (target=shapeB)
*
* 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:
* - The top-most (first) stage must have mouse over interactions enabled (via enableMouseOver)
* - All stages that wish to participate in mouse over interaction must enable them via enableMouseOver
* - All relay targets will share the frequency value of the top-most stage
*
* 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:
*
* - When using a video source that may loop or seek, use a {{#crossLink "VideoBuffer"}}{{/crossLink}} object to
* prevent blinking / flashing.
*
- 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.
* - 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.
* - 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.
* - 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";`
*
*
* @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;
}
};
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.
* - Some browsers get an
XDomainRequest
when loading cross-domain.
* - XMLHttpRequest are created when available.
* - ActiveX.XMLHTTP objects are used in older IE browsers.
* - Text requests override the mime type if possible
* - Origin headers are sent for crossdomain requests in some browsers.
* - 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:
*
* - 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.
* - 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.
* - 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.
* - An Array of files to load.
*
*
* 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:
*
* - Set the `allowDefault` property on the Touch class constructor to `true` (defaults to `false`).
* - Set the `preventSelection` property on the EaselJS `Stage` to `false`.
*
* 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
})();