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']});
676
YUI.add('transition-timer', function(Y) {
679
* The Transition Utility provides an API for creating advanced transitions.
684
* Provides the base Transition class, for animating numeric properties.
687
* @submodule transition-timer
691
var Transition = Y.Transition;
693
Y.mix(Transition.prototype, {
695
if (Transition.useNative) {
702
_runTimer: function() {
706
Transition._running[Y.stamp(anim)] = anim;
707
anim._startTime = new Date();
708
Transition._startTimer();
711
_endTimer: function() {
713
delete Transition._running[Y.stamp(anim)];
714
anim._startTime = null;
717
_runFrame: function() {
718
var t = new Date() - this._startTime;
722
_runAttrs: function(time) {
725
config = anim._config,
727
attrs = Transition._nodeAttrs[uid],
728
customAttr = Transition.behaviors,
741
for (name in attrs) {
742
attribute = attrs[name];
743
if ((attribute && attribute.transition === anim)) {
744
d = attribute.duration;
745
delay = attribute.delay;
746
elapsed = (time - delay) / 1000;
755
setter = (i in customAttr && 'set' in customAttr[i]) ?
756
customAttr[i].set : Transition.DEFAULT_SETTER;
764
if (!delay || time >= delay) {
765
setter(anim, name, attribute.from, attribute.to, t - delay, d - delay,
766
attribute.easing, attribute.unit);
772
if (config[name] && config[name].on && config[name].on.end) {
773
config[name].on.end.call(Y.one(node), data);
776
//node.fire('transition:propertyEnd', data);
778
if (!allDone && anim._count <= 0) {
790
_initAttrs: function() {
792
customAttr = Transition.behaviors,
793
uid = Y.stamp(anim._node),
794
attrs = Transition._nodeAttrs[uid],
805
for (name in attrs) {
806
attribute = attrs[name];
807
if (attrs.hasOwnProperty(name) && (attribute && attribute.transition === anim)) {
808
duration = attribute.duration * 1000;
809
delay = attribute.delay * 1000;
810
easing = attribute.easing;
811
val = attribute.value;
813
// only allow supported properties
814
if (name in anim._node.style || name in Y.DOM.CUSTOM_STYLES) {
815
begin = (name in customAttr && 'get' in customAttr[name]) ?
816
customAttr[name].get(anim, name) : Transition.DEFAULT_GETTER(anim, name);
818
mFrom = Transition.RE_UNITS.exec(begin);
819
mTo = Transition.RE_UNITS.exec(val);
821
begin = mFrom ? mFrom[1] : begin;
822
end = mTo ? mTo[1] : val;
823
unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
825
if (!unit && Transition.RE_DEFAULT_UNIT.test(name)) {
826
unit = Transition.DEFAULT_UNIT;
829
if (typeof easing === 'string') {
830
if (easing.indexOf('cubic-bezier') > -1) {
831
easing = easing.substring(13, easing.length - 1).split(',');
832
} else if (Transition.easings[easing]) {
833
easing = Transition.easings[easing];
837
attribute.from = Number(begin);
838
attribute.to = Number(end);
839
attribute.unit = unit;
840
attribute.easing = easing;
841
attribute.duration = duration + delay;
842
attribute.delay = delay;
851
destroy: function() {
857
Y.mix(Y.Transition, {
860
* Regex of properties that should use the default unit.
862
* @property RE_DEFAULT_UNIT
865
RE_DEFAULT_UNIT: /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i,
868
* The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
870
* @property DEFAULT_UNIT
876
* Time in milliseconds passed to setInterval for frame processing
878
* @property intervalTime
885
* Bucket for custom getters and setters
887
* @property behaviors
892
get: function(anim, attr) {
893
return Y.DOM._getAttrOffset(anim._node, attr);
899
* The default setter to use when setting object properties.
901
* @property DEFAULT_SETTER
904
DEFAULT_SETTER: function(anim, att, from, to, elapsed, duration, fn, unit) {
908
var node = anim._node,
909
val = Transition.cubicBezier(fn, elapsed / duration);
911
val = from + val[0] * (to - from);
914
if (att in node.style || att in Y.DOM.CUSTOM_STYLES) {
916
Y.DOM.setStyle(node, att, val + unit);
924
* The default getter to use when getting object properties.
926
* @property DEFAULT_GETTER
929
DEFAULT_GETTER: function(anim, att) {
930
var node = anim._node,
933
if (att in node.style || att in Y.DOM.CUSTOM_STYLES) {
934
val = Y.DOM.getComputedStyle(node, att);
940
_startTimer: function() {
941
if (!Transition._timer) {
942
Transition._timer = setInterval(Transition._runFrame, Transition.intervalTime);
946
_stopTimer: function() {
947
clearInterval(Transition._timer);
948
Transition._timer = null;
952
* Called per Interval to handle each animation frame.
957
_runFrame: function() {
960
for (anim in Transition._running) {
961
if (Transition._running[anim]._runFrame) {
963
Transition._running[anim]._runFrame();
968
Transition._stopTimer();
972
cubicBezier: function(p, t) {
982
A = x3 - 3 * x2 + 3 * x1 - x0,
983
B = 3 * x2 - 6 * x1 + 3 * x0,
986
E = y3 - 3 * y2 + 3 * y1 - y0,
987
F = 3 * y2 - 6 * y1 + 3 * y0,
991
x = (((A*t) + B)*t + C)*t + D,
992
y = (((E*t) + F)*t + G)*t + H;
998
ease: [0.25, 0, 1, 0.25],
999
linear: [0, 0, 1, 1],
1000
'ease-in': [0.42, 0, 1, 1],
1001
'ease-out': [0, 0, 0.58, 1],
1002
'ease-in-out': [0.42, 0, 0.58, 1]
1008
RE_UNITS: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/
1011
Transition.behaviors.top = Transition.behaviors.bottom = Transition.behaviors.right = Transition.behaviors.left;
1013
Y.Transition = Transition;
1016
}, '3.3.0' ,{requires:['transition-native', 'node-style']});
1019
YUI.add('transition', function(Y){}, '3.3.0' ,{use:['transition-native', 'transition-timer']});