2 * @license AngularJS v1.4.8
3 * (c) 2010-2015 Google, Inc. http://angularjs.org
6 (function(window, angular, undefined) {'use strict';
8 /* jshint ignore:start */
9 var noop = angular.noop;
10 var extend = angular.extend;
11 var jqLite = angular.element;
12 var forEach = angular.forEach;
13 var isArray = angular.isArray;
14 var isString = angular.isString;
15 var isObject = angular.isObject;
16 var isUndefined = angular.isUndefined;
17 var isDefined = angular.isDefined;
18 var isFunction = angular.isFunction;
19 var isElement = angular.isElement;
24 var ADD_CLASS_SUFFIX = '-add';
25 var REMOVE_CLASS_SUFFIX = '-remove';
26 var EVENT_CLASS_PREFIX = 'ng-';
27 var ACTIVE_CLASS_SUFFIX = '-active';
29 var NG_ANIMATE_CLASSNAME = 'ng-animate';
30 var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
32 // Detect proper transitionend/animationend event names.
33 var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
35 // If unprefixed events are not supported but webkit-prefixed are, use the latter.
36 // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
37 // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
38 // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
39 // Register both events in case `window.onanimationend` is not supported because of that,
40 // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
41 // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
42 // therefore there is no reason to test anymore for other vendor prefixes:
43 // http://caniuse.com/#search=transition
44 if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
45 CSS_PREFIX = '-webkit-';
46 TRANSITION_PROP = 'WebkitTransition';
47 TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
49 TRANSITION_PROP = 'transition';
50 TRANSITIONEND_EVENT = 'transitionend';
53 if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
54 CSS_PREFIX = '-webkit-';
55 ANIMATION_PROP = 'WebkitAnimation';
56 ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
58 ANIMATION_PROP = 'animation';
59 ANIMATIONEND_EVENT = 'animationend';
62 var DURATION_KEY = 'Duration';
63 var PROPERTY_KEY = 'Property';
64 var DELAY_KEY = 'Delay';
65 var TIMING_KEY = 'TimingFunction';
66 var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
67 var ANIMATION_PLAYSTATE_KEY = 'PlayState';
68 var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
70 var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
71 var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
72 var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
73 var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
75 var isPromiseLike = function(p) {
76 return p && p.then ? true : false;
79 function assertArg(arg, name, reason) {
81 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
86 function mergeClasses(a,b) {
87 if (!a && !b) return '';
90 if (isArray(a)) a = a.join(' ');
91 if (isArray(b)) b = b.join(' ');
95 function packageStyles(options) {
97 if (options && (options.to || options.from)) {
98 styles.to = options.to;
99 styles.from = options.from;
104 function pendClasses(classes, fix, isPrefix) {
106 classes = isArray(classes)
108 : classes && isString(classes) && classes.length
109 ? classes.split(/\s+/)
111 forEach(classes, function(klass, i) {
112 if (klass && klass.length > 0) {
113 className += (i > 0) ? ' ' : '';
114 className += isPrefix ? fix + klass
121 function removeFromArray(arr, val) {
122 var index = arr.indexOf(val);
124 arr.splice(index, 1);
128 function stripCommentsFromElement(element) {
129 if (element instanceof jqLite) {
130 switch (element.length) {
136 // there is no point of stripping anything if the element
137 // is the only element within the jqLite wrapper.
138 // (it's important that we retain the element instance.)
139 if (element[0].nodeType === ELEMENT_NODE) {
145 return jqLite(extractElementNode(element));
150 if (element.nodeType === ELEMENT_NODE) {
151 return jqLite(element);
155 function extractElementNode(element) {
156 if (!element[0]) return element;
157 for (var i = 0; i < element.length; i++) {
158 var elm = element[i];
159 if (elm.nodeType == ELEMENT_NODE) {
165 function $$addClass($$jqLite, element, className) {
166 forEach(element, function(elm) {
167 $$jqLite.addClass(elm, className);
171 function $$removeClass($$jqLite, element, className) {
172 forEach(element, function(elm) {
173 $$jqLite.removeClass(elm, className);
177 function applyAnimationClassesFactory($$jqLite) {
178 return function(element, options) {
179 if (options.addClass) {
180 $$addClass($$jqLite, element, options.addClass);
181 options.addClass = null;
183 if (options.removeClass) {
184 $$removeClass($$jqLite, element, options.removeClass);
185 options.removeClass = null;
190 function prepareAnimationOptions(options) {
191 options = options || {};
192 if (!options.$$prepared) {
193 var domOperation = options.domOperation || noop;
194 options.domOperation = function() {
195 options.$$domOperationFired = true;
199 options.$$prepared = true;
204 function applyAnimationStyles(element, options) {
205 applyAnimationFromStyles(element, options);
206 applyAnimationToStyles(element, options);
209 function applyAnimationFromStyles(element, options) {
211 element.css(options.from);
216 function applyAnimationToStyles(element, options) {
218 element.css(options.to);
223 function mergeAnimationOptions(element, target, newOptions) {
224 var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
225 var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
226 var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
228 if (newOptions.preparationClasses) {
229 target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
230 delete newOptions.preparationClasses;
233 // noop is basically when there is no callback; otherwise something has been set
234 var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
236 extend(target, newOptions);
238 // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
239 if (realDomOperation) {
240 target.domOperation = realDomOperation;
243 if (classes.addClass) {
244 target.addClass = classes.addClass;
246 target.addClass = null;
249 if (classes.removeClass) {
250 target.removeClass = classes.removeClass;
252 target.removeClass = null;
258 function resolveElementClasses(existing, toAdd, toRemove) {
260 var REMOVE_CLASS = -1;
263 existing = splitClassesToLookup(existing);
265 toAdd = splitClassesToLookup(toAdd);
266 forEach(toAdd, function(value, key) {
267 flags[key] = ADD_CLASS;
270 toRemove = splitClassesToLookup(toRemove);
271 forEach(toRemove, function(value, key) {
272 flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
280 forEach(flags, function(val, klass) {
282 if (val === ADD_CLASS) {
284 allow = !existing[klass];
285 } else if (val === REMOVE_CLASS) {
286 prop = 'removeClass';
287 allow = existing[klass];
290 if (classes[prop].length) {
291 classes[prop] += ' ';
293 classes[prop] += klass;
297 function splitClassesToLookup(classes) {
298 if (isString(classes)) {
299 classes = classes.split(' ');
303 forEach(classes, function(klass) {
304 // sometimes the split leaves empty string values
305 // incase extra spaces were applied to the options
316 function getDomNode(element) {
317 return (element instanceof angular.element) ? element[0] : element;
320 function applyGeneratedPreparationClasses(element, event, options) {
323 classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
325 if (options.addClass) {
326 classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
328 if (options.removeClass) {
329 classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
331 if (classes.length) {
332 options.preparationClasses = classes;
333 element.addClass(classes);
337 function clearGeneratedClasses(element, options) {
338 if (options.preparationClasses) {
339 element.removeClass(options.preparationClasses);
340 options.preparationClasses = null;
342 if (options.activeClasses) {
343 element.removeClass(options.activeClasses);
344 options.activeClasses = null;
348 function blockTransitions(node, duration) {
349 // we use a negative delay value since it performs blocking
350 // yet it doesn't kill any existing transitions running on the
351 // same element which makes this safe for class-based animations
352 var value = duration ? '-' + duration + 's' : '';
353 applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
354 return [TRANSITION_DELAY_PROP, value];
357 function blockKeyframeAnimations(node, applyBlock) {
358 var value = applyBlock ? 'paused' : '';
359 var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
360 applyInlineStyle(node, [key, value]);
364 function applyInlineStyle(node, styleTuple) {
365 var prop = styleTuple[0];
366 var value = styleTuple[1];
367 node.style[prop] = value;
370 function concatWithSpace(a,b) {
376 var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
379 function scheduler(tasks) {
380 // we make a copy since RAFScheduler mutates the state
381 // of the passed in array variable and this would be difficult
382 // to track down on the outside code
383 queue = queue.concat(tasks);
387 queue = scheduler.queue = [];
389 /* waitUntilQuiet does two things:
390 * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
391 * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
393 * The motivation here is that animation code can request more time from the scheduler
394 * before the next wave runs. This allows for certain DOM properties such as classes to
395 * be resolved in time for the next animation to run.
397 scheduler.waitUntilQuiet = function(fn) {
398 if (cancelFn) cancelFn();
400 cancelFn = $$rAF(function() {
409 function nextTick() {
410 if (!queue.length) return;
412 var items = queue.shift();
413 for (var i = 0; i < items.length; i++) {
419 if (!cancelFn) nextTick();
425 var $$AnimateChildrenDirective = [function() {
426 return function(scope, element, attrs) {
427 var val = attrs.ngAnimateChildren;
428 if (angular.isString(val) && val.length === 0) { //empty attribute
429 element.data(NG_ANIMATE_CHILDREN_DATA, true);
431 attrs.$observe('ngAnimateChildren', function(value) {
432 value = value === 'on' || value === 'true';
433 element.data(NG_ANIMATE_CHILDREN_DATA, value);
439 var ANIMATE_TIMER_KEY = '$$animateCss';
447 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
448 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
449 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
450 * directives to create more complex animations that can be purely driven using CSS code.
452 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
453 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
456 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
457 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
458 * any automatic control over cancelling animations and/or preventing animations from being run on
459 * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
460 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
463 * The example below shows how we can create a folding animation on an element using `ng-if`:
466 * <!-- notice the `fold-animation` CSS class -->
467 * <div ng-if="onOff" class="fold-animation">
468 * This element will go BOOM
470 * <button ng-click="onOff=true">Fold In</button>
473 * Now we create the **JavaScript animation** that will trigger the CSS transition:
476 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
478 * enter: function(element, doneFn) {
479 * var height = element[0].offsetHeight;
480 * return $animateCss(element, {
481 * from: { height:'0px' },
482 * to: { height:height + 'px' },
483 * duration: 1 // one second
490 * ## More Advanced Uses
492 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
493 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
495 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
496 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
497 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
498 * to provide a working animation that will run in CSS.
500 * The example below showcases a more advanced version of the `.fold-animation` from the example above:
503 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
505 * enter: function(element, doneFn) {
506 * var height = element[0].offsetHeight;
507 * return $animateCss(element, {
508 * addClass: 'red large-text pulse-twice',
509 * easing: 'ease-out',
510 * from: { height:'0px' },
511 * to: { height:height + 'px' },
512 * duration: 1 // one second
519 * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
522 * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code,
523 * the CSS classes below will be transitioned despite them being defined as regular CSS classes */
524 * .red { background:red; }
525 * .large-text { font-size:20px; }
527 * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */
529 * animation: 0.5s pulse linear 2;
530 * -webkit-animation: 0.5s pulse linear 2;
534 * from { transform: scale(0.5); }
535 * to { transform: scale(1.5); }
538 * @-webkit-keyframes pulse {
539 * from { -webkit-transform: scale(0.5); }
540 * to { -webkit-transform: scale(1.5); }
544 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
546 * ## How the Options are handled
548 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
549 * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
550 * styles using the `from` and `to` properties.
553 * var animator = $animateCss(element, {
554 * from: { background:'red' },
555 * to: { background:'blue' }
561 * .rotating-animation {
562 * animation:0.5s rotate linear;
563 * -webkit-animation:0.5s rotate linear;
566 * @keyframes rotate {
567 * from { transform: rotate(0deg); }
568 * to { transform: rotate(360deg); }
571 * @-webkit-keyframes rotate {
572 * from { -webkit-transform: rotate(0deg); }
573 * to { -webkit-transform: rotate(360deg); }
577 * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
578 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
579 * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
580 * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
581 * and spread across the transition and keyframe animation.
583 * ## What is returned
585 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
586 * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
587 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
590 * var animator = $animateCss(element, { ... });
593 * Now what do the contents of our `animator` variable look like:
597 * // starts the animation
600 * // ends (aborts) the animation
605 * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
606 * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been
607 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
608 * and that changing them will not reconfigure the parameters of the animation.
610 * ### runner.done() vs runner.then()
611 * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
612 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
613 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
614 * unless you really need a digest to kick off afterwards.
616 * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
617 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
618 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
620 * @param {DOMElement} element the element that will be animated
621 * @param {object} options the animation-related options that will be applied during the animation
623 * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
624 * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
625 * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
626 * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
627 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
628 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
629 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
630 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
631 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
632 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
633 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
634 * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
635 * is provided then the animation will be skipped entirely.
636 * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
637 * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
638 * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
640 * * `stagger` - A numeric time value representing the delay between successively animated elements
641 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
642 * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
643 * * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
644 * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
645 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
646 * the animation is closed. This is useful for when the styles are used purely for the sake of
647 * the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
648 * By default this value is set to `false`.
650 * @return {object} an object with start and end methods and details about the animation.
652 * * `start` - The method to start the animation. This will return a `Promise` when called.
653 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
655 var ONE_SECOND = 1000;
658 var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
659 var CLOSING_TIME_BUFFER = 1.5;
661 var DETECT_CSS_PROPERTIES = {
662 transitionDuration: TRANSITION_DURATION_PROP,
663 transitionDelay: TRANSITION_DELAY_PROP,
664 transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
665 animationDuration: ANIMATION_DURATION_PROP,
666 animationDelay: ANIMATION_DELAY_PROP,
667 animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
670 var DETECT_STAGGER_CSS_PROPERTIES = {
671 transitionDuration: TRANSITION_DURATION_PROP,
672 transitionDelay: TRANSITION_DELAY_PROP,
673 animationDuration: ANIMATION_DURATION_PROP,
674 animationDelay: ANIMATION_DELAY_PROP
677 function getCssKeyframeDurationStyle(duration) {
678 return [ANIMATION_DURATION_PROP, duration + 's'];
681 function getCssDelayStyle(delay, isKeyframeAnimation) {
682 var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
683 return [prop, delay + 's'];
686 function computeCssStyles($window, element, properties) {
687 var styles = Object.create(null);
688 var detectedStyles = $window.getComputedStyle(element) || {};
689 forEach(properties, function(formalStyleName, actualStyleName) {
690 var val = detectedStyles[formalStyleName];
692 var c = val.charAt(0);
694 // only numerical-based values have a negative sign or digit as the first value
695 if (c === '-' || c === '+' || c >= 0) {
696 val = parseMaxTime(val);
699 // by setting this to null in the event that the delay is not set or is set directly as 0
700 // then we can still allow for zegative values to be used later on and not mistake this
701 // value for being greater than any other negative value.
705 styles[actualStyleName] = val;
712 function parseMaxTime(str) {
714 var values = str.split(/\s*,\s*/);
715 forEach(values, function(value) {
716 // it's always safe to consider only second values and omit `ms` values since
717 // getComputedStyle will always handle the conversion for us
718 if (value.charAt(value.length - 1) == 's') {
719 value = value.substring(0, value.length - 1);
721 value = parseFloat(value) || 0;
722 maxValue = maxValue ? Math.max(value, maxValue) : value;
727 function truthyTimingValue(val) {
728 return val === 0 || val != null;
731 function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
732 var style = TRANSITION_PROP;
733 var value = duration + 's';
734 if (applyOnlyDuration) {
735 style += DURATION_KEY;
737 value += ' linear all';
739 return [style, value];
742 function createLocalCacheLookup() {
743 var cache = Object.create(null);
746 cache = Object.create(null);
749 count: function(key) {
750 var entry = cache[key];
751 return entry ? entry.total : 0;
755 var entry = cache[key];
756 return entry && entry.value;
759 put: function(key, value) {
761 cache[key] = { total: 1, value: value };
769 // we do not reassign an already present style value since
770 // if we detect the style property value again we may be
771 // detecting styles that were added via the `from` styles.
772 // We make use of `isDefined` here since an empty string
773 // or null value (which is what getPropertyValue will return
774 // for a non-existing style) will still be marked as a valid
775 // value for the style (a falsy value implies that the style
776 // is to be removed at the end of the animation). If we had a simple
777 // "OR" statement then it would not be enough to catch that.
778 function registerRestorableStyles(backup, node, properties) {
779 forEach(properties, function(prop) {
780 backup[prop] = isDefined(backup[prop])
782 : node.style.getPropertyValue(prop);
786 var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
787 var gcsLookup = createLocalCacheLookup();
788 var gcsStaggerLookup = createLocalCacheLookup();
790 this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
791 '$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
792 function($window, $$jqLite, $$AnimateRunner, $timeout,
793 $$forceReflow, $sniffer, $$rAFScheduler, $animate) {
795 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
797 var parentCounter = 0;
798 function gcsHashFn(node, extraClasses) {
799 var KEY = "$$ngAnimateParentKey";
800 var parentNode = node.parentNode;
801 var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
802 return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
805 function computeCachedCssStyles(node, className, cacheKey, properties) {
806 var timings = gcsLookup.get(cacheKey);
809 timings = computeCssStyles($window, node, properties);
810 if (timings.animationIterationCount === 'infinite') {
811 timings.animationIterationCount = 1;
815 // we keep putting this in multiple times even though the value and the cacheKey are the same
816 // because we're keeping an interal tally of how many duplicate animations are detected.
817 gcsLookup.put(cacheKey, timings);
821 function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
824 // if we have one or more existing matches of matching elements
825 // containing the same parent + CSS styles (which is how cacheKey works)
826 // then staggering is possible
827 if (gcsLookup.count(cacheKey) > 0) {
828 stagger = gcsStaggerLookup.get(cacheKey);
831 var staggerClassName = pendClasses(className, '-stagger');
833 $$jqLite.addClass(node, staggerClassName);
835 stagger = computeCssStyles($window, node, properties);
837 // force the conversion of a null value to zero incase not set
838 stagger.animationDuration = Math.max(stagger.animationDuration, 0);
839 stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
841 $$jqLite.removeClass(node, staggerClassName);
843 gcsStaggerLookup.put(cacheKey, stagger);
847 return stagger || {};
850 var cancelLastRAFRequest;
851 var rafWaitQueue = [];
852 function waitUntilQuiet(callback) {
853 rafWaitQueue.push(callback);
854 $$rAFScheduler.waitUntilQuiet(function() {
856 gcsStaggerLookup.flush();
858 // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
859 // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
860 var pageWidth = $$forceReflow();
862 // we use a for loop to ensure that if the queue is changed
863 // during this looping then it will consider new requests
864 for (var i = 0; i < rafWaitQueue.length; i++) {
865 rafWaitQueue[i](pageWidth);
867 rafWaitQueue.length = 0;
871 function computeTimings(node, className, cacheKey) {
872 var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
873 var aD = timings.animationDelay;
874 var tD = timings.transitionDelay;
875 timings.maxDelay = aD && tD
878 timings.maxDuration = Math.max(
879 timings.animationDuration * timings.animationIterationCount,
880 timings.transitionDuration);
885 return function init(element, options) {
886 var restoreStyles = {};
887 var node = getDomNode(element);
890 || !$animate.enabled()) {
891 return closeAndReturnNoopAnimator();
894 options = prepareAnimationOptions(options);
896 var temporaryStyles = [];
897 var classes = element.attr('class');
898 var styles = packageStyles(options);
901 var animationCompleted;
909 if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
910 return closeAndReturnNoopAnimator();
913 var method = options.event && isArray(options.event)
914 ? options.event.join(' ')
917 var isStructural = method && options.structural;
918 var structuralClassName = '';
919 var addRemoveClassName = '';
922 structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
924 structuralClassName = method;
927 if (options.addClass) {
928 addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
931 if (options.removeClass) {
932 if (addRemoveClassName.length) {
933 addRemoveClassName += ' ';
935 addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
938 // there may be a situation where a structural animation is combined together
939 // with CSS classes that need to resolve before the animation is computed.
940 // However this means that there is no explicit CSS code to block the animation
941 // from happening (by setting 0s none in the class name). If this is the case
942 // we need to apply the classes before the first rAF so we know to continue if
943 // there actually is a detected transition or keyframe animation
944 if (options.applyClassesEarly && addRemoveClassName.length) {
945 applyAnimationClasses(element, options);
948 var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
949 var fullClassName = classes + ' ' + preparationClasses;
950 var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
951 var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
952 var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
954 // there is no way we can trigger an animation if no styles and
955 // no classes are being applied which would then trigger a transition,
956 // unless there a is raw keyframe value that is applied to the element.
957 if (!containsKeyframeAnimation
959 && !preparationClasses) {
960 return closeAndReturnNoopAnimator();
963 var cacheKey, stagger;
964 if (options.stagger > 0) {
965 var staggerVal = parseFloat(options.stagger);
967 transitionDelay: staggerVal,
968 animationDelay: staggerVal,
969 transitionDuration: 0,
973 cacheKey = gcsHashFn(node, fullClassName);
974 stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
977 if (!options.$$skipPreparationClasses) {
978 $$jqLite.addClass(element, preparationClasses);
981 var applyOnlyDuration;
983 if (options.transitionStyle) {
984 var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
985 applyInlineStyle(node, transitionStyle);
986 temporaryStyles.push(transitionStyle);
989 if (options.duration >= 0) {
990 applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
991 var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
993 // we set the duration so that it will be picked up by getComputedStyle later
994 applyInlineStyle(node, durationStyle);
995 temporaryStyles.push(durationStyle);
998 if (options.keyframeStyle) {
999 var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
1000 applyInlineStyle(node, keyframeStyle);
1001 temporaryStyles.push(keyframeStyle);
1004 var itemIndex = stagger
1005 ? options.staggerIndex >= 0
1006 ? options.staggerIndex
1007 : gcsLookup.count(cacheKey)
1010 var isFirst = itemIndex === 0;
1012 // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
1013 // without causing any combination of transitions to kick in. By adding a negative delay value
1014 // it forces the setup class' transition to end immediately. We later then remove the negative
1015 // transition delay to allow for the transition to naturally do it's thing. The beauty here is
1016 // that if there is no transition defined then nothing will happen and this will also allow
1017 // other transitions to be stacked on top of each other without any chopping them out.
1018 if (isFirst && !options.skipBlocking) {
1019 blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
1022 var timings = computeTimings(node, fullClassName, cacheKey);
1023 var relativeDelay = timings.maxDelay;
1024 maxDelay = Math.max(relativeDelay, 0);
1025 maxDuration = timings.maxDuration;
1028 flags.hasTransitions = timings.transitionDuration > 0;
1029 flags.hasAnimations = timings.animationDuration > 0;
1030 flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';
1031 flags.applyTransitionDuration = hasToStyles && (
1032 (flags.hasTransitions && !flags.hasTransitionAll)
1033 || (flags.hasAnimations && !flags.hasTransitions));
1034 flags.applyAnimationDuration = options.duration && flags.hasAnimations;
1035 flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
1036 flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations;
1037 flags.recalculateTimingStyles = addRemoveClassName.length > 0;
1039 if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
1040 maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
1042 if (flags.applyTransitionDuration) {
1043 flags.hasTransitions = true;
1044 timings.transitionDuration = maxDuration;
1045 applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
1046 temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
1049 if (flags.applyAnimationDuration) {
1050 flags.hasAnimations = true;
1051 timings.animationDuration = maxDuration;
1052 temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
1056 if (maxDuration === 0 && !flags.recalculateTimingStyles) {
1057 return closeAndReturnNoopAnimator();
1060 if (options.delay != null) {
1061 var delayStyle = parseFloat(options.delay);
1063 if (flags.applyTransitionDelay) {
1064 temporaryStyles.push(getCssDelayStyle(delayStyle));
1067 if (flags.applyAnimationDelay) {
1068 temporaryStyles.push(getCssDelayStyle(delayStyle, true));
1072 // we need to recalculate the delay value since we used a pre-emptive negative
1073 // delay value and the delay value is required for the final event checking. This
1074 // property will ensure that this will happen after the RAF phase has passed.
1075 if (options.duration == null && timings.transitionDuration > 0) {
1076 flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
1079 maxDelayTime = maxDelay * ONE_SECOND;
1080 maxDurationTime = maxDuration * ONE_SECOND;
1081 if (!options.skipBlocking) {
1082 flags.blockTransition = timings.transitionDuration > 0;
1083 flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
1084 stagger.animationDelay > 0 &&
1085 stagger.animationDuration === 0;
1089 if (options.cleanupStyles) {
1090 registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
1092 applyAnimationFromStyles(element, options);
1095 if (flags.blockTransition || flags.blockKeyframeAnimation) {
1096 applyBlocking(maxDuration);
1097 } else if (!options.skipBlocking) {
1098 blockTransitions(node, false);
1101 // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
1103 $$willAnimate: true,
1106 if (animationClosed) return;
1111 resume: null, //this will be set during the start() phase
1115 runner = new $$AnimateRunner(runnerHost);
1117 waitUntilQuiet(start);
1119 // we don't have access to pause/resume the animation
1120 // since it hasn't run yet. AnimateRunner will therefore
1121 // set noop functions for resume and pause and they will
1122 // later be overridden once the animation is triggered
1131 function cancelFn() {
1135 function close(rejected) { // jshint ignore:line
1136 // if the promise has been called already then we shouldn't close
1137 // the animation again
1138 if (animationClosed || (animationCompleted && animationPaused)) return;
1139 animationClosed = true;
1140 animationPaused = false;
1142 if (!options.$$skipPreparationClasses) {
1143 $$jqLite.removeClass(element, preparationClasses);
1145 $$jqLite.removeClass(element, activeClasses);
1147 blockKeyframeAnimations(node, false);
1148 blockTransitions(node, false);
1150 forEach(temporaryStyles, function(entry) {
1151 // There is only one way to remove inline style properties entirely from elements.
1152 // By using `removeProperty` this works, but we need to convert camel-cased CSS
1153 // styles down to hyphenated values.
1154 node.style[entry[0]] = '';
1157 applyAnimationClasses(element, options);
1158 applyAnimationStyles(element, options);
1160 if (Object.keys(restoreStyles).length) {
1161 forEach(restoreStyles, function(value, prop) {
1162 value ? node.style.setProperty(prop, value)
1163 : node.style.removeProperty(prop);
1167 // the reason why we have this option is to allow a synchronous closing callback
1168 // that is fired as SOON as the animation ends (when the CSS is removed) or if
1169 // the animation never takes off at all. A good example is a leave animation since
1170 // the element must be removed just after the animation is over or else the element
1171 // will appear on screen for one animation frame causing an overbearing flicker.
1172 if (options.onDone) {
1176 // if the preparation function fails then the promise is not setup
1178 runner.complete(!rejected);
1182 function applyBlocking(duration) {
1183 if (flags.blockTransition) {
1184 blockTransitions(node, duration);
1187 if (flags.blockKeyframeAnimation) {
1188 blockKeyframeAnimations(node, !!duration);
1192 function closeAndReturnNoopAnimator() {
1193 runner = new $$AnimateRunner({
1198 // should flush the cache animation
1199 waitUntilQuiet(noop);
1203 $$willAnimate: false,
1212 if (animationClosed) return;
1213 if (!node.parentNode) {
1218 var startTime, events = [];
1220 // even though we only pause keyframe animations here the pause flag
1221 // will still happen when transitions are used. Only the transition will
1222 // not be paused since that is not possible. If the animation ends when
1223 // paused then it will not complete until unpaused or cancelled.
1224 var playPause = function(playAnimation) {
1225 if (!animationCompleted) {
1226 animationPaused = !playAnimation;
1227 if (timings.animationDuration) {
1228 var value = blockKeyframeAnimations(node, animationPaused);
1230 ? temporaryStyles.push(value)
1231 : removeFromArray(temporaryStyles, value);
1233 } else if (animationPaused && playAnimation) {
1234 animationPaused = false;
1239 // checking the stagger duration prevents an accidently cascade of the CSS delay style
1240 // being inherited from the parent. If the transition duration is zero then we can safely
1241 // rely that the delay value is an intential stagger delay style.
1242 var maxStagger = itemIndex > 0
1243 && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
1244 (timings.animationDuration && stagger.animationDuration === 0))
1245 && Math.max(stagger.animationDelay, stagger.transitionDelay);
1247 $timeout(triggerAnimationStart,
1248 Math.floor(maxStagger * itemIndex * ONE_SECOND),
1251 triggerAnimationStart();
1254 // this will decorate the existing promise runner with pause/resume methods
1255 runnerHost.resume = function() {
1259 runnerHost.pause = function() {
1263 function triggerAnimationStart() {
1264 // just incase a stagger animation kicks in when the animation
1265 // itself was cancelled entirely
1266 if (animationClosed) return;
1268 applyBlocking(false);
1270 forEach(temporaryStyles, function(entry) {
1272 var value = entry[1];
1273 node.style[key] = value;
1276 applyAnimationClasses(element, options);
1277 $$jqLite.addClass(element, activeClasses);
1279 if (flags.recalculateTimingStyles) {
1280 fullClassName = node.className + ' ' + preparationClasses;
1281 cacheKey = gcsHashFn(node, fullClassName);
1283 timings = computeTimings(node, fullClassName, cacheKey);
1284 relativeDelay = timings.maxDelay;
1285 maxDelay = Math.max(relativeDelay, 0);
1286 maxDuration = timings.maxDuration;
1288 if (maxDuration === 0) {
1293 flags.hasTransitions = timings.transitionDuration > 0;
1294 flags.hasAnimations = timings.animationDuration > 0;
1297 if (flags.applyAnimationDelay) {
1298 relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
1299 ? parseFloat(options.delay)
1302 maxDelay = Math.max(relativeDelay, 0);
1303 timings.animationDelay = relativeDelay;
1304 delayStyle = getCssDelayStyle(relativeDelay, true);
1305 temporaryStyles.push(delayStyle);
1306 node.style[delayStyle[0]] = delayStyle[1];
1309 maxDelayTime = maxDelay * ONE_SECOND;
1310 maxDurationTime = maxDuration * ONE_SECOND;
1312 if (options.easing) {
1313 var easeProp, easeVal = options.easing;
1314 if (flags.hasTransitions) {
1315 easeProp = TRANSITION_PROP + TIMING_KEY;
1316 temporaryStyles.push([easeProp, easeVal]);
1317 node.style[easeProp] = easeVal;
1319 if (flags.hasAnimations) {
1320 easeProp = ANIMATION_PROP + TIMING_KEY;
1321 temporaryStyles.push([easeProp, easeVal]);
1322 node.style[easeProp] = easeVal;
1326 if (timings.transitionDuration) {
1327 events.push(TRANSITIONEND_EVENT);
1330 if (timings.animationDuration) {
1331 events.push(ANIMATIONEND_EVENT);
1334 startTime = Date.now();
1335 var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
1336 var endTime = startTime + timerTime;
1338 var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
1339 var setupFallbackTimer = true;
1340 if (animationsData.length) {
1341 var currentTimerData = animationsData[0];
1342 setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
1343 if (setupFallbackTimer) {
1344 $timeout.cancel(currentTimerData.timer);
1346 animationsData.push(close);
1350 if (setupFallbackTimer) {
1351 var timer = $timeout(onAnimationExpired, timerTime, false);
1352 animationsData[0] = {
1354 expectedEndTime: endTime
1356 animationsData.push(close);
1357 element.data(ANIMATE_TIMER_KEY, animationsData);
1360 element.on(events.join(' '), onAnimationProgress);
1362 if (options.cleanupStyles) {
1363 registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
1365 applyAnimationToStyles(element, options);
1369 function onAnimationExpired() {
1370 var animationsData = element.data(ANIMATE_TIMER_KEY);
1372 // this will be false in the event that the element was
1373 // removed from the DOM (via a leave animation or something
1375 if (animationsData) {
1376 for (var i = 1; i < animationsData.length; i++) {
1377 animationsData[i]();
1379 element.removeData(ANIMATE_TIMER_KEY);
1383 function onAnimationProgress(event) {
1384 event.stopPropagation();
1385 var ev = event.originalEvent || event;
1386 var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1388 /* Firefox (or possibly just Gecko) likes to not round values up
1389 * when a ms measurement is used for the animation */
1390 var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1392 /* $manualTimeStamp is a mocked timeStamp value which is set
1393 * within browserTrigger(). This is only here so that tests can
1394 * mock animations properly. Real events fallback to event.timeStamp,
1395 * or, if they don't, then a timeStamp is automatically created for them.
1396 * We're checking to see if the timeStamp surpasses the expected delay,
1397 * but we're using elapsedTime instead of the timeStamp on the 2nd
1398 * pre-condition since animations sometimes close off early */
1399 if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1400 // we set this flag to ensure that if the transition is paused then, when resumed,
1401 // the animation will automatically close itself since transitions cannot be paused.
1402 animationCompleted = true;
1411 var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
1412 $$animationProvider.drivers.push('$$animateCssDriver');
1414 var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
1415 var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
1417 var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
1418 var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
1420 function isDocumentFragment(node) {
1421 return node.parentNode && node.parentNode.nodeType === 11;
1424 this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
1425 function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) {
1427 // only browsers that support these properties can render animations
1428 if (!$sniffer.animations && !$sniffer.transitions) return noop;
1430 var bodyNode = $document[0].body;
1431 var rootNode = getDomNode($rootElement);
1433 var rootBodyElement = jqLite(
1434 // this is to avoid using something that exists outside of the body
1435 // we also special case the doc fragement case because our unit test code
1436 // appends the $rootElement to the body after the app has been bootstrapped
1437 isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
1440 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1442 return function initDriverFn(animationDetails) {
1443 return animationDetails.from && animationDetails.to
1444 ? prepareFromToAnchorAnimation(animationDetails.from,
1445 animationDetails.to,
1446 animationDetails.classes,
1447 animationDetails.anchors)
1448 : prepareRegularAnimation(animationDetails);
1451 function filterCssClasses(classes) {
1452 //remove all the `ng-` stuff
1453 return classes.replace(/\bng-\S+\b/g, '');
1456 function getUniqueValues(a, b) {
1457 if (isString(a)) a = a.split(' ');
1458 if (isString(b)) b = b.split(' ');
1459 return a.filter(function(val) {
1460 return b.indexOf(val) === -1;
1464 function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
1465 var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
1466 var startingClasses = filterCssClasses(getClassVal(clone));
1468 outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1469 inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1471 clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
1473 rootBodyElement.append(clone);
1475 var animatorIn, animatorOut = prepareOutAnimation();
1477 // the user may not end up using the `out` animation and
1478 // only making use of the `in` animation or vice-versa.
1479 // In either case we should allow this and not assume the
1480 // animation is over unless both animations are not used.
1482 animatorIn = prepareInAnimation();
1488 var startingAnimator = animatorOut || animatorIn;
1494 var currentAnimation = startingAnimator.start();
1495 currentAnimation.done(function() {
1496 currentAnimation = null;
1498 animatorIn = prepareInAnimation();
1500 currentAnimation = animatorIn.start();
1501 currentAnimation.done(function() {
1502 currentAnimation = null;
1506 return currentAnimation;
1509 // in the event that there is no `in` animation
1514 runner = new $$AnimateRunner({
1522 if (currentAnimation) {
1523 currentAnimation.end();
1529 function calculateAnchorStyles(anchor) {
1532 var coords = getDomNode(anchor).getBoundingClientRect();
1534 // we iterate directly since safari messes up and doesn't return
1535 // all the keys for the coods object when iterated
1536 forEach(['width','height','top','left'], function(key) {
1537 var value = coords[key];
1540 value += bodyNode.scrollTop;
1543 value += bodyNode.scrollLeft;
1546 styles[key] = Math.floor(value) + 'px';
1551 function prepareOutAnimation() {
1552 var animator = $animateCss(clone, {
1553 addClass: NG_OUT_ANCHOR_CLASS_NAME,
1555 from: calculateAnchorStyles(outAnchor)
1558 // read the comment within `prepareRegularAnimation` to understand
1559 // why this check is necessary
1560 return animator.$$willAnimate ? animator : null;
1563 function getClassVal(element) {
1564 return element.attr('class') || '';
1567 function prepareInAnimation() {
1568 var endingClasses = filterCssClasses(getClassVal(inAnchor));
1569 var toAdd = getUniqueValues(endingClasses, startingClasses);
1570 var toRemove = getUniqueValues(startingClasses, endingClasses);
1572 var animator = $animateCss(clone, {
1573 to: calculateAnchorStyles(inAnchor),
1574 addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
1575 removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
1579 // read the comment within `prepareRegularAnimation` to understand
1580 // why this check is necessary
1581 return animator.$$willAnimate ? animator : null;
1586 outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1587 inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1591 function prepareFromToAnchorAnimation(from, to, classes, anchors) {
1592 var fromAnimation = prepareRegularAnimation(from, noop);
1593 var toAnimation = prepareRegularAnimation(to, noop);
1595 var anchorAnimations = [];
1596 forEach(anchors, function(anchor) {
1597 var outElement = anchor['out'];
1598 var inElement = anchor['in'];
1599 var animator = prepareAnchoredAnimation(classes, outElement, inElement);
1601 anchorAnimations.push(animator);
1605 // no point in doing anything when there are no elements to animate
1606 if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
1610 var animationRunners = [];
1612 if (fromAnimation) {
1613 animationRunners.push(fromAnimation.start());
1617 animationRunners.push(toAnimation.start());
1620 forEach(anchorAnimations, function(animation) {
1621 animationRunners.push(animation.start());
1624 var runner = new $$AnimateRunner({
1626 cancel: endFn // CSS-driven animations cannot be cancelled, only ended
1629 $$AnimateRunner.all(animationRunners, function(status) {
1630 runner.complete(status);
1636 forEach(animationRunners, function(runner) {
1644 function prepareRegularAnimation(animationDetails) {
1645 var element = animationDetails.element;
1646 var options = animationDetails.options || {};
1648 if (animationDetails.structural) {
1649 options.event = animationDetails.event;
1650 options.structural = true;
1651 options.applyClassesEarly = true;
1653 // we special case the leave animation since we want to ensure that
1654 // the element is removed as soon as the animation is over. Otherwise
1655 // a flicker might appear or the element may not be removed at all
1656 if (animationDetails.event === 'leave') {
1657 options.onDone = options.domOperation;
1661 // We assign the preparationClasses as the actual animation event since
1662 // the internals of $animateCss will just suffix the event token values
1663 // with `-active` to trigger the animation.
1664 if (options.preparationClasses) {
1665 options.event = concatWithSpace(options.event, options.preparationClasses);
1668 var animator = $animateCss(element, options);
1670 // the driver lookup code inside of $$animation attempts to spawn a
1671 // driver one by one until a driver returns a.$$willAnimate animator object.
1672 // $animateCss will always return an object, however, it will pass in
1673 // a flag as a hint as to whether an animation was detected or not
1674 return animator.$$willAnimate ? animator : null;
1679 // TODO(matsko): use caching here to speed things up for detection
1680 // TODO(matsko): add documentation
1683 var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1684 this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
1685 function($injector, $$AnimateRunner, $$jqLite) {
1687 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1688 // $animateJs(element, 'enter');
1689 return function(element, event, classes, options) {
1690 // the `classes` argument is optional and if it is not used
1691 // then the classes will be resolved from the element's className
1692 // property as well as options.addClass/options.removeClass.
1693 if (arguments.length === 3 && isObject(classes)) {
1698 options = prepareAnimationOptions(options);
1700 classes = element.attr('class') || '';
1701 if (options.addClass) {
1702 classes += ' ' + options.addClass;
1704 if (options.removeClass) {
1705 classes += ' ' + options.removeClass;
1709 var classesToAdd = options.addClass;
1710 var classesToRemove = options.removeClass;
1712 // the lookupAnimations function returns a series of animation objects that are
1713 // matched up with one or more of the CSS classes. These animation objects are
1714 // defined via the module.animation factory function. If nothing is detected then
1715 // we don't return anything which then makes $animation query the next driver.
1716 var animations = lookupAnimations(classes);
1718 if (animations.length) {
1719 var afterFn, beforeFn;
1720 if (event == 'leave') {
1722 afterFn = 'afterLeave'; // TODO(matsko): get rid of this
1724 beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
1728 if (event !== 'enter' && event !== 'move') {
1729 before = packageAnimations(element, event, options, animations, beforeFn);
1731 after = packageAnimations(element, event, options, animations, afterFn);
1734 // no matching animations
1735 if (!before && !after) return;
1737 function applyOptions() {
1738 options.domOperation();
1739 applyAnimationClasses(element, options);
1744 var closeActiveAnimations;
1748 chain.push(function(fn) {
1749 closeActiveAnimations = before(fn);
1754 chain.push(function(fn) {
1763 chain.push(function(fn) {
1764 closeActiveAnimations = after(fn);
1768 var animationClosed = false;
1769 var runner = new $$AnimateRunner({
1773 cancel: function() {
1774 endAnimations(true);
1778 $$AnimateRunner.chain(chain, onComplete);
1781 function onComplete(success) {
1782 animationClosed = true;
1784 applyAnimationStyles(element, options);
1785 runner.complete(success);
1788 function endAnimations(cancelled) {
1789 if (!animationClosed) {
1790 (closeActiveAnimations || noop)(cancelled);
1791 onComplete(cancelled);
1797 function executeAnimationFn(fn, element, event, options, onDone) {
1801 args = [element, options.from, options.to, onDone];
1805 args = [element, classesToAdd, classesToRemove, onDone];
1809 args = [element, classesToAdd, onDone];
1813 args = [element, classesToRemove, onDone];
1817 args = [element, onDone];
1823 var value = fn.apply(fn, args);
1825 if (isFunction(value.start)) {
1826 value = value.start();
1829 if (value instanceof $$AnimateRunner) {
1831 } else if (isFunction(value)) {
1832 // optional onEnd / onCancel callback
1840 function groupEventedAnimations(element, event, options, animations, fnName) {
1841 var operations = [];
1842 forEach(animations, function(ani) {
1843 var animation = ani[fnName];
1844 if (!animation) return;
1846 // note that all of these animations will run in parallel
1847 operations.push(function() {
1851 var resolved = false;
1852 var onAnimationComplete = function(rejected) {
1855 (endProgressCb || noop)(rejected);
1856 runner.complete(!rejected);
1860 runner = new $$AnimateRunner({
1862 onAnimationComplete();
1864 cancel: function() {
1865 onAnimationComplete(true);
1869 endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
1870 var cancelled = result === false;
1871 onAnimationComplete(cancelled);
1881 function packageAnimations(element, event, options, animations, fnName) {
1882 var operations = groupEventedAnimations(element, event, options, animations, fnName);
1883 if (operations.length === 0) {
1885 if (fnName === 'beforeSetClass') {
1886 a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
1887 b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
1888 } else if (fnName === 'setClass') {
1889 a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
1890 b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
1894 operations = operations.concat(a);
1897 operations = operations.concat(b);
1901 if (operations.length === 0) return;
1903 // TODO(matsko): add documentation
1904 return function startAnimation(callback) {
1906 if (operations.length) {
1907 forEach(operations, function(animateFn) {
1908 runners.push(animateFn());
1912 runners.length ? $$AnimateRunner.all(runners, callback) : callback();
1914 return function endFn(reject) {
1915 forEach(runners, function(runner) {
1916 reject ? runner.cancel() : runner.end();
1923 function lookupAnimations(classes) {
1924 classes = isArray(classes) ? classes : classes.split(' ');
1925 var matches = [], flagMap = {};
1926 for (var i=0; i < classes.length; i++) {
1927 var klass = classes[i],
1928 animationFactory = $animateProvider.$$registeredAnimations[klass];
1929 if (animationFactory && !flagMap[klass]) {
1930 matches.push($injector.get(animationFactory));
1931 flagMap[klass] = true;
1939 var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
1940 $$animationProvider.drivers.push('$$animateJsDriver');
1941 this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
1942 return function initDriverFn(animationDetails) {
1943 if (animationDetails.from && animationDetails.to) {
1944 var fromAnimation = prepareAnimation(animationDetails.from);
1945 var toAnimation = prepareAnimation(animationDetails.to);
1946 if (!fromAnimation && !toAnimation) return;
1950 var animationRunners = [];
1952 if (fromAnimation) {
1953 animationRunners.push(fromAnimation.start());
1957 animationRunners.push(toAnimation.start());
1960 $$AnimateRunner.all(animationRunners, done);
1962 var runner = new $$AnimateRunner({
1963 end: endFnFactory(),
1964 cancel: endFnFactory()
1969 function endFnFactory() {
1971 forEach(animationRunners, function(runner) {
1972 // at this point we cannot cancel animations for groups just yet. 1.5+
1978 function done(status) {
1979 runner.complete(status);
1984 return prepareAnimation(animationDetails);
1988 function prepareAnimation(animationDetails) {
1989 // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
1990 var element = animationDetails.element;
1991 var event = animationDetails.event;
1992 var options = animationDetails.options;
1993 var classes = animationDetails.classes;
1994 return $$animateJs(element, event, classes, options);
1999 var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
2000 var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
2001 var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2002 var PRE_DIGEST_STATE = 1;
2003 var RUNNING_STATE = 2;
2005 var rules = this.rules = {
2011 function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
2012 return rules[ruleType].some(function(fn) {
2013 return fn(element, currentAnimation, previousAnimation);
2017 function hasAnimationClasses(options, and) {
2018 options = options || {};
2019 var a = (options.addClass || '').length > 0;
2020 var b = (options.removeClass || '').length > 0;
2021 return and ? a && b : a || b;
2024 rules.join.push(function(element, newAnimation, currentAnimation) {
2025 // if the new animation is class-based then we can just tack that on
2026 return !newAnimation.structural && hasAnimationClasses(newAnimation.options);
2029 rules.skip.push(function(element, newAnimation, currentAnimation) {
2030 // there is no need to animate anything if no classes are being added and
2031 // there is no structural animation that will be triggered
2032 return !newAnimation.structural && !hasAnimationClasses(newAnimation.options);
2035 rules.skip.push(function(element, newAnimation, currentAnimation) {
2036 // why should we trigger a new structural animation if the element will
2037 // be removed from the DOM anyway?
2038 return currentAnimation.event == 'leave' && newAnimation.structural;
2041 rules.skip.push(function(element, newAnimation, currentAnimation) {
2042 // if there is an ongoing current animation then don't even bother running the class-based animation
2043 return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
2046 rules.cancel.push(function(element, newAnimation, currentAnimation) {
2047 // there can never be two structural animations running at the same time
2048 return currentAnimation.structural && newAnimation.structural;
2051 rules.cancel.push(function(element, newAnimation, currentAnimation) {
2052 // if the previous animation is already running, but the new animation will
2053 // be triggered, but the new animation is structural
2054 return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
2057 rules.cancel.push(function(element, newAnimation, currentAnimation) {
2058 var nO = newAnimation.options;
2059 var cO = currentAnimation.options;
2061 // if the exact same CSS class is added/removed then it's safe to cancel it
2062 return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
2065 this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
2066 '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
2067 function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
2068 $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
2070 var activeAnimationsLookup = new $$HashMap();
2071 var disabledElementsLookup = new $$HashMap();
2072 var animationsEnabled = null;
2074 function postDigestTaskFactory() {
2075 var postDigestCalled = false;
2076 return function(fn) {
2077 // we only issue a call to postDigest before
2078 // it has first passed. This prevents any callbacks
2079 // from not firing once the animation has completed
2080 // since it will be out of the digest cycle.
2081 if (postDigestCalled) {
2084 $rootScope.$$postDigest(function() {
2085 postDigestCalled = true;
2092 // Wait until all directive and route-related templates are downloaded and
2093 // compiled. The $templateRequest.totalPendingRequests variable keeps track of
2094 // all of the remote templates being currently downloaded. If there are no
2095 // templates currently downloading then the watcher will still fire anyway.
2096 var deregisterWatch = $rootScope.$watch(
2097 function() { return $templateRequest.totalPendingRequests === 0; },
2099 if (!isEmpty) return;
2102 // Now that all templates have been downloaded, $animate will wait until
2103 // the post digest queue is empty before enabling animations. By having two
2104 // calls to $postDigest calls we can ensure that the flag is enabled at the
2105 // very end of the post digest queue. Since all of the animations in $animate
2106 // use $postDigest, it's important that the code below executes at the end.
2107 // This basically means that the page is fully downloaded and compiled before
2108 // any animations are triggered.
2109 $rootScope.$$postDigest(function() {
2110 $rootScope.$$postDigest(function() {
2111 // we check for null directly in the event that the application already called
2112 // .enabled() with whatever arguments that it provided it with
2113 if (animationsEnabled === null) {
2114 animationsEnabled = true;
2121 var callbackRegistry = {};
2123 // remember that the classNameFilter is set during the provider/config
2124 // stage therefore we can optimize here and setup a helper function
2125 var classNameFilter = $animateProvider.classNameFilter();
2126 var isAnimatableClassName = !classNameFilter
2127 ? function() { return true; }
2128 : function(className) {
2129 return classNameFilter.test(className);
2132 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2134 function normalizeAnimationOptions(element, options) {
2135 return mergeAnimationOptions(element, options, {});
2138 function findCallbacks(parent, element, event) {
2139 var targetNode = getDomNode(element);
2140 var targetParentNode = getDomNode(parent);
2143 var entries = callbackRegistry[event];
2145 forEach(entries, function(entry) {
2146 if (entry.node.contains(targetNode)) {
2147 matches.push(entry.callback);
2148 } else if (event === 'leave' && entry.node.contains(targetParentNode)) {
2149 matches.push(entry.callback);
2158 on: function(event, container, callback) {
2159 var node = extractElementNode(container);
2160 callbackRegistry[event] = callbackRegistry[event] || [];
2161 callbackRegistry[event].push({
2167 off: function(event, container, callback) {
2168 var entries = callbackRegistry[event];
2169 if (!entries) return;
2171 callbackRegistry[event] = arguments.length === 1
2173 : filterFromRegistry(entries, container, callback);
2175 function filterFromRegistry(list, matchContainer, matchCallback) {
2176 var containerNode = extractElementNode(matchContainer);
2177 return list.filter(function(entry) {
2178 var isMatch = entry.node === containerNode &&
2179 (!matchCallback || entry.callback === matchCallback);
2185 pin: function(element, parentElement) {
2186 assertArg(isElement(element), 'element', 'not an element');
2187 assertArg(isElement(parentElement), 'parentElement', 'not an element');
2188 element.data(NG_ANIMATE_PIN_DATA, parentElement);
2191 push: function(element, event, options, domOperation) {
2192 options = options || {};
2193 options.domOperation = domOperation;
2194 return queueAnimation(element, event, options);
2197 // this method has four signatures:
2198 // () - global getter
2199 // (bool) - global setter
2200 // (element) - element getter
2201 // (element, bool) - element setter<F37>
2202 enabled: function(element, bool) {
2203 var argCount = arguments.length;
2205 if (argCount === 0) {
2206 // () - Global getter
2207 bool = !!animationsEnabled;
2209 var hasElement = isElement(element);
2212 // (bool) - Global setter
2213 bool = animationsEnabled = !!element;
2215 var node = getDomNode(element);
2216 var recordExists = disabledElementsLookup.get(node);
2218 if (argCount === 1) {
2219 // (element) - Element getter
2220 bool = !recordExists;
2222 // (element, bool) - Element setter
2225 disabledElementsLookup.put(node, true);
2226 } else if (recordExists) {
2227 disabledElementsLookup.remove(node);
2237 function queueAnimation(element, event, options) {
2239 element = stripCommentsFromElement(element);
2241 node = getDomNode(element);
2242 parent = element.parent();
2245 options = prepareAnimationOptions(options);
2247 // we create a fake runner with a working promise.
2248 // These methods will become available after the digest has passed
2249 var runner = new $$AnimateRunner();
2251 // this is used to trigger callbacks in postDigest mode
2252 var runInNextPostDigestOrNow = postDigestTaskFactory();
2254 if (isArray(options.addClass)) {
2255 options.addClass = options.addClass.join(' ');
2258 if (options.addClass && !isString(options.addClass)) {
2259 options.addClass = null;
2262 if (isArray(options.removeClass)) {
2263 options.removeClass = options.removeClass.join(' ');
2266 if (options.removeClass && !isString(options.removeClass)) {
2267 options.removeClass = null;
2270 if (options.from && !isObject(options.from)) {
2271 options.from = null;
2274 if (options.to && !isObject(options.to)) {
2278 // there are situations where a directive issues an animation for
2279 // a jqLite wrapper that contains only comment nodes... If this
2280 // happens then there is no way we can perform an animation
2286 var className = [node.className, options.addClass, options.removeClass].join(' ');
2287 if (!isAnimatableClassName(className)) {
2292 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2294 // this is a hard disable of all animations for the application or on
2295 // the element itself, therefore there is no need to continue further
2296 // past this point if not enabled
2297 var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node);
2298 var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2299 var hasExistingAnimation = !!existingAnimation.state;
2301 // there is no point in traversing the same collection of parent ancestors if a followup
2302 // animation will be run on the same element that already did all that checking work
2303 if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
2304 skipAnimations = !areAnimationsAllowed(element, parent, event);
2307 if (skipAnimations) {
2313 closeChildAnimations(element);
2316 var newAnimation = {
2317 structural: isStructural,
2325 if (hasExistingAnimation) {
2326 var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
2327 if (skipAnimationFlag) {
2328 if (existingAnimation.state === RUNNING_STATE) {
2332 mergeAnimationOptions(element, existingAnimation.options, options);
2333 return existingAnimation.runner;
2337 var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
2338 if (cancelAnimationFlag) {
2339 if (existingAnimation.state === RUNNING_STATE) {
2340 // this will end the animation right away and it is safe
2341 // to do so since the animation is already running and the
2342 // runner callback code will run in async
2343 existingAnimation.runner.end();
2344 } else if (existingAnimation.structural) {
2345 // this means that the animation is queued into a digest, but
2346 // hasn't started yet. Therefore it is safe to run the close
2347 // method which will call the runner methods in async.
2348 existingAnimation.close();
2350 // this will merge the new animation options into existing animation options
2351 mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2352 return existingAnimation.runner;
2355 // a joined animation means that this animation will take over the existing one
2356 // so an example would involve a leave animation taking over an enter. Then when
2357 // the postDigest kicks in the enter will be ignored.
2358 var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
2359 if (joinAnimationFlag) {
2360 if (existingAnimation.state === RUNNING_STATE) {
2361 normalizeAnimationOptions(element, options);
2363 applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
2365 event = newAnimation.event = existingAnimation.event;
2366 options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2368 //we return the same runner since only the option values of this animation will
2369 //be fed into the `existingAnimation`.
2370 return existingAnimation.runner;
2375 // normalization in this case means that it removes redundant CSS classes that
2376 // already exist (addClass) or do not exist (removeClass) on the element
2377 normalizeAnimationOptions(element, options);
2380 // when the options are merged and cleaned up we may end up not having to do
2381 // an animation at all, therefore we should check this before issuing a post
2382 // digest callback. Structural animations will always run no matter what.
2383 var isValidAnimation = newAnimation.structural;
2384 if (!isValidAnimation) {
2385 // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
2386 isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
2387 || hasAnimationClasses(newAnimation.options);
2390 if (!isValidAnimation) {
2392 clearElementAnimationState(element);
2396 // the counter keeps track of cancelled animations
2397 var counter = (existingAnimation.counter || 0) + 1;
2398 newAnimation.counter = counter;
2400 markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
2402 $rootScope.$$postDigest(function() {
2403 var animationDetails = activeAnimationsLookup.get(node);
2404 var animationCancelled = !animationDetails;
2405 animationDetails = animationDetails || {};
2407 // if addClass/removeClass is called before something like enter then the
2408 // registered parent element may not be present. The code below will ensure
2409 // that a final value for parent element is obtained
2410 var parentElement = element.parent() || [];
2412 // animate/structural/class-based animations all have requirements. Otherwise there
2413 // is no point in performing an animation. The parent node must also be set.
2414 var isValidAnimation = parentElement.length > 0
2415 && (animationDetails.event === 'animate'
2416 || animationDetails.structural
2417 || hasAnimationClasses(animationDetails.options));
2419 // this means that the previous animation was cancelled
2420 // even if the follow-up animation is the same event
2421 if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
2422 // if another animation did not take over then we need
2423 // to make sure that the domOperation and options are
2424 // handled accordingly
2425 if (animationCancelled) {
2426 applyAnimationClasses(element, options);
2427 applyAnimationStyles(element, options);
2430 // if the event changed from something like enter to leave then we do
2431 // it, otherwise if it's the same then the end result will be the same too
2432 if (animationCancelled || (isStructural && animationDetails.event !== event)) {
2433 options.domOperation();
2437 // in the event that the element animation was not cancelled or a follow-up animation
2438 // isn't allowed to animate from here then we need to clear the state of the element
2439 // so that any future animations won't read the expired animation data.
2440 if (!isValidAnimation) {
2441 clearElementAnimationState(element);
2447 // this combined multiple class to addClass / removeClass into a setClass event
2448 // so long as a structural event did not take over the animation
2449 event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)
2451 : animationDetails.event;
2453 markElementAnimationState(element, RUNNING_STATE);
2454 var realRunner = $$animation(element, event, animationDetails.options);
2456 realRunner.done(function(status) {
2458 var animationDetails = activeAnimationsLookup.get(node);
2459 if (animationDetails && animationDetails.counter === counter) {
2460 clearElementAnimationState(getDomNode(element));
2462 notifyProgress(runner, event, 'close', {});
2465 // this will update the runner's flow-control events based on
2466 // the `realRunner` object.
2467 runner.setHost(realRunner);
2468 notifyProgress(runner, event, 'start', {});
2473 function notifyProgress(runner, event, phase, data) {
2474 runInNextPostDigestOrNow(function() {
2475 var callbacks = findCallbacks(parent, element, event);
2476 if (callbacks.length) {
2477 // do not optimize this call here to RAF because
2478 // we don't know how heavy the callback code here will
2479 // be and if this code is buffered then this can
2480 // lead to a performance regression.
2482 forEach(callbacks, function(callback) {
2483 callback(element, phase, data);
2488 runner.progress(event, phase, data);
2491 function close(reject) { // jshint ignore:line
2492 clearGeneratedClasses(element, options);
2493 applyAnimationClasses(element, options);
2494 applyAnimationStyles(element, options);
2495 options.domOperation();
2496 runner.complete(!reject);
2500 function closeChildAnimations(element) {
2501 var node = getDomNode(element);
2502 var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
2503 forEach(children, function(child) {
2504 var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
2505 var animationDetails = activeAnimationsLookup.get(child);
2508 animationDetails.runner.end();
2510 case PRE_DIGEST_STATE:
2511 if (animationDetails) {
2512 activeAnimationsLookup.remove(child);
2519 function clearElementAnimationState(element) {
2520 var node = getDomNode(element);
2521 node.removeAttribute(NG_ANIMATE_ATTR_NAME);
2522 activeAnimationsLookup.remove(node);
2525 function isMatchingElement(nodeOrElmA, nodeOrElmB) {
2526 return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
2529 function areAnimationsAllowed(element, parentElement, event) {
2530 var bodyElement = jqLite($document[0].body);
2531 var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
2532 var rootElementDetected = isMatchingElement(element, $rootElement);
2533 var parentAnimationDetected = false;
2534 var animateChildren;
2536 var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2538 parentElement = parentHost;
2541 while (parentElement && parentElement.length) {
2542 if (!rootElementDetected) {
2543 // angular doesn't want to attempt to animate elements outside of the application
2544 // therefore we need to ensure that the rootElement is an ancestor of the current element
2545 rootElementDetected = isMatchingElement(parentElement, $rootElement);
2548 var parentNode = parentElement[0];
2549 if (parentNode.nodeType !== ELEMENT_NODE) {
2550 // no point in inspecting the #document element
2554 var details = activeAnimationsLookup.get(parentNode) || {};
2555 // either an enter, leave or move animation will commence
2556 // therefore we can't allow any animations to take place
2557 // but if a parent animation is class-based then that's ok
2558 if (!parentAnimationDetected) {
2559 parentAnimationDetected = details.structural || disabledElementsLookup.get(parentNode);
2562 if (isUndefined(animateChildren) || animateChildren === true) {
2563 var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
2564 if (isDefined(value)) {
2565 animateChildren = value;
2569 // there is no need to continue traversing at this point
2570 if (parentAnimationDetected && animateChildren === false) break;
2572 if (!rootElementDetected) {
2573 // angular doesn't want to attempt to animate elements outside of the application
2574 // therefore we need to ensure that the rootElement is an ancestor of the current element
2575 rootElementDetected = isMatchingElement(parentElement, $rootElement);
2576 if (!rootElementDetected) {
2577 parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2579 parentElement = parentHost;
2584 if (!bodyElementDetected) {
2585 // we also need to ensure that the element is or will be apart of the body element
2586 // otherwise it is pointless to even issue an animation to be rendered
2587 bodyElementDetected = isMatchingElement(parentElement, bodyElement);
2590 parentElement = parentElement.parent();
2593 var allowAnimation = !parentAnimationDetected || animateChildren;
2594 return allowAnimation && rootElementDetected && bodyElementDetected;
2597 function markElementAnimationState(element, state, details) {
2598 details = details || {};
2599 details.state = state;
2601 var node = getDomNode(element);
2602 node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
2604 var oldValue = activeAnimationsLookup.get(node);
2605 var newValue = oldValue
2606 ? extend(oldValue, details)
2608 activeAnimationsLookup.put(node, newValue);
2613 var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
2616 function waitForTick(fn) {
2618 if (waitQueue.length > 1) return;
2620 for (var i = 0; i < waitQueue.length; i++) {
2629 waitForTick(function() {
2632 return function(callback) {
2633 passed ? callback() : waitForTick(callback);
2638 var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
2639 function($q, $sniffer, $$animateAsyncRun) {
2641 var INITIAL_STATE = 0;
2642 var DONE_PENDING_STATE = 1;
2643 var DONE_COMPLETE_STATE = 2;
2645 AnimateRunner.chain = function(chain, callback) {
2650 if (index === chain.length) {
2655 chain[index](function(response) {
2656 if (response === false) {
2666 AnimateRunner.all = function(runners, callback) {
2669 forEach(runners, function(runner) {
2670 runner.done(onProgress);
2673 function onProgress(response) {
2674 status = status && response;
2675 if (++count === runners.length) {
2681 function AnimateRunner(host) {
2684 this._doneCallbacks = [];
2685 this._runInAnimationFrame = $$animateAsyncRun();
2689 AnimateRunner.prototype = {
2690 setHost: function(host) {
2691 this.host = host || {};
2694 done: function(fn) {
2695 if (this._state === DONE_COMPLETE_STATE) {
2698 this._doneCallbacks.push(fn);
2704 getPromise: function() {
2705 if (!this.promise) {
2707 this.promise = $q(function(resolve, reject) {
2708 self.done(function(status) {
2709 status === false ? reject() : resolve();
2713 return this.promise;
2716 then: function(resolveHandler, rejectHandler) {
2717 return this.getPromise().then(resolveHandler, rejectHandler);
2720 'catch': function(handler) {
2721 return this.getPromise()['catch'](handler);
2724 'finally': function(handler) {
2725 return this.getPromise()['finally'](handler);
2729 if (this.host.pause) {
2734 resume: function() {
2735 if (this.host.resume) {
2741 if (this.host.end) {
2744 this._resolve(true);
2747 cancel: function() {
2748 if (this.host.cancel) {
2751 this._resolve(false);
2754 complete: function(response) {
2756 if (self._state === INITIAL_STATE) {
2757 self._state = DONE_PENDING_STATE;
2758 self._runInAnimationFrame(function() {
2759 self._resolve(response);
2764 _resolve: function(response) {
2765 if (this._state !== DONE_COMPLETE_STATE) {
2766 forEach(this._doneCallbacks, function(fn) {
2769 this._doneCallbacks.length = 0;
2770 this._state = DONE_COMPLETE_STATE;
2775 return AnimateRunner;
2778 var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2779 var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2781 var drivers = this.drivers = [];
2783 var RUNNER_STORAGE_KEY = '$$animationRunner';
2785 function setRunner(element, runner) {
2786 element.data(RUNNER_STORAGE_KEY, runner);
2789 function removeRunner(element) {
2790 element.removeData(RUNNER_STORAGE_KEY);
2793 function getRunner(element) {
2794 return element.data(RUNNER_STORAGE_KEY);
2797 this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
2798 function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
2800 var animationQueue = [];
2801 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2803 function sortAnimations(animations) {
2804 var tree = { children: [] };
2805 var i, lookup = new $$HashMap();
2807 // this is done first beforehand so that the hashmap
2808 // is filled with a list of the elements that will be animated
2809 for (i = 0; i < animations.length; i++) {
2810 var animation = animations[i];
2811 lookup.put(animation.domNode, animations[i] = {
2812 domNode: animation.domNode,
2818 for (i = 0; i < animations.length; i++) {
2819 processNode(animations[i]);
2822 return flatten(tree);
2824 function processNode(entry) {
2825 if (entry.processed) return entry;
2826 entry.processed = true;
2828 var elementNode = entry.domNode;
2829 var parentNode = elementNode.parentNode;
2830 lookup.put(elementNode, entry);
2833 while (parentNode) {
2834 parentEntry = lookup.get(parentNode);
2836 if (!parentEntry.processed) {
2837 parentEntry = processNode(parentEntry);
2841 parentNode = parentNode.parentNode;
2844 (parentEntry || tree).children.push(entry);
2848 function flatten(tree) {
2853 for (i = 0; i < tree.children.length; i++) {
2854 queue.push(tree.children[i]);
2857 var remainingLevelEntries = queue.length;
2858 var nextLevelEntries = 0;
2861 for (i = 0; i < queue.length; i++) {
2862 var entry = queue[i];
2863 if (remainingLevelEntries <= 0) {
2864 remainingLevelEntries = nextLevelEntries;
2865 nextLevelEntries = 0;
2870 entry.children.forEach(function(childEntry) {
2872 queue.push(childEntry);
2874 remainingLevelEntries--;
2885 // TODO(matsko): document the signature in a better way
2886 return function(element, event, options) {
2887 options = prepareAnimationOptions(options);
2888 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2890 // there is no animation at the current moment, however
2891 // these runner methods will get later updated with the
2892 // methods leading into the driver's end/cancel methods
2893 // for now they just stop the animation from starting
2894 var runner = new $$AnimateRunner({
2895 end: function() { close(); },
2896 cancel: function() { close(true); }
2899 if (!drivers.length) {
2904 setRunner(element, runner);
2906 var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
2907 var tempClasses = options.tempClasses;
2909 classes += ' ' + tempClasses;
2910 options.tempClasses = null;
2913 animationQueue.push({
2914 // this data is used by the postDigest code and passed into
2915 // the driver step function
2919 structural: isStructural,
2921 beforeStart: beforeStart,
2925 element.on('$destroy', handleDestroyedElement);
2927 // we only want there to be one function called within the post digest
2928 // block. This way we can group animations for all the animations that
2929 // were apart of the same postDigest flush call.
2930 if (animationQueue.length > 1) return runner;
2932 $rootScope.$$postDigest(function() {
2933 var animations = [];
2934 forEach(animationQueue, function(entry) {
2935 // the element was destroyed early on which removed the runner
2936 // form its storage. This means we can't animate this element
2937 // at all and it already has been closed due to destruction.
2938 if (getRunner(entry.element)) {
2939 animations.push(entry);
2945 // now any future animations will be in another postDigest
2946 animationQueue.length = 0;
2948 var groupedAnimations = groupAnimations(animations);
2949 var toBeSortedAnimations = [];
2951 forEach(groupedAnimations, function(animationEntry) {
2952 toBeSortedAnimations.push({
2953 domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
2954 fn: function triggerAnimationStart() {
2955 // it's important that we apply the `ng-animate` CSS class and the
2956 // temporary classes before we do any driver invoking since these
2957 // CSS classes may be required for proper CSS detection.
2958 animationEntry.beforeStart();
2960 var startAnimationFn, closeFn = animationEntry.close;
2962 // in the event that the element was removed before the digest runs or
2963 // during the RAF sequencing then we should not trigger the animation.
2964 var targetElement = animationEntry.anchors
2965 ? (animationEntry.from.element || animationEntry.to.element)
2966 : animationEntry.element;
2968 if (getRunner(targetElement)) {
2969 var operation = invokeFirstDriver(animationEntry);
2971 startAnimationFn = operation.start;
2975 if (!startAnimationFn) {
2978 var animationRunner = startAnimationFn();
2979 animationRunner.done(function(status) {
2982 updateAnimationRunners(animationEntry, animationRunner);
2988 // we need to sort each of the animations in order of parent to child
2989 // relationships. This ensures that the child classes are applied at the
2991 $$rAFScheduler(sortAnimations(toBeSortedAnimations));
2996 // TODO(matsko): change to reference nodes
2997 function getAnchorNodes(node) {
2998 var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
2999 var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
3001 : node.querySelectorAll(SELECTOR);
3003 forEach(items, function(node) {
3004 var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
3005 if (attr && attr.length) {
3012 function groupAnimations(animations) {
3013 var preparedAnimations = [];
3015 forEach(animations, function(animation, index) {
3016 var element = animation.element;
3017 var node = getDomNode(element);
3018 var event = animation.event;
3019 var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
3020 var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
3022 if (anchorNodes.length) {
3023 var direction = enterOrMove ? 'to' : 'from';
3025 forEach(anchorNodes, function(anchor) {
3026 var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
3027 refLookup[key] = refLookup[key] || {};
3028 refLookup[key][direction] = {
3030 element: jqLite(anchor)
3034 preparedAnimations.push(animation);
3038 var usedIndicesLookup = {};
3039 var anchorGroups = {};
3040 forEach(refLookup, function(operations, key) {
3041 var from = operations.from;
3042 var to = operations.to;
3045 // only one of these is set therefore we can't have an
3046 // anchor animation since all three pieces are required
3047 var index = from ? from.animationID : to.animationID;
3048 var indexKey = index.toString();
3049 if (!usedIndicesLookup[indexKey]) {
3050 usedIndicesLookup[indexKey] = true;
3051 preparedAnimations.push(animations[index]);
3056 var fromAnimation = animations[from.animationID];
3057 var toAnimation = animations[to.animationID];
3058 var lookupKey = from.animationID.toString();
3059 if (!anchorGroups[lookupKey]) {
3060 var group = anchorGroups[lookupKey] = {
3062 beforeStart: function() {
3063 fromAnimation.beforeStart();
3064 toAnimation.beforeStart();
3067 fromAnimation.close();
3068 toAnimation.close();
3070 classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
3071 from: fromAnimation,
3073 anchors: [] // TODO(matsko): change to reference nodes
3076 // the anchor animations require that the from and to elements both have at least
3077 // one shared CSS class which effictively marries the two elements together to use
3078 // the same animation driver and to properly sequence the anchor animation.
3079 if (group.classes.length) {
3080 preparedAnimations.push(group);
3082 preparedAnimations.push(fromAnimation);
3083 preparedAnimations.push(toAnimation);
3087 anchorGroups[lookupKey].anchors.push({
3088 'out': from.element, 'in': to.element
3092 return preparedAnimations;
3095 function cssClassesIntersection(a,b) {
3100 for (var i = 0; i < a.length; i++) {
3102 if (aa.substring(0,3) === 'ng-') continue;
3104 for (var j = 0; j < b.length; j++) {
3112 return matches.join(' ');
3115 function invokeFirstDriver(animationDetails) {
3116 // we loop in reverse order since the more general drivers (like CSS and JS)
3117 // may attempt more elements, but custom drivers are more particular
3118 for (var i = drivers.length - 1; i >= 0; i--) {
3119 var driverName = drivers[i];
3120 if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
3122 var factory = $injector.get(driverName);
3123 var driver = factory(animationDetails);
3130 function beforeStart() {
3131 element.addClass(NG_ANIMATE_CLASSNAME);
3133 $$jqLite.addClass(element, tempClasses);
3137 function updateAnimationRunners(animation, newRunner) {
3138 if (animation.from && animation.to) {
3139 update(animation.from.element);
3140 update(animation.to.element);
3142 update(animation.element);
3145 function update(element) {
3146 getRunner(element).setHost(newRunner);
3150 function handleDestroyedElement() {
3151 var runner = getRunner(element);
3152 if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
3157 function close(rejected) { // jshint ignore:line
3158 element.off('$destroy', handleDestroyedElement);
3159 removeRunner(element);
3161 applyAnimationClasses(element, options);
3162 applyAnimationStyles(element, options);
3163 options.domOperation();
3166 $$jqLite.removeClass(element, tempClasses);
3169 element.removeClass(NG_ANIMATE_CLASSNAME);
3170 runner.complete(!rejected);
3176 /* global angularAnimateModule: true,
3178 $$AnimateAsyncRunFactory,
3179 $$rAFSchedulerFactory,
3180 $$AnimateChildrenDirective,
3181 $$AnimateRunnerFactory,
3182 $$AnimateQueueProvider,
3183 $$AnimationProvider,
3184 $AnimateCssProvider,
3185 $$AnimateCssDriverProvider,
3186 $$AnimateJsProvider,
3187 $$AnimateJsDriverProvider,
3195 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
3196 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
3198 * <div doc-module-components="ngAnimate"></div>
3201 * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
3202 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
3203 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
3204 * the HTML element that the animation will be triggered on.
3206 * ## Directive Support
3207 * The following directives are "animation aware":
3209 * | Directive | Supported Animations |
3210 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
3211 * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
3212 * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
3213 * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
3214 * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
3215 * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
3216 * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
3217 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
3218 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
3219 * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
3220 * | {@link module:ngMessages#animations ngMessage} | enter and leave |
3222 * (More information can be found by visiting each the documentation associated with each directive.)
3224 * ## CSS-based Animations
3226 * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
3227 * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
3229 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
3232 * <div ng-if="bool" class="fade">
3235 * <button ng-click="bool=true">Fade In!</button>
3236 * <button ng-click="bool=false">Fade Out!</button>
3239 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
3242 * /* The starting CSS styles for the enter animation */
3244 * transition:0.5s linear all;
3248 * /* The finishing CSS styles for the enter animation */
3249 * .fade.ng-enter.ng-enter-active {
3254 * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
3255 * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
3256 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
3258 * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
3261 * /* now the element will fade out before it is removed from the DOM */
3263 * transition:0.5s linear all;
3266 * .fade.ng-leave.ng-leave-active {
3271 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
3274 * /* there is no need to define anything inside of the destination
3275 * CSS class since the keyframe will take charge of the animation */
3277 * animation: my_fade_animation 0.5s linear;
3278 * -webkit-animation: my_fade_animation 0.5s linear;
3281 * @keyframes my_fade_animation {
3282 * from { opacity:1; }
3286 * @-webkit-keyframes my_fade_animation {
3287 * from { opacity:1; }
3292 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
3294 * ### CSS Class-based Animations
3296 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
3297 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
3300 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
3303 * <div ng-show="bool" class="fade">
3306 * <button ng-click="bool=true">Toggle</button>
3310 * transition:0.5s linear all;
3316 * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
3317 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
3319 * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
3323 * <div ng-class="{on:onOff}" class="highlight">
3324 * Highlight this box
3326 * <button ng-click="onOff=!onOff">Toggle</button>
3330 * transition:0.5s linear all;
3332 * .highlight.on-add {
3336 * background:yellow;
3338 * .highlight.on-remove {
3344 * We can also make use of CSS keyframes by placing them within the CSS classes.
3347 * ### CSS Staggering Animations
3348 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
3349 * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
3350 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
3351 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
3352 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
3355 * .my-animation.ng-enter {
3356 * /* standard transition code */
3357 * transition: 1s linear all;
3360 * .my-animation.ng-enter-stagger {
3361 * /* this will have a 100ms delay between each successive leave animation */
3362 * transition-delay: 0.1s;
3364 * /* As of 1.4.4, this must always be set: it signals ngAnimate
3365 * to not accidentally inherit a delay property from another CSS class */
3366 * transition-duration: 0s;
3368 * .my-animation.ng-enter.ng-enter-active {
3369 * /* standard transition styles */
3374 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
3375 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
3376 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
3377 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
3379 * The following code will issue the **ng-leave-stagger** event on the element provided:
3382 * var kids = parent.children();
3384 * $animate.leave(kids[0]); //stagger index=0
3385 * $animate.leave(kids[1]); //stagger index=1
3386 * $animate.leave(kids[2]); //stagger index=2
3387 * $animate.leave(kids[3]); //stagger index=3
3388 * $animate.leave(kids[4]); //stagger index=4
3390 * window.requestAnimationFrame(function() {
3391 * //stagger has reset itself
3392 * $animate.leave(kids[5]); //stagger index=0
3393 * $animate.leave(kids[6]); //stagger index=1
3399 * Stagger animations are currently only supported within CSS-defined animations.
3401 * ### The `ng-animate` CSS class
3403 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
3404 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
3406 * Therefore, animations can be applied to an element using this temporary class directly via CSS.
3409 * .zipper.ng-animate {
3410 * transition:0.5s linear all;
3412 * .zipper.ng-enter {
3415 * .zipper.ng-enter.ng-enter-active {
3418 * .zipper.ng-leave {
3421 * .zipper.ng-leave.ng-leave-active {
3426 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
3427 * the CSS class once an animation has completed.)
3430 * ## JavaScript-based Animations
3432 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
3433 * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
3434 * `module.animation()` module function we can register the ainmation.
3436 * Let's see an example of a enter/leave animation using `ngRepeat`:
3439 * <div ng-repeat="item in items" class="slide">
3444 * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
3447 * myModule.animation('.slide', [function() {
3449 * // make note that other events (like addClass/removeClass)
3450 * // have different function input parameters
3451 * enter: function(element, doneFn) {
3452 * jQuery(element).fadeIn(1000, doneFn);
3454 * // remember to call doneFn so that angular
3455 * // knows that the animation has concluded
3458 * move: function(element, doneFn) {
3459 * jQuery(element).fadeIn(1000, doneFn);
3462 * leave: function(element, doneFn) {
3463 * jQuery(element).fadeOut(1000, doneFn);
3469 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
3470 * greensock.js and velocity.js.
3472 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
3473 * our animations inside of the same registered animation, however, the function input arguments are a bit different:
3476 * <div ng-class="color" class="colorful">
3479 * <button ng-click="color='red'">Change to red</button>
3480 * <button ng-click="color='blue'">Change to blue</button>
3481 * <button ng-click="color='green'">Change to green</button>
3485 * myModule.animation('.colorful', [function() {
3487 * addClass: function(element, className, doneFn) {
3488 * // do some cool animation and call the doneFn
3490 * removeClass: function(element, className, doneFn) {
3491 * // do some cool animation and call the doneFn
3493 * setClass: function(element, addedClass, removedClass, doneFn) {
3494 * // do some cool animation and call the doneFn
3500 * ## CSS + JS Animations Together
3502 * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
3503 * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
3504 * charge of the animation**:
3507 * <div ng-if="bool" class="slide">
3513 * myModule.animation('.slide', [function() {
3515 * enter: function(element, doneFn) {
3516 * jQuery(element).slideIn(1000, doneFn);
3524 * transition:0.5s linear all;
3525 * transform:translateY(-100px);
3527 * .slide.ng-enter.ng-enter-active {
3528 * transform:translateY(0);
3532 * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
3533 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
3534 * our own JS-based animation code:
3537 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3539 * enter: function(element) {
3540 * // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
3541 * return $animateCss(element, {
3550 * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
3552 * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
3553 * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
3554 * data into `$animateCss` directly:
3557 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3559 * enter: function(element) {
3560 * return $animateCss(element, {
3563 * addClass: 'maroon-setting',
3564 * from: { height:0 },
3565 * to: { height: 200 }
3572 * Now we can fill in the rest via our transition CSS code:
3575 * /* the transition tells ngAnimate to make the animation happen */
3576 * .slide.ng-enter { transition:0.5s linear all; }
3578 * /* this extra CSS class will be absorbed into the transition
3579 * since the $animateCss code is adding the class */
3580 * .maroon-setting { background:red; }
3583 * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
3585 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
3587 * ## Animation Anchoring (via `ng-animate-ref`)
3589 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
3590 * structural areas of an application (like views) by pairing up elements using an attribute
3591 * called `ng-animate-ref`.
3593 * Let's say for example we have two views that are managed by `ng-view` and we want to show
3594 * that there is a relationship between two components situated in within these views. By using the
3595 * `ng-animate-ref` attribute we can identify that the two components are paired together and we
3596 * can then attach an animation, which is triggered when the view changes.
3598 * Say for example we have the following template code:
3601 * <!-- index.html -->
3602 * <div ng-view class="view-animation">
3605 * <!-- home.html -->
3606 * <a href="#/banner-page">
3607 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3610 * <!-- banner-page.html -->
3611 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3614 * Now, when the view changes (once the link is clicked), ngAnimate will examine the
3615 * HTML contents to see if there is a match reference between any components in the view
3616 * that is leaving and the view that is entering. It will scan both the view which is being
3617 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
3618 * contain a matching ref value.
3620 * The two images match since they share the same ref value. ngAnimate will now create a
3621 * transport element (which is a clone of the first image element) and it will then attempt
3622 * to animate to the position of the second image element in the next view. For the animation to
3623 * work a special CSS class called `ng-anchor` will be added to the transported element.
3625 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
3626 * ngAnimate will handle the entire transition for us as well as the addition and removal of
3627 * any changes of CSS classes between the elements:
3630 * .banner.ng-anchor {
3631 * /* this animation will last for 1 second since there are
3632 * two phases to the animation (an `in` and an `out` phase) */
3633 * transition:0.5s linear all;
3637 * We also **must** include animations for the views that are being entered and removed
3638 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
3641 * .view-animation.ng-enter, .view-animation.ng-leave {
3642 * transition:0.5s linear all;
3648 * .view-animation.ng-enter {
3649 * transform:translateX(100%);
3651 * .view-animation.ng-leave,
3652 * .view-animation.ng-enter.ng-enter-active {
3653 * transform:translateX(0%);
3655 * .view-animation.ng-leave.ng-leave-active {
3656 * transform:translateX(-100%);
3660 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
3661 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
3662 * from its origin. Once that animation is over then the `in` stage occurs which animates the
3663 * element to its destination. The reason why there are two animations is to give enough time
3664 * for the enter animation on the new element to be ready.
3666 * The example above sets up a transition for both the in and out phases, but we can also target the out or
3667 * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
3670 * .banner.ng-anchor-out {
3671 * transition: 0.5s linear all;
3673 * /* the scale will be applied during the out animation,
3674 * but will be animated away when the in animation runs */
3675 * transform: scale(1.2);
3678 * .banner.ng-anchor-in {
3679 * transition: 1s linear all;
3686 * ### Anchoring Demo
3688 <example module="anchoringExample"
3689 name="anchoringExample"
3690 id="anchoringExample"
3691 deps="angular-animate.js;angular-route.js"
3693 <file name="index.html">
3694 <a href="#/">Home</a>
3696 <div class="view-container">
3697 <div ng-view class="view"></div>
3700 <file name="script.js">
3701 angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])
3702 .config(['$routeProvider', function($routeProvider) {
3703 $routeProvider.when('/', {
3704 templateUrl: 'home.html',
3705 controller: 'HomeController as home'
3707 $routeProvider.when('/profile/:id', {
3708 templateUrl: 'profile.html',
3709 controller: 'ProfileController as profile'
3712 .run(['$rootScope', function($rootScope) {
3713 $rootScope.records = [
3714 { id:1, title: "Miss Beulah Roob" },
3715 { id:2, title: "Trent Morissette" },
3716 { id:3, title: "Miss Ava Pouros" },
3717 { id:4, title: "Rod Pouros" },
3718 { id:5, title: "Abdul Rice" },
3719 { id:6, title: "Laurie Rutherford Sr." },
3720 { id:7, title: "Nakia McLaughlin" },
3721 { id:8, title: "Jordon Blanda DVM" },
3722 { id:9, title: "Rhoda Hand" },
3723 { id:10, title: "Alexandrea Sauer" }
3726 .controller('HomeController', [function() {
3729 .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {
3730 var index = parseInt($routeParams.id, 10);
3731 var record = $rootScope.records[index - 1];
3733 this.title = record.title;
3734 this.id = record.id;
3737 <file name="home.html">
3738 <h2>Welcome to the home page</h1>
3739 <p>Please click on an element</p>
3741 ng-href="#/profile/{{ record.id }}"
3742 ng-animate-ref="{{ record.id }}"
3743 ng-repeat="record in records">
3747 <file name="profile.html">
3748 <div class="profile record" ng-animate-ref="{{ profile.id }}">
3752 <file name="animations.css">
3765 .view-container > .view.ng-animate {
3772 .view.ng-enter, .view.ng-leave,
3774 transition:0.5s linear all;
3777 transform:translateX(100%);
3779 .view.ng-enter.ng-enter-active, .view.ng-leave {
3780 transform:translateX(0%);
3782 .view.ng-leave.ng-leave-active {
3783 transform:translateX(-100%);
3785 .record.ng-anchor-out {
3791 * ### How is the element transported?
3793 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
3794 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
3795 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
3796 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
3797 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
3798 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
3799 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
3800 * will become visible since the shim class will be removed.
3802 * ### How is the morphing handled?
3804 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
3805 * what CSS classes differ between the starting element and the destination element. These different CSS classes
3806 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
3807 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
3808 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
3809 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
3810 * the cloned element is placed inside of root element which is likely close to the body element).
3812 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.
3815 * ## Using $animate in your directive code
3817 * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
3818 * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
3819 * imagine we have a greeting box that shows and hides itself when the data changes
3822 * <greeting-box active="onOrOff">Hi there</greeting-box>
3826 * ngModule.directive('greetingBox', ['$animate', function($animate) {
3827 * return function(scope, element, attrs) {
3828 * attrs.$observe('active', function(value) {
3829 * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
3835 * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
3836 * in our HTML code then we can trigger a CSS or JS animation to happen.
3839 * /* normally we would create a CSS class to reference on the element */
3840 * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
3843 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
3844 * possible be sure to visit the {@link ng.$animate $animate service API page}.
3847 * ### Preventing Collisions With Third Party Libraries
3849 * Some third-party frameworks place animation duration defaults across many element or className
3850 * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
3851 * is expecting actual animations on these elements and has to wait for their completion.
3853 * You can prevent this unwanted behavior by using a prefix on all your animation classes:
3856 * /* prefixed with animate- */
3857 * .animate-fade-add.animate-fade-add-active {
3858 * transition:1s linear all;
3863 * You then configure `$animate` to enforce this prefix:
3866 * $animateProvider.classNameFilter(/animate-/);
3869 * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
3870 * will be evaluated for animation when any DOM changes occur in the application.
3872 * ## Callbacks and Promises
3874 * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
3875 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
3876 * ended by chaining onto the returned promise that animation method returns.
3879 * // somewhere within the depths of the directive
3880 * $animate.enter(element, parent).then(function() {
3881 * //the animation has completed
3885 * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
3888 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
3889 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
3890 * routing controller to hook into that:
3893 * ngModule.controller('HomePageController', ['$animate', function($animate) {
3894 * $animate.on('enter', ngViewElement, function(element) {
3895 * // the animation for this route has completed
3900 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
3909 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
3911 * Click here {@link ng.$animate to learn more about animations with `$animate`}.
3913 angular.module('ngAnimate', [])
3914 .directive('ngAnimateChildren', $$AnimateChildrenDirective)
3915 .factory('$$rAFScheduler', $$rAFSchedulerFactory)
3917 .factory('$$AnimateRunner', $$AnimateRunnerFactory)
3918 .factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
3920 .provider('$$animateQueue', $$AnimateQueueProvider)
3921 .provider('$$animation', $$AnimationProvider)
3923 .provider('$animateCss', $AnimateCssProvider)
3924 .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
3926 .provider('$$animateJs', $$AnimateJsProvider)
3927 .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
3930 })(window, window.angular);