transition-native.js revision ef982fe144b380cd6cd8dd1bc6ba74c7210033f9
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews/**
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User* Provides the transition method for Node.
bef75d63d74f58abc0f834ed271526672777ba29Automatic Updater* Transition has no API of its own, but adds the transition method to Node.
ca67ebfe9eef0b8f04179f7e511a19e0337a5422Automatic Updater*
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews* @module transition
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews* @requires node
bef75d63d74f58abc0f834ed271526672777ba29Automatic Updater*/
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrewsvar TRANSITION = '-webkit-transition',
bef75d63d74f58abc0f834ed271526672777ba29Automatic Updater TRANSITION_PROPERTY_CAMEL = 'WebkitTransition',
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews TRANSITION_PROPERTY = '-webkit-transition-property',
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews TRANSITION_DURATION = '-webkit-transition-duration',
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews TRANSITION_TIMING_FUNCTION = '-webkit-transition-timing-function',
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews TRANSITION_DELAY = '-webkit-transition-delay',
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews TRANSITION_END = 'webkitTransitionEnd',
ea94d370123a5892f6c47a97f21d1b28d44bb168Tinderbox User TRANSFORM_CAMEL = 'WebkitTransform',
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews EMPTY_OBJ = {},
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews/**
ca67ebfe9eef0b8f04179f7e511a19e0337a5422Automatic Updater * A class for constructing transition instances.
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews * Adds the "transition" method to Node.
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews * @class Transition
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews * @constructor
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews */
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark AndrewsTransition = function() {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews this.init.apply(this, arguments);
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews};
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
e839bf134fb138920d4833cf05cb8b8906787a8dAutomatic UpdaterTransition._toCamel = function(property) {
e839bf134fb138920d4833cf05cb8b8906787a8dAutomatic Updater property = property.replace(/-([a-z])/gi, function(m0, m1) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews return m1.toUpperCase();
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews });
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User
922312472e2e05ebc64993d465999c5351b83036Automatic Updater return property;
b27ce68bae92006e2ad7a9b75602c6385e529c3bAutomatic Updater};
922312472e2e05ebc64993d465999c5351b83036Automatic Updater
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark AndrewsTransition._toHyphen = function(property) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews property = property.replace(/([A-Z]?)([a-z]+)([A-Z]?)/g, function(m0, m1, m2, m3) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews var str = '';
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User if (m1) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews str += '-' + m1.toLowerCase();
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews str += m2;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews if (m3) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews str += '-' + m3.toLowerCase();
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews return str;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews });
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews return property;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews};
0e9e255d1643375056aa9ed7fe2a279713ffae78Automatic Updater
aaaf8d4f4873d21e55c3ffb4f656203d08339865Mark Andrews
aaaf8d4f4873d21e55c3ffb4f656203d08339865Mark AndrewsTransition._reKeywords = /^(?:node|duration|iterations|easing|delay)$/;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
e839bf134fb138920d4833cf05cb8b8906787a8dAutomatic UpdaterTransition.useNative = false;
e839bf134fb138920d4833cf05cb8b8906787a8dAutomatic Updater
e839bf134fb138920d4833cf05cb8b8906787a8dAutomatic Updaterif (TRANSITION in Y.config.doc.documentElement.style) {
e839bf134fb138920d4833cf05cb8b8906787a8dAutomatic Updater Transition.useNative = true;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater Transition.supported = true; // TODO: remove
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews}
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic UpdaterY.Node.DOM_EVENTS[TRANSITION_END] = 1;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic UpdaterTransition.NAME = 'transition';
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
795a316ec568b2470aab18b9481443966047652eAutomatic UpdaterTransition.DEFAULT_EASING = 'ease';
795a316ec568b2470aab18b9481443966047652eAutomatic UpdaterTransition.DEFAULT_DURATION = 0.5;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic UpdaterTransition.DEFAULT_DELAY = 0;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic UpdaterTransition._nodeAttrs = {};
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
795a316ec568b2470aab18b9481443966047652eAutomatic UpdaterTransition.prototype = {
795a316ec568b2470aab18b9481443966047652eAutomatic Updater constructor: Transition,
795a316ec568b2470aab18b9481443966047652eAutomatic Updater init: function(node, config) {
795a316ec568b2470aab18b9481443966047652eAutomatic Updater var anim = this;
795a316ec568b2470aab18b9481443966047652eAutomatic Updater if (!anim._running) {
795a316ec568b2470aab18b9481443966047652eAutomatic Updater anim._node = node;
795a316ec568b2470aab18b9481443966047652eAutomatic Updater anim._config = config;
795a316ec568b2470aab18b9481443966047652eAutomatic Updater node._transition = anim; // cache for reuse
795a316ec568b2470aab18b9481443966047652eAutomatic Updater
795a316ec568b2470aab18b9481443966047652eAutomatic Updater anim._duration = ('duration' in config) ?
795a316ec568b2470aab18b9481443966047652eAutomatic Updater config.duration: anim.constructor.DEFAULT_DURATION;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater anim._delay = ('delay' in config) ?
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater config.delay: anim.constructor.DEFAULT_DELAY;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater anim._easing = config.easing || anim.constructor.DEFAULT_EASING;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews anim._count = 0; // track number of animated properties
b272d38cc5d24f64c0647a9afb340c21c4b9aaf7Evan Hunt anim._running = false;
b272d38cc5d24f64c0647a9afb340c21c4b9aaf7Evan Hunt
b272d38cc5d24f64c0647a9afb340c21c4b9aaf7Evan Hunt anim.initAttrs(config);
b272d38cc5d24f64c0647a9afb340c21c4b9aaf7Evan Hunt
b272d38cc5d24f64c0647a9afb340c21c4b9aaf7Evan Hunt }
163af735c2082a024167be111d27bd5b5ff4f462Automatic Updater
163af735c2082a024167be111d27bd5b5ff4f462Automatic Updater return anim;
b272d38cc5d24f64c0647a9afb340c21c4b9aaf7Evan Hunt },
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews addProperty: function(prop, config) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews var anim = this,
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater node = this._node,
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews uid = Y.stamp(node),
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews attrs = Transition._nodeAttrs[uid],
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews attr,
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater val;
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews if (!attrs) {
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater attrs = Transition._nodeAttrs[uid] = {};
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
0a7ed88633a680bb881868b75ded4d09a7bbbc50Automatic Updater
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews attr = attrs[prop];
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews // might just be a value
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews if (config && config.value !== undefined) {
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User val = config.value;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews } else if (config !== undefined) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews val = config;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews config = EMPTY_OBJ;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews if (typeof val === 'function') {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews val = val.call(node, node);
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews // take control if another transition owns this property
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews if (attr && attr.transition && attr.transition !== anim) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews attr.transition._count--; // remapping attr to this transition
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User anim._count++; // properties per transition
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews attrs[prop] = {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews value: val,
b27ce68bae92006e2ad7a9b75602c6385e529c3bAutomatic Updater duration: ((typeof config.duration !== 'undefined') ? config.duration :
922312472e2e05ebc64993d465999c5351b83036Automatic Updater anim._duration) || 0.0001, // make 0 async and fire events
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews delay: (typeof config.delay !== 'undefined') ? config.delay :
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews anim._delay,
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
922312472e2e05ebc64993d465999c5351b83036Automatic Updater easing: config.easing || anim._easing,
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews transition: anim
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews };
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User },
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
922312472e2e05ebc64993d465999c5351b83036Automatic Updater removeProperty: function(prop) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews var anim = this,
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews attrs = Transition._nodeAttrs[Y.stamp(anim._node)];
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User if (attrs && attrs[prop]) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews delete attrs[prop];
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews anim._count--;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
163af735c2082a024167be111d27bd5b5ff4f462Automatic Updater },
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews initAttrs: function(config) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews var attr;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
794b79e6bbc3f5db1ea6ae154d739b9f1ef1a375Tinderbox User if (config.transform && !config[TRANSFORM_CAMEL]) {
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews config[TRANSFORM_CAMEL] = config.transform;
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews delete config.transform; // TODO: copy
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews }
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews
582f8b9a8d170a80ef67475bddb8ad5cf7cd7cadMark Andrews for (attr in config) {
if (config.hasOwnProperty(attr) && !Transition._reKeywords.test(attr)) {
this.addProperty(attr, config[attr]);
}
}
},
/**
* Starts or an animation.
* @method run
* @chainable
* @private
*/
run: function(callback) {
var anim = this;
if (!anim._running) {
anim._running = true;
anim._node.fire('transition:start', {
type: 'transition:start',
config: anim._config
});
anim._start();
anim._callback = callback;
}
return anim;
},
_start: function() {
this._runNative();
},
_prepDur: function(dur) {
dur = parseFloat(dur);
return dur + 's';
},
_runNative: function(time) {
var anim = this,
node = anim._node,
uid = Y.stamp(node),
domNode = node._node,
style = domNode.style,
computed = getComputedStyle(domNode),
attrs = Transition._nodeAttrs[uid],
cssText = '',
cssTransition = computed[TRANSITION_PROPERTY],
transitionText = TRANSITION_PROPERTY + ': ',
duration = TRANSITION_DURATION + ': ',
easing = TRANSITION_TIMING_FUNCTION + ': ',
delay = TRANSITION_DELAY + ': ',
hyphy,
attr,
name;
// preserve existing transitions
if (cssTransition !== 'all') {
transitionText += cssTransition + ',';
duration += computed[TRANSITION_DURATION] + ',';
easing += computed[TRANSITION_TIMING_FUNCTION] + ',';
delay += computed[TRANSITION_DELAY] + ',';
}
// run transitions mapped to this instance
for (name in attrs) {
hyphy = Transition._toHyphen(name);
attr = attrs[name];
if (attrs.hasOwnProperty(name) && attr.transition === anim) {
if (name in domNode.style) { // only native styles allowed
duration += anim._prepDur(attr.duration) + ',';
delay += anim._prepDur(attr.delay) + ',';
easing += (attr.easing) + ',';
transitionText += hyphy + ',';
cssText += hyphy + ': ' + attr.value + '; ';
} else {
this.removeProperty(name);
}
}
}
transitionText = transitionText.replace(/,$/, ';');
duration = duration.replace(/,$/, ';');
easing = easing.replace(/,$/, ';');
delay = delay.replace(/,$/, ';');
// only one native end event per node
if (!node._hasTransitionEnd) {
anim._detach = node.on(TRANSITION_END, anim._onNativeEnd);
node._hasTransitionEnd = true;
}
style.cssText += transitionText + duration + easing + delay + cssText;
},
_end: function(elapsed) {
var anim = this,
node = anim._node,
callback = anim._callback,
data = {
type: 'transition:end',
config: anim._config,
elapsedTime: elapsed
};
anim._running = false;
if (callback) {
anim._callback = null;
setTimeout(function() { // IE: allow previous update to finish
callback.call(node, data);
}, 1);
}
node.fire('transition:end', data);
},
_endNative: function(name) {
var node = this._node,
value = node.getComputedStyle(TRANSITION_PROPERTY);
if (typeof value === 'string') {
value = value.replace(new RegExp('(?:^|,\\s)' + name + ',?'), ',');
value = value.replace(/^,|,$/, '');
node.setStyle(TRANSITION_PROPERTY_CAMEL, value);
}
},
_onNativeEnd: function(e) {
var node = this,
uid = Y.stamp(node),
event = e._event,
name = Transition._toCamel(event.propertyName),
elapsed = event.elapsedTime,
attrs = Transition._nodeAttrs[uid],
attr = attrs[name],
anim = (attr) ? attr.transition : null;
if (anim) {
anim.removeProperty(name);
anim._endNative(name);
node.fire('transition:propertyEnd', {
type: 'propertyEnd',
propertyName: name,
elapsedTime: elapsed
});
if (anim._count <= 0) { // after propertEnd fires
anim._end(elapsed);
}
}
},
destroy: function() {
var anim = this;
if (anim._detach) {
anim._detach.detach();
}
anim._node = null;
}
};
Y.Transition = Transition;
Y.TransitionNative = Transition; // TODO: remove
/**
* Animate one or more css properties to a given value. Requires the "transition" module.
* <pre>example usage:
* Y.one('#demo').transition({
* duration: 1, // in seconds, default is 0.5
* easing: 'ease-out', // default is 'ease'
* delay: '1', // delay start for 1 second, default is 0
*
* height: '10px',
* width: '10px',
*
* opacity: { // per property
* value: 0,
* duration: 2,
* delay: 2,
* easing: 'ease-in'
* }
* });
* </pre>
* @for Node
* @method transition
* @param {Object} config An object containing one or more style properties, a duration and an easing.
* @param {Function} callback A function to run after the transition has completed.
* @chainable
*/
Y.Node.prototype.transition = function(config, callback) {
var anim = this._transition;
if (anim && !anim._running) {
anim.init(this, config);
} else {
anim = new Transition(this, config);
}
anim.run(callback);
return this;
};
/**
* Animate one or more css properties to a given value. Requires the "transition" module.
* <pre>example usage:
* Y.all('.demo').transition({
* duration: 1, // in seconds, default is 0.5
* easing: 'ease-out', // default is 'ease'
* delay: '1', // delay start for 1 second, default is 0
*
* height: '10px',
* width: '10px',
*
* opacity: { // per property
* value: 0,
* duration: 2,
* delay: 2,
* easing: 'ease-in'
* }
* });
* </pre>
* @for NodeList
* @method transition
* @param {Object} config An object containing one or more style properties, a duration and an easing.
* @param {Function} callback A function to run after the transition has completed. The callback fires
* once per item in the NodeList.
* @chainable
*/
Y.NodeList.prototype.transition = function(config, callback) {
this.each(function(node) {
node.transition(config, callback);
});
return this;
};