NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / anim-base / anim-base.js
blob8c0aab398900ccb172929e0500aefcc6b4595f2e
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('anim-base', function (Y, NAME) {
10 /**
11 * The Animation Utility provides an API for creating advanced transitions.
12 * @module anim
15 /**
16 * Provides the base Anim class, for animating numeric properties.
18 * @module anim
19 * @submodule anim-base
22     /**
23      * A class for constructing animation instances.
24      * @class Anim
25      * @for Anim
26      * @constructor
27      * @extends Base
28      */
30     var RUNNING = 'running',
31         START_TIME = 'startTime',
32         ELAPSED_TIME = 'elapsedTime',
33         /**
34         * @for Anim
35         * @event start
36         * @description fires when an animation begins.
37         * @param {Event} ev The start event.
38         * @type Event.Custom
39         */
40         START = 'start',
42         /**
43         * @event tween
44         * @description fires every frame of the animation.
45         * @param {Event} ev The tween event.
46         * @type Event.Custom
47         */
48         TWEEN = 'tween',
50         /**
51         * @event end
52         * @description fires after the animation completes.
53         * @param {Event} ev The end event.
54         * @type Event.Custom
55         */
56         END = 'end',
57         NODE = 'node',
58         PAUSED = 'paused',
59         REVERSE = 'reverse', // TODO: cleanup
60         ITERATION_COUNT = 'iterationCount',
62         NUM = Number;
64     var _running = {},
65         _timer;
67     Y.Anim = function() {
68         Y.Anim.superclass.constructor.apply(this, arguments);
69         Y.Anim._instances[Y.stamp(this)] = this;
70     };
72     Y.Anim.NAME = 'anim';
74     Y.Anim._instances = {};
76     /**
77      * Regex of properties that should use the default unit.
78      *
79      * @property RE_DEFAULT_UNIT
80      * @static
81      */
82     Y.Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;
84     /**
85      * The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
86      *
87      * @property DEFAULT_UNIT
88      * @static
89      */
90     Y.Anim.DEFAULT_UNIT = 'px';
92     Y.Anim.DEFAULT_EASING = function (t, b, c, d) {
93         return c * t / d + b; // linear easing
94     };
96     /**
97      * Time in milliseconds passed to setInterval for frame processing
98      *
99      * @property intervalTime
100      * @default 20
101      * @static
102      */
103     Y.Anim._intervalTime = 20;
105     /**
106      * Bucket for custom getters and setters
107      *
108      * @property behaviors
109      * @static
110      */
111     Y.Anim.behaviors = {
112         left: {
113             get: function(anim, attr) {
114                 return anim._getOffset(attr);
115             }
116         }
117     };
119     Y.Anim.behaviors.top = Y.Anim.behaviors.left;
121     /**
122      * The default setter to use when setting object properties.
123      *
124      * @property DEFAULT_SETTER
125      * @static
126      */
127     Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
128         var node = anim._node,
129             domNode = node._node,
130             val = fn(elapsed, NUM(from), NUM(to) - NUM(from), duration);
132         if (domNode) {
133             if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
134                 unit = unit || '';
135                 node.setStyle(att, val + unit);
136             } else if ('attributes' in domNode && att in domNode.attributes) {
137                 node.setAttribute(att, val);
138             } else if (att in domNode) {
139                 domNode[att] = val;
140             }
141         } else if (node.set) {
142             node.set(att, val);
143         } else if (att in node) {
144             node[att] = val;
145         }
146     };
148     /**
149      * The default getter to use when getting object properties.
150      *
151      * @property DEFAULT_GETTER
152      * @static
153      */
154     Y.Anim.DEFAULT_GETTER = function(anim, att) {
155         var node = anim._node,
156             domNode = node._node,
157             val = '';
159         if (domNode) {
160             if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
161                 val = node.getComputedStyle(att);
162             } else if ('attributes' in domNode && att in domNode.attributes) {
163                 val = node.getAttribute(att);
164             } else if (att in domNode) {
165                 val = domNode[att];
166             }
167         } else if (node.get) {
168             val = node.get(att);
169         } else if (att in node) {
170             val = node[att];
171         }
173         return val;
174     };
176     Y.Anim.ATTRS = {
177         /**
178          * The object to be animated.
179          * @attribute node
180          * @type Node
181          */
182         node: {
183             setter: function(node) {
184                 if (node) {
185                     if (typeof node === 'string' || node.nodeType) {
186                         node = Y.one(node);
187                     }
188                 }
190                 this._node = node;
191                 if (!node) {
192                 }
193                 return node;
194             }
195         },
197         /**
198          * The length of the animation.  Defaults to "1" (second).
199          * @attribute duration
200          * @type NUM
201          */
202         duration: {
203             value: 1
204         },
206         /**
207          * The method that will provide values to the attribute(s) during the animation.
208          * Defaults to "Easing.easeNone".
209          * @attribute easing
210          * @type Function
211          */
212         easing: {
213             value: Y.Anim.DEFAULT_EASING,
215             setter: function(val) {
216                 if (typeof val === 'string' && Y.Easing) {
217                     return Y.Easing[val];
218                 }
219             }
220         },
222         /**
223          * The starting values for the animated properties.
224          *
225          * Fields may be strings, numbers, or functions.
226          * If a function is used, the return value becomes the from value.
227          * If no from value is specified, the DEFAULT_GETTER will be used.
228          * Supports any unit, provided it matches the "to" (or default)
229          * unit (e.g. `{width: '10em', color: 'rgb(0, 0, 0)', borderColor: '#ccc'}`).
230          *
231          * If using the default ('px' for length-based units), the unit may be omitted
232          * (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels
233          * and hex, respectively).
234          *
235          * @attribute from
236          * @type Object
237          */
238         from: {},
240         /**
241          * The ending values for the animated properties.
242          *
243          * Fields may be strings, numbers, or functions.
244          * Supports any unit, provided it matches the "from" (or default)
245          * unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`).
246          *
247          * If using the default ('px' for length-based units), the unit may be omitted
248          * (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels
249          * and hex, respectively).
250          *
251          * @attribute to
252          * @type Object
253          */
254         to: {},
256         /**
257          * Date stamp for the first frame of the animation.
258          * @attribute startTime
259          * @type Int
260          * @default 0
261          * @readOnly
262          */
263         startTime: {
264             value: 0,
265             readOnly: true
266         },
268         /**
269          * Current time the animation has been running.
270          * @attribute elapsedTime
271          * @type Int
272          * @default 0
273          * @readOnly
274          */
275         elapsedTime: {
276             value: 0,
277             readOnly: true
278         },
280         /**
281          * Whether or not the animation is currently running.
282          * @attribute running
283          * @type Boolean
284          * @default false
285          * @readOnly
286          */
287         running: {
288             getter: function() {
289                 return !!_running[Y.stamp(this)];
290             },
291             value: false,
292             readOnly: true
293         },
295         /**
296          * The number of times the animation should run
297          * @attribute iterations
298          * @type Int
299          * @default 1
300          */
301         iterations: {
302             value: 1
303         },
305         /**
306          * The number of iterations that have occurred.
307          * Resets when an animation ends (reaches iteration count or stop() called).
308          * @attribute iterationCount
309          * @type Int
310          * @default 0
311          * @readOnly
312          */
313         iterationCount: {
314             value: 0,
315             readOnly: true
316         },
318         /**
319          * How iterations of the animation should behave.
320          * Possible values are "normal" and "alternate".
321          * Normal will repeat the animation, alternate will reverse on every other pass.
322          *
323          * @attribute direction
324          * @type String
325          * @default "normal"
326          */
327         direction: {
328             value: 'normal' // | alternate (fwd on odd, rev on even per spec)
329         },
331         /**
332          * Whether or not the animation is currently paused.
333          * @attribute paused
334          * @type Boolean
335          * @default false
336          * @readOnly
337          */
338         paused: {
339             readOnly: true,
340             value: false
341         },
343         /**
344          * If true, animation begins from last frame
345          * @attribute reverse
346          * @type Boolean
347          * @default false
348          */
349         reverse: {
350             value: false
351         }
354     };
356     /**
357      * Runs all animation instances.
358      * @method run
359      * @static
360      */
361     Y.Anim.run = function() {
362         var instances = Y.Anim._instances,
363             i;
364         for (i in instances) {
365             if (instances[i].run) {
366                 instances[i].run();
367             }
368         }
369     };
371     /**
372      * Pauses all animation instances.
373      * @method pause
374      * @static
375      */
376     Y.Anim.pause = function() {
377         for (var i in _running) { // stop timer if nothing running
378             if (_running[i].pause) {
379                 _running[i].pause();
380             }
381         }
383         Y.Anim._stopTimer();
384     };
386     /**
387      * Stops all animation instances.
388      * @method stop
389      * @static
390      */
391     Y.Anim.stop = function() {
392         for (var i in _running) { // stop timer if nothing running
393             if (_running[i].stop) {
394                 _running[i].stop();
395             }
396         }
397         Y.Anim._stopTimer();
398     };
400     Y.Anim._startTimer = function() {
401         if (!_timer) {
402             _timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
403         }
404     };
406     Y.Anim._stopTimer = function() {
407         clearInterval(_timer);
408         _timer = 0;
409     };
411     /**
412      * Called per Interval to handle each animation frame.
413      * @method _runFrame
414      * @private
415      * @static
416      */
417     Y.Anim._runFrame = function() {
418         var done = true,
419             anim;
420         for (anim in _running) {
421             if (_running[anim]._runFrame) {
422                 done = false;
423                 _running[anim]._runFrame();
424             }
425         }
427         if (done) {
428             Y.Anim._stopTimer();
429         }
430     };
432     Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
434     var proto = {
435         /**
436          * Starts or resumes an animation.
437          * @method run
438          * @chainable
439          */
440         run: function() {
441             if (this.get(PAUSED)) {
442                 this._resume();
443             } else if (!this.get(RUNNING)) {
444                 this._start();
445             }
446             return this;
447         },
449         /**
450          * Pauses the animation and
451          * freezes it in its current state and time.
452          * Calling run() will continue where it left off.
453          * @method pause
454          * @chainable
455          */
456         pause: function() {
457             if (this.get(RUNNING)) {
458                 this._pause();
459             }
460             return this;
461         },
463         /**
464          * Stops the animation and resets its time.
465          * @method stop
466          * @param {Boolean} finish If true, the animation will move to the last frame
467          * @chainable
468          */
469         stop: function(finish) {
470             if (this.get(RUNNING) || this.get(PAUSED)) {
471                 this._end(finish);
472             }
473             return this;
474         },
476         _added: false,
478         _start: function() {
479             this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
480             this._actualFrames = 0;
481             if (!this.get(PAUSED)) {
482                 this._initAnimAttr();
483             }
484             _running[Y.stamp(this)] = this;
485             Y.Anim._startTimer();
487             this.fire(START);
488         },
490         _pause: function() {
491             this._set(START_TIME, null);
492             this._set(PAUSED, true);
493             delete _running[Y.stamp(this)];
495             /**
496             * @event pause
497             * @description fires when an animation is paused.
498             * @param {Event} ev The pause event.
499             * @type Event.Custom
500             */
501             this.fire('pause');
502         },
504         _resume: function() {
505             this._set(PAUSED, false);
506             _running[Y.stamp(this)] = this;
507             this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
508             Y.Anim._startTimer();
510             /**
511             * @event resume
512             * @description fires when an animation is resumed (run from pause).
513             * @param {Event} ev The pause event.
514             * @type Event.Custom
515             */
516             this.fire('resume');
517         },
519         _end: function(finish) {
520             var duration = this.get('duration') * 1000;
521             if (finish) { // jump to last frame
522                 this._runAttrs(duration, duration, this.get(REVERSE));
523             }
525             this._set(START_TIME, null);
526             this._set(ELAPSED_TIME, 0);
527             this._set(PAUSED, false);
529             delete _running[Y.stamp(this)];
530             this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
531         },
533         _runFrame: function() {
534             var d = this._runtimeAttr.duration,
535                 t = new Date() - this.get(START_TIME),
536                 reverse = this.get(REVERSE),
537                 done = (t >= d);
539             this._runAttrs(t, d, reverse);
540             this._actualFrames += 1;
541             this._set(ELAPSED_TIME, t);
543             this.fire(TWEEN);
544             if (done) {
545                 this._lastFrame();
546             }
547         },
549         _runAttrs: function(t, d, reverse) {
550             var attr = this._runtimeAttr,
551                 customAttr = Y.Anim.behaviors,
552                 easing = attr.easing,
553                 lastFrame = d,
554                 done = false,
555                 attribute,
556                 setter,
557                 i;
559             if (t >= d) {
560                 done = true;
561             }
563             if (reverse) {
564                 t = d - t;
565                 lastFrame = 0;
566             }
568             for (i in attr) {
569                 if (attr[i].to) {
570                     attribute = attr[i];
571                     setter = (i in customAttr && 'set' in customAttr[i]) ?
572                             customAttr[i].set : Y.Anim.DEFAULT_SETTER;
574                     if (!done) {
575                         setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
576                     } else {
577                         setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
578                     }
579                 }
580             }
583         },
585         _lastFrame: function() {
586             var iter = this.get('iterations'),
587                 iterCount = this.get(ITERATION_COUNT);
589             iterCount += 1;
590             if (iter === 'infinite' || iterCount < iter) {
591                 if (this.get('direction') === 'alternate') {
592                     this.set(REVERSE, !this.get(REVERSE)); // flip it
593                 }
594                 /**
595                 * @event iteration
596                 * @description fires when an animation begins an iteration.
597                 * @param {Event} ev The iteration event.
598                 * @type Event.Custom
599                 */
600                 this.fire('iteration');
601             } else {
602                 iterCount = 0;
603                 this._end();
604             }
606             this._set(START_TIME, new Date());
607             this._set(ITERATION_COUNT, iterCount);
608         },
610         _initAnimAttr: function() {
611             var from = this.get('from') || {},
612                 to = this.get('to') || {},
613                 attr = {
614                     duration: this.get('duration') * 1000,
615                     easing: this.get('easing')
616                 },
617                 customAttr = Y.Anim.behaviors,
618                 node = this.get(NODE), // implicit attr init
619                 unit, begin, end;
621             Y.each(to, function(val, name) {
622                 if (typeof val === 'function') {
623                     val = val.call(this, node);
624                 }
626                 begin = from[name];
627                 if (begin === undefined) {
628                     begin = (name in customAttr && 'get' in customAttr[name])  ?
629                             customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
630                 } else if (typeof begin === 'function') {
631                     begin = begin.call(this, node);
632                 }
634                 var mFrom = Y.Anim.RE_UNITS.exec(begin),
635                     mTo = Y.Anim.RE_UNITS.exec(val);
637                 begin = mFrom ? mFrom[1] : begin;
638                 end = mTo ? mTo[1] : val;
639                 unit = mTo ? mTo[2] : mFrom ?  mFrom[2] : ''; // one might be zero TODO: mixed units
641                 if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
642                     unit = Y.Anim.DEFAULT_UNIT;
643                 }
645                 if (!begin || !end) {
646                     Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
647                     return;
648                 }
650                 attr[name] = {
651                     from: Y.Lang.isObject(begin) ? Y.clone(begin) : begin,
652                     to: end,
653                     unit: unit
654                 };
656             }, this);
658             this._runtimeAttr = attr;
659         },
662         // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
663         _getOffset: function(attr) {
664             var node = this._node,
665                 val = node.getComputedStyle(attr),
666                 get = (attr === 'left') ? 'getX': 'getY',
667                 set = (attr === 'left') ? 'setX': 'setY',
668                 position;
670             if (val === 'auto') {
671                 position = node.getStyle('position');
672                 if (position === 'absolute' || position === 'fixed') {
673                     val = node[get]();
674                     node[set](val);
675                 } else {
676                     val = 0;
677                 }
678             }
680             return val;
681         },
683         destructor: function() {
684             delete Y.Anim._instances[Y.stamp(this)];
685         }
686     };
688     Y.extend(Y.Anim, Y.Base, proto);
691 }, '3.13.0', {"requires": ["base-base", "node-style"]});