2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('transition-native', function(Y) {
11 * Provides the transition method for Node.
12 * Transition has no API of its own, but adds the transition method to Node.
15 * @requires node-style
18 var TRANSITION = '-webkit-transition',
19 TRANSITION_CAMEL = 'WebkitTransition',
20 TRANSITION_PROPERTY_CAMEL = 'WebkitTransitionProperty',
21 TRANSITION_PROPERTY = '-webkit-transition-property',
22 TRANSITION_DURATION = '-webkit-transition-duration',
23 TRANSITION_TIMING_FUNCTION = '-webkit-transition-timing-function',
24 TRANSITION_DELAY = '-webkit-transition-delay',
25 TRANSITION_END = 'webkitTransitionEnd',
26 ON_TRANSITION_END = 'onwebkittransitionend',
27 TRANSFORM_CAMEL = 'WebkitTransform',
32 * A class for constructing transition instances.
33 * Adds the "transition" method to Node.
38 Transition = function() {
39 this.init.apply(this, arguments);
43 Transition.toggles = {};
45 Transition._hasEnd = {};
47 Transition._toCamel = function(property) {
48 property = property.replace(/-([a-z])/gi, function(m0, m1) {
49 return m1.toUpperCase();
55 Transition._toHyphen = function(property) {
56 property = property.replace(/([A-Z]?)([a-z]+)([A-Z]?)/g, function(m0, m1, m2, m3) {
59 str += '-' + m1.toLowerCase();
64 str += '-' + m3.toLowerCase();
74 Transition._reKeywords = /^(?:node|duration|iterations|easing|delay|on|onstart|onend)$/i;
76 Transition.useNative = false;
78 if (TRANSITION in Y.config.doc.documentElement.style) {
79 Transition.useNative = true;
80 Transition.supported = true; // TODO: remove
83 Y.Node.DOM_EVENTS[TRANSITION_END] = 1;
85 Transition.NAME = 'transition';
87 Transition.DEFAULT_EASING = 'ease';
88 Transition.DEFAULT_DURATION = 0.5;
89 Transition.DEFAULT_DELAY = 0;
91 Transition._nodeAttrs = {};
93 Transition.prototype = {
94 constructor: Transition,
95 init: function(node, config) {
98 if (!anim._running && config) {
99 anim._config = config;
100 node._transition = anim; // cache for reuse
102 anim._duration = ('duration' in config) ?
103 config.duration: anim.constructor.DEFAULT_DURATION;
105 anim._delay = ('delay' in config) ?
106 config.delay: anim.constructor.DEFAULT_DELAY;
108 anim._easing = config.easing || anim.constructor.DEFAULT_EASING;
109 anim._count = 0; // track number of animated properties
110 anim._running = false;
117 addProperty: function(prop, config) {
121 nodeInstance = Y.one(node),
122 attrs = Transition._nodeAttrs[uid],
130 attrs = Transition._nodeAttrs[uid] = {};
135 // might just be a value
136 if (config && config.value !== undefined) {
138 } else if (config !== undefined) {
143 if (typeof val === 'function') {
144 val = val.call(nodeInstance, nodeInstance);
147 if (attr && attr.transition) {
148 // take control if another transition owns this property
149 if (attr.transition !== anim) {
150 attr.transition._count--; // remapping attr to this transition
154 anim._count++; // properties per transition
156 // make 0 async and fire events
157 dur = ((typeof config.duration != 'undefined') ? config.duration :
158 anim._duration) || 0.0001;
163 delay: (typeof config.delay != 'undefined') ? config.delay :
166 easing: config.easing || anim._easing,
171 // native end event doesnt fire when setting to same value
172 // supplementing with timer
173 // val may be a string or number (height: 0, etc), but computedStyle is always string
174 computed = Y.DOM.getComputedStyle(node, prop);
175 compareVal = (typeof val === 'string') ? computed : parseFloat(computed);
177 if (Transition.useNative && compareVal === val) {
178 setTimeout(function() {
179 anim._onNativeEnd.call(node, {
187 removeProperty: function(prop) {
189 attrs = Transition._nodeAttrs[Y.stamp(anim._node)];
191 if (attrs && attrs[prop]) {
198 initAttrs: function(config) {
202 if (config.transform && !config[TRANSFORM_CAMEL]) {
203 config[TRANSFORM_CAMEL] = config.transform;
204 delete config.transform; // TODO: copy
207 for (attr in config) {
208 if (config.hasOwnProperty(attr) && !Transition._reKeywords.test(attr)) {
209 this.addProperty(attr, config[attr]);
211 // when size is auto or % webkit starts from zero instead of computed
212 // (https://bugs.webkit.org/show_bug.cgi?id=16020)
213 // TODO: selective set
214 if (node.style[attr] === '') {
215 Y.DOM.setStyle(node, attr, Y.DOM.getComputedStyle(node, attr));
222 * Starts or an animation.
227 run: function(callback) {
230 config = anim._config,
232 type: 'transition:start',
237 if (!anim._running) {
238 anim._running = true;
240 //anim._node.fire('transition:start', data);
242 if (config.on && config.on.start) {
243 config.on.start.call(Y.one(node), data);
246 anim.initAttrs(anim._config);
248 anim._callback = callback;
260 _prepDur: function(dur) {
261 dur = parseFloat(dur);
266 _runNative: function(time) {
271 computed = getComputedStyle(node),
272 attrs = Transition._nodeAttrs[uid],
274 cssTransition = computed[TRANSITION_PROPERTY],
276 transitionText = TRANSITION_PROPERTY + ': ',
277 duration = TRANSITION_DURATION + ': ',
278 easing = TRANSITION_TIMING_FUNCTION + ': ',
279 delay = TRANSITION_DELAY + ': ',
284 // preserve existing transitions
285 if (cssTransition !== 'all') {
286 transitionText += cssTransition + ',';
287 duration += computed[TRANSITION_DURATION] + ',';
288 easing += computed[TRANSITION_TIMING_FUNCTION] + ',';
289 delay += computed[TRANSITION_DELAY] + ',';
293 // run transitions mapped to this instance
294 for (name in attrs) {
295 hyphy = Transition._toHyphen(name);
297 if (attrs.hasOwnProperty(name) && attr.transition === anim) {
298 if (name in node.style) { // only native styles allowed
299 duration += anim._prepDur(attr.duration) + ',';
300 delay += anim._prepDur(attr.delay) + ',';
301 easing += (attr.easing) + ',';
303 transitionText += hyphy + ',';
304 cssText += hyphy + ': ' + attr.value + '; ';
306 this.removeProperty(name);
311 transitionText = transitionText.replace(/,$/, ';');
312 duration = duration.replace(/,$/, ';');
313 easing = easing.replace(/,$/, ';');
314 delay = delay.replace(/,$/, ';');
316 // only one native end event per node
317 if (!Transition._hasEnd[uid]) {
318 //anim._detach = Y.on(TRANSITION_END, anim._onNativeEnd, node);
319 //node[ON_TRANSITION_END] = anim._onNativeEnd;
320 node.addEventListener(TRANSITION_END, anim._onNativeEnd, false);
321 Transition._hasEnd[uid] = true;
325 //setTimeout(function() { // allow updates to apply (size fix, onstart, etc)
326 style.cssText += transitionText + duration + easing + delay + cssText;
331 _end: function(elapsed) {
334 callback = anim._callback,
335 config = anim._config,
337 type: 'transition:end',
342 nodeInstance = Y.one(node);
344 anim._running = false;
345 anim._callback = null;
348 if (config.on && config.on.end) {
349 setTimeout(function() { // IE: allow previous update to finish
350 config.on.end.call(nodeInstance, data);
352 // nested to ensure proper fire order
354 callback.call(nodeInstance, data);
358 } else if (callback) {
359 setTimeout(function() { // IE: allow previous update to finish
360 callback.call(nodeInstance, data);
363 //node.fire('transition:end', data);
368 _endNative: function(name) {
369 var node = this._node,
370 value = node.ownerDocument.defaultView.getComputedStyle(node, '')[TRANSITION_PROPERTY];
372 if (typeof value === 'string') {
373 value = value.replace(new RegExp('(?:^|,\\s)' + name + ',?'), ',');
374 value = value.replace(/^,|,$/, '');
375 node.style[TRANSITION_CAMEL] = value;
379 _onNativeEnd: function(e) {
382 event = e,//e._event,
383 name = Transition._toCamel(event.propertyName),
384 elapsed = event.elapsedTime,
385 attrs = Transition._nodeAttrs[uid],
387 anim = (attr) ? attr.transition : null,
392 anim.removeProperty(name);
393 anim._endNative(name);
394 config = anim._config[name];
399 elapsedTime: elapsed,
403 if (config && config.on && config.on.end) {
404 config.on.end.call(Y.one(node), data);
407 //node.fire('transition:propertyEnd', data);
409 if (anim._count <= 0) { // after propertyEnd fires
415 destroy: function() {
419 anim._detach.detach();
422 //anim._node[ON_TRANSITION_END] = null;
423 node.removeEventListener(TRANSITION_END, anim._onNativeEnd, false);
428 Y.Transition = Transition;
429 Y.TransitionNative = Transition; // TODO: remove
432 * Animate one or more css properties to a given value. Requires the "transition" module.
433 * <pre>example usage:
434 * Y.one('#demo').transition({
435 * duration: 1, // in seconds, default is 0.5
436 * easing: 'ease-out', // default is 'ease'
437 * delay: '1', // delay start for 1 second, default is 0
442 * opacity: { // per property
452 * @param {Object} config An object containing one or more style properties, a duration and an easing.
453 * @param {Function} callback A function to run after the transition has completed.
456 Y.Node.prototype.transition = function(name, config, callback) {
458 transitionAttrs = Transition._nodeAttrs[Y.stamp(this._node)],
459 anim = (transitionAttrs) ? transitionAttrs.transition || null : null,
463 if (typeof name === 'string') { // named effect, pull config from registry
464 if (typeof config === 'function') {
469 fxConfig = Transition.fx[name];
471 if (config && typeof config !== 'boolean') {
472 config = Y.clone(config);
474 for (prop in fxConfig) {
475 if (fxConfig.hasOwnProperty(prop)) {
476 if (! (prop in config)) {
477 config[prop] = fxConfig[prop];
485 } else { // name is a config, config is a callback or undefined
490 if (anim && !anim._running) {
491 anim.init(this, config);
493 anim = new Transition(this._node, config);
500 Y.Node.prototype.show = function(name, config, callback) {
501 this._show(); // show prior to transition
502 if (name && Y.Transition) {
503 if (typeof name !== 'string' && !name.push) { // named effect or array of effects supercedes default
504 if (typeof config === 'function') {
508 name = this.SHOW_TRANSITION;
510 this.transition(name, config, callback);
515 var _wrapCallBack = function(anim, fn, callback) {
521 callback.apply(anim._node, arguments);
526 Y.Node.prototype.hide = function(name, config, callback) {
527 if (name && Y.Transition) {
528 if (typeof config === 'function') {
533 callback = _wrapCallBack(this, this._hide, callback); // wrap with existing callback
534 if (typeof name !== 'string' && !name.push) { // named effect or array of effects supercedes default
535 if (typeof config === 'function') {
539 name = this.HIDE_TRANSITION;
541 this.transition(name, config, callback);
549 * Animate one or more css properties to a given value. Requires the "transition" module.
550 * <pre>example usage:
551 * Y.all('.demo').transition({
552 * duration: 1, // in seconds, default is 0.5
553 * easing: 'ease-out', // default is 'ease'
554 * delay: '1', // delay start for 1 second, default is 0
559 * opacity: { // per property
569 * @param {Object} config An object containing one or more style properties, a duration and an easing.
570 * @param {Function} callback A function to run after the transition has completed. The callback fires
571 * once per item in the NodeList.
574 Y.NodeList.prototype.transition = function(config, callback) {
575 var nodes = this._nodes,
579 while ((node = nodes[i++])) {
580 Y.one(node).transition(config, callback);
586 Y.Node.prototype.toggleView = function(name, on) {
588 this._toggles = this._toggles || [];
590 if (typeof name == 'boolean') { // no transition, just toggle
593 if (typeof on === 'undefined' && name in this._toggles) {
594 on = ! this._toggles[name];
602 callback = _wrapCallBack(anim, this._hide);
605 this._toggles[name] = on;
606 this.transition(Y.Transition.toggles[name][on], callback);
609 Y.NodeList.prototype.toggleView = function(config, callback) {
610 var nodes = this._nodes,
614 while ((node = nodes[i++])) {
615 Y.one(node).toggleView(config, callback);
621 Y.mix(Transition.fx, {
642 height: function(node) {
643 return node.get('scrollHeight') + 'px';
645 width: function(node) {
646 return node.get('scrollWidth') + 'px';
653 var overflow = this.getStyle('overflow');
654 if (overflow !== 'hidden') { // enable scrollHeight/Width
655 this.setStyle('overflow', 'hidden');
656 this._transitionOverflow = overflow;
661 if (this._transitionOverflow) { // revert overridden value
662 this.setStyle('overflow', this._transitionOverflow);
669 Y.mix(Transition.toggles, {
670 size: ['sizeIn', 'sizeOut'],
671 fade: ['fadeOut', 'fadeIn']
675 }, '3.3.0' ,{requires:['node-base']});