2 * @license AngularJS v1.5.0
3 * (c) 2010-2016 Google, Inc. http://angularjs.org
6 (function(window, angular, undefined) {'use strict';
8 /* jshint ignore:start */
9 var noop = angular.noop;
10 var copy = angular.copy;
11 var extend = angular.extend;
12 var jqLite = angular.element;
13 var forEach = angular.forEach;
14 var isArray = angular.isArray;
15 var isString = angular.isString;
16 var isObject = angular.isObject;
17 var isUndefined = angular.isUndefined;
18 var isDefined = angular.isDefined;
19 var isFunction = angular.isFunction;
20 var isElement = angular.isElement;
25 var ADD_CLASS_SUFFIX = '-add';
26 var REMOVE_CLASS_SUFFIX = '-remove';
27 var EVENT_CLASS_PREFIX = 'ng-';
28 var ACTIVE_CLASS_SUFFIX = '-active';
29 var PREPARE_CLASS_SUFFIX = '-prepare';
31 var NG_ANIMATE_CLASSNAME = 'ng-animate';
32 var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
34 // Detect proper transitionend/animationend event names.
35 var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
37 // If unprefixed events are not supported but webkit-prefixed are, use the latter.
38 // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
39 // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
40 // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
41 // Register both events in case `window.onanimationend` is not supported because of that,
42 // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
43 // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
44 // therefore there is no reason to test anymore for other vendor prefixes:
45 // http://caniuse.com/#search=transition
46 if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
47 CSS_PREFIX = '-webkit-';
48 TRANSITION_PROP = 'WebkitTransition';
49 TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
51 TRANSITION_PROP = 'transition';
52 TRANSITIONEND_EVENT = 'transitionend';
55 if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
56 CSS_PREFIX = '-webkit-';
57 ANIMATION_PROP = 'WebkitAnimation';
58 ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
60 ANIMATION_PROP = 'animation';
61 ANIMATIONEND_EVENT = 'animationend';
64 var DURATION_KEY = 'Duration';
65 var PROPERTY_KEY = 'Property';
66 var DELAY_KEY = 'Delay';
67 var TIMING_KEY = 'TimingFunction';
68 var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
69 var ANIMATION_PLAYSTATE_KEY = 'PlayState';
70 var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
72 var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
73 var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
74 var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
75 var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
77 var isPromiseLike = function(p) {
78 return p && p.then ? true : false;
81 var ngMinErr = angular.$$minErr('ng');
82 function assertArg(arg, name, reason) {
84 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
89 function mergeClasses(a,b) {
90 if (!a && !b) return '';
93 if (isArray(a)) a = a.join(' ');
94 if (isArray(b)) b = b.join(' ');
98 function packageStyles(options) {
100 if (options && (options.to || options.from)) {
101 styles.to = options.to;
102 styles.from = options.from;
107 function pendClasses(classes, fix, isPrefix) {
109 classes = isArray(classes)
111 : classes && isString(classes) && classes.length
112 ? classes.split(/\s+/)
114 forEach(classes, function(klass, i) {
115 if (klass && klass.length > 0) {
116 className += (i > 0) ? ' ' : '';
117 className += isPrefix ? fix + klass
124 function removeFromArray(arr, val) {
125 var index = arr.indexOf(val);
127 arr.splice(index, 1);
131 function stripCommentsFromElement(element) {
132 if (element instanceof jqLite) {
133 switch (element.length) {
139 // there is no point of stripping anything if the element
140 // is the only element within the jqLite wrapper.
141 // (it's important that we retain the element instance.)
142 if (element[0].nodeType === ELEMENT_NODE) {
148 return jqLite(extractElementNode(element));
153 if (element.nodeType === ELEMENT_NODE) {
154 return jqLite(element);
158 function extractElementNode(element) {
159 if (!element[0]) return element;
160 for (var i = 0; i < element.length; i++) {
161 var elm = element[i];
162 if (elm.nodeType == ELEMENT_NODE) {
168 function $$addClass($$jqLite, element, className) {
169 forEach(element, function(elm) {
170 $$jqLite.addClass(elm, className);
174 function $$removeClass($$jqLite, element, className) {
175 forEach(element, function(elm) {
176 $$jqLite.removeClass(elm, className);
180 function applyAnimationClassesFactory($$jqLite) {
181 return function(element, options) {
182 if (options.addClass) {
183 $$addClass($$jqLite, element, options.addClass);
184 options.addClass = null;
186 if (options.removeClass) {
187 $$removeClass($$jqLite, element, options.removeClass);
188 options.removeClass = null;
193 function prepareAnimationOptions(options) {
194 options = options || {};
195 if (!options.$$prepared) {
196 var domOperation = options.domOperation || noop;
197 options.domOperation = function() {
198 options.$$domOperationFired = true;
202 options.$$prepared = true;
207 function applyAnimationStyles(element, options) {
208 applyAnimationFromStyles(element, options);
209 applyAnimationToStyles(element, options);
212 function applyAnimationFromStyles(element, options) {
214 element.css(options.from);
219 function applyAnimationToStyles(element, options) {
221 element.css(options.to);
226 function mergeAnimationDetails(element, oldAnimation, newAnimation) {
227 var target = oldAnimation.options || {};
228 var newOptions = newAnimation.options || {};
230 var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
231 var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
232 var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
234 if (newOptions.preparationClasses) {
235 target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
236 delete newOptions.preparationClasses;
239 // noop is basically when there is no callback; otherwise something has been set
240 var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
242 extend(target, newOptions);
244 // 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.
245 if (realDomOperation) {
246 target.domOperation = realDomOperation;
249 if (classes.addClass) {
250 target.addClass = classes.addClass;
252 target.addClass = null;
255 if (classes.removeClass) {
256 target.removeClass = classes.removeClass;
258 target.removeClass = null;
261 oldAnimation.addClass = target.addClass;
262 oldAnimation.removeClass = target.removeClass;
267 function resolveElementClasses(existing, toAdd, toRemove) {
269 var REMOVE_CLASS = -1;
272 existing = splitClassesToLookup(existing);
274 toAdd = splitClassesToLookup(toAdd);
275 forEach(toAdd, function(value, key) {
276 flags[key] = ADD_CLASS;
279 toRemove = splitClassesToLookup(toRemove);
280 forEach(toRemove, function(value, key) {
281 flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
289 forEach(flags, function(val, klass) {
291 if (val === ADD_CLASS) {
293 allow = !existing[klass];
294 } else if (val === REMOVE_CLASS) {
295 prop = 'removeClass';
296 allow = existing[klass];
299 if (classes[prop].length) {
300 classes[prop] += ' ';
302 classes[prop] += klass;
306 function splitClassesToLookup(classes) {
307 if (isString(classes)) {
308 classes = classes.split(' ');
312 forEach(classes, function(klass) {
313 // sometimes the split leaves empty string values
314 // incase extra spaces were applied to the options
325 function getDomNode(element) {
326 return (element instanceof angular.element) ? element[0] : element;
329 function applyGeneratedPreparationClasses(element, event, options) {
332 classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
334 if (options.addClass) {
335 classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
337 if (options.removeClass) {
338 classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
340 if (classes.length) {
341 options.preparationClasses = classes;
342 element.addClass(classes);
346 function clearGeneratedClasses(element, options) {
347 if (options.preparationClasses) {
348 element.removeClass(options.preparationClasses);
349 options.preparationClasses = null;
351 if (options.activeClasses) {
352 element.removeClass(options.activeClasses);
353 options.activeClasses = null;
357 function blockTransitions(node, duration) {
358 // we use a negative delay value since it performs blocking
359 // yet it doesn't kill any existing transitions running on the
360 // same element which makes this safe for class-based animations
361 var value = duration ? '-' + duration + 's' : '';
362 applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
363 return [TRANSITION_DELAY_PROP, value];
366 function blockKeyframeAnimations(node, applyBlock) {
367 var value = applyBlock ? 'paused' : '';
368 var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
369 applyInlineStyle(node, [key, value]);
373 function applyInlineStyle(node, styleTuple) {
374 var prop = styleTuple[0];
375 var value = styleTuple[1];
376 node.style[prop] = value;
379 function concatWithSpace(a,b) {
385 var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
388 function scheduler(tasks) {
389 // we make a copy since RAFScheduler mutates the state
390 // of the passed in array variable and this would be difficult
391 // to track down on the outside code
392 queue = queue.concat(tasks);
396 queue = scheduler.queue = [];
398 /* waitUntilQuiet does two things:
399 * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
400 * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
402 * The motivation here is that animation code can request more time from the scheduler
403 * before the next wave runs. This allows for certain DOM properties such as classes to
404 * be resolved in time for the next animation to run.
406 scheduler.waitUntilQuiet = function(fn) {
407 if (cancelFn) cancelFn();
409 cancelFn = $$rAF(function() {
418 function nextTick() {
419 if (!queue.length) return;
421 var items = queue.shift();
422 for (var i = 0; i < items.length; i++) {
428 if (!cancelFn) nextTick();
436 * @name ngAnimateChildren
442 * ngAnimateChildren allows you to specify that children of this element should animate even if any
443 * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
444 * (structural) animation, child elements that also have an active structural animation are not animated.
446 * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
449 * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
450 * then child animations are allowed. If the value is `false`, child animations are not allowed.
453 * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
454 <file name="index.html">
455 <div ng-controller="mainController as main">
456 <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
457 <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
459 <div ng-animate-children="{{main.animateChildren}}">
460 <div ng-if="main.enterElement" class="container">
462 <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div>
467 <file name="animations.css">
470 .container.ng-leave {
471 transition: all ease 1.5s;
475 .container.ng-leave-active {
480 .container.ng-enter-active {
485 background: firebrick;
492 transition: transform 1.5s ease;
496 transform: translateX(50px);
499 .item.ng-enter-active {
500 transform: translateX(0);
503 <file name="script.js">
504 angular.module('ngAnimateChildren', ['ngAnimate'])
505 .controller('mainController', function() {
506 this.animateChildren = false;
507 this.enterElement = false;
512 var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
514 link: function(scope, element, attrs) {
515 var val = attrs.ngAnimateChildren;
516 if (angular.isString(val) && val.length === 0) { //empty attribute
517 element.data(NG_ANIMATE_CHILDREN_DATA, true);
519 // Interpolate and set the value, so that it is available to
520 // animations that run right after compilation
521 setData($interpolate(val)(scope));
522 attrs.$observe('ngAnimateChildren', setData);
525 function setData(value) {
526 value = value === 'on' || value === 'true';
527 element.data(NG_ANIMATE_CHILDREN_DATA, value);
533 var ANIMATE_TIMER_KEY = '$$animateCss';
541 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
542 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
543 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
544 * directives to create more complex animations that can be purely driven using CSS code.
546 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
547 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
550 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
551 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
552 * any automatic control over cancelling animations and/or preventing animations from being run on
553 * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
554 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
557 * The example below shows how we can create a folding animation on an element using `ng-if`:
560 * <!-- notice the `fold-animation` CSS class -->
561 * <div ng-if="onOff" class="fold-animation">
562 * This element will go BOOM
564 * <button ng-click="onOff=true">Fold In</button>
567 * Now we create the **JavaScript animation** that will trigger the CSS transition:
570 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
572 * enter: function(element, doneFn) {
573 * var height = element[0].offsetHeight;
574 * return $animateCss(element, {
575 * from: { height:'0px' },
576 * to: { height:height + 'px' },
577 * duration: 1 // one second
584 * ## More Advanced Uses
586 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
587 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
589 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
590 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
591 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
592 * to provide a working animation that will run in CSS.
594 * The example below showcases a more advanced version of the `.fold-animation` from the example above:
597 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
599 * enter: function(element, doneFn) {
600 * var height = element[0].offsetHeight;
601 * return $animateCss(element, {
602 * addClass: 'red large-text pulse-twice',
603 * easing: 'ease-out',
604 * from: { height:'0px' },
605 * to: { height:height + 'px' },
606 * duration: 1 // one second
613 * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
616 * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code,
617 * the CSS classes below will be transitioned despite them being defined as regular CSS classes */
618 * .red { background:red; }
619 * .large-text { font-size:20px; }
621 * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */
623 * animation: 0.5s pulse linear 2;
624 * -webkit-animation: 0.5s pulse linear 2;
628 * from { transform: scale(0.5); }
629 * to { transform: scale(1.5); }
632 * @-webkit-keyframes pulse {
633 * from { -webkit-transform: scale(0.5); }
634 * to { -webkit-transform: scale(1.5); }
638 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
640 * ## How the Options are handled
642 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
643 * 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
644 * styles using the `from` and `to` properties.
647 * var animator = $animateCss(element, {
648 * from: { background:'red' },
649 * to: { background:'blue' }
655 * .rotating-animation {
656 * animation:0.5s rotate linear;
657 * -webkit-animation:0.5s rotate linear;
660 * @keyframes rotate {
661 * from { transform: rotate(0deg); }
662 * to { transform: rotate(360deg); }
665 * @-webkit-keyframes rotate {
666 * from { -webkit-transform: rotate(0deg); }
667 * to { -webkit-transform: rotate(360deg); }
671 * 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
672 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
673 * 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
674 * 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
675 * and spread across the transition and keyframe animation.
677 * ## What is returned
679 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
680 * 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
681 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
684 * var animator = $animateCss(element, { ... });
687 * Now what do the contents of our `animator` variable look like:
691 * // starts the animation
694 * // ends (aborts) the animation
699 * 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.
700 * 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 styles may have been
701 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
702 * and that changing them will not reconfigure the parameters of the animation.
704 * ### runner.done() vs runner.then()
705 * 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
706 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
707 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
708 * unless you really need a digest to kick off afterwards.
710 * 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
711 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
712 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
714 * @param {DOMElement} element the element that will be animated
715 * @param {object} options the animation-related options that will be applied during the animation
717 * * `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
718 * 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.)
719 * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
720 * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
721 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
722 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
723 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
724 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
725 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
726 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
727 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
728 * * `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`
729 * is provided then the animation will be skipped entirely.
730 * * `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
731 * 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
732 * 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
734 * * `stagger` - A numeric time value representing the delay between successively animated elements
735 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
736 * * `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
737 * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
738 * * `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 occurring on the classes being added and removed.)
739 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
740 * the animation is closed. This is useful for when the styles are used purely for the sake of
741 * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
742 * By default this value is set to `false`.
744 * @return {object} an object with start and end methods and details about the animation.
746 * * `start` - The method to start the animation. This will return a `Promise` when called.
747 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
749 var ONE_SECOND = 1000;
752 var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
753 var CLOSING_TIME_BUFFER = 1.5;
755 var DETECT_CSS_PROPERTIES = {
756 transitionDuration: TRANSITION_DURATION_PROP,
757 transitionDelay: TRANSITION_DELAY_PROP,
758 transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
759 animationDuration: ANIMATION_DURATION_PROP,
760 animationDelay: ANIMATION_DELAY_PROP,
761 animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
764 var DETECT_STAGGER_CSS_PROPERTIES = {
765 transitionDuration: TRANSITION_DURATION_PROP,
766 transitionDelay: TRANSITION_DELAY_PROP,
767 animationDuration: ANIMATION_DURATION_PROP,
768 animationDelay: ANIMATION_DELAY_PROP
771 function getCssKeyframeDurationStyle(duration) {
772 return [ANIMATION_DURATION_PROP, duration + 's'];
775 function getCssDelayStyle(delay, isKeyframeAnimation) {
776 var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
777 return [prop, delay + 's'];
780 function computeCssStyles($window, element, properties) {
781 var styles = Object.create(null);
782 var detectedStyles = $window.getComputedStyle(element) || {};
783 forEach(properties, function(formalStyleName, actualStyleName) {
784 var val = detectedStyles[formalStyleName];
786 var c = val.charAt(0);
788 // only numerical-based values have a negative sign or digit as the first value
789 if (c === '-' || c === '+' || c >= 0) {
790 val = parseMaxTime(val);
793 // by setting this to null in the event that the delay is not set or is set directly as 0
794 // then we can still allow for negative values to be used later on and not mistake this
795 // value for being greater than any other negative value.
799 styles[actualStyleName] = val;
806 function parseMaxTime(str) {
808 var values = str.split(/\s*,\s*/);
809 forEach(values, function(value) {
810 // it's always safe to consider only second values and omit `ms` values since
811 // getComputedStyle will always handle the conversion for us
812 if (value.charAt(value.length - 1) == 's') {
813 value = value.substring(0, value.length - 1);
815 value = parseFloat(value) || 0;
816 maxValue = maxValue ? Math.max(value, maxValue) : value;
821 function truthyTimingValue(val) {
822 return val === 0 || val != null;
825 function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
826 var style = TRANSITION_PROP;
827 var value = duration + 's';
828 if (applyOnlyDuration) {
829 style += DURATION_KEY;
831 value += ' linear all';
833 return [style, value];
836 function createLocalCacheLookup() {
837 var cache = Object.create(null);
840 cache = Object.create(null);
843 count: function(key) {
844 var entry = cache[key];
845 return entry ? entry.total : 0;
849 var entry = cache[key];
850 return entry && entry.value;
853 put: function(key, value) {
855 cache[key] = { total: 1, value: value };
863 // we do not reassign an already present style value since
864 // if we detect the style property value again we may be
865 // detecting styles that were added via the `from` styles.
866 // We make use of `isDefined` here since an empty string
867 // or null value (which is what getPropertyValue will return
868 // for a non-existing style) will still be marked as a valid
869 // value for the style (a falsy value implies that the style
870 // is to be removed at the end of the animation). If we had a simple
871 // "OR" statement then it would not be enough to catch that.
872 function registerRestorableStyles(backup, node, properties) {
873 forEach(properties, function(prop) {
874 backup[prop] = isDefined(backup[prop])
876 : node.style.getPropertyValue(prop);
880 var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
881 var gcsLookup = createLocalCacheLookup();
882 var gcsStaggerLookup = createLocalCacheLookup();
884 this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
885 '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
886 function($window, $$jqLite, $$AnimateRunner, $timeout,
887 $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
889 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
891 var parentCounter = 0;
892 function gcsHashFn(node, extraClasses) {
893 var KEY = "$$ngAnimateParentKey";
894 var parentNode = node.parentNode;
895 var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
896 return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
899 function computeCachedCssStyles(node, className, cacheKey, properties) {
900 var timings = gcsLookup.get(cacheKey);
903 timings = computeCssStyles($window, node, properties);
904 if (timings.animationIterationCount === 'infinite') {
905 timings.animationIterationCount = 1;
909 // we keep putting this in multiple times even though the value and the cacheKey are the same
910 // because we're keeping an internal tally of how many duplicate animations are detected.
911 gcsLookup.put(cacheKey, timings);
915 function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
918 // if we have one or more existing matches of matching elements
919 // containing the same parent + CSS styles (which is how cacheKey works)
920 // then staggering is possible
921 if (gcsLookup.count(cacheKey) > 0) {
922 stagger = gcsStaggerLookup.get(cacheKey);
925 var staggerClassName = pendClasses(className, '-stagger');
927 $$jqLite.addClass(node, staggerClassName);
929 stagger = computeCssStyles($window, node, properties);
931 // force the conversion of a null value to zero incase not set
932 stagger.animationDuration = Math.max(stagger.animationDuration, 0);
933 stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
935 $$jqLite.removeClass(node, staggerClassName);
937 gcsStaggerLookup.put(cacheKey, stagger);
941 return stagger || {};
944 var cancelLastRAFRequest;
945 var rafWaitQueue = [];
946 function waitUntilQuiet(callback) {
947 rafWaitQueue.push(callback);
948 $$rAFScheduler.waitUntilQuiet(function() {
950 gcsStaggerLookup.flush();
952 // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
953 // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
954 var pageWidth = $$forceReflow();
956 // we use a for loop to ensure that if the queue is changed
957 // during this looping then it will consider new requests
958 for (var i = 0; i < rafWaitQueue.length; i++) {
959 rafWaitQueue[i](pageWidth);
961 rafWaitQueue.length = 0;
965 function computeTimings(node, className, cacheKey) {
966 var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
967 var aD = timings.animationDelay;
968 var tD = timings.transitionDelay;
969 timings.maxDelay = aD && tD
972 timings.maxDuration = Math.max(
973 timings.animationDuration * timings.animationIterationCount,
974 timings.transitionDuration);
979 return function init(element, initialOptions) {
980 // all of the animation functions should create
981 // a copy of the options data, however, if a
982 // parent service has already created a copy then
983 // we should stick to using that
984 var options = initialOptions || {};
985 if (!options.$$prepared) {
986 options = prepareAnimationOptions(copy(options));
989 var restoreStyles = {};
990 var node = getDomNode(element);
993 || !$$animateQueue.enabled()) {
994 return closeAndReturnNoopAnimator();
997 var temporaryStyles = [];
998 var classes = element.attr('class');
999 var styles = packageStyles(options);
1000 var animationClosed;
1001 var animationPaused;
1002 var animationCompleted;
1008 var maxDurationTime;
1012 if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
1013 return closeAndReturnNoopAnimator();
1016 var method = options.event && isArray(options.event)
1017 ? options.event.join(' ')
1020 var isStructural = method && options.structural;
1021 var structuralClassName = '';
1022 var addRemoveClassName = '';
1025 structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
1026 } else if (method) {
1027 structuralClassName = method;
1030 if (options.addClass) {
1031 addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
1034 if (options.removeClass) {
1035 if (addRemoveClassName.length) {
1036 addRemoveClassName += ' ';
1038 addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
1041 // there may be a situation where a structural animation is combined together
1042 // with CSS classes that need to resolve before the animation is computed.
1043 // However this means that there is no explicit CSS code to block the animation
1044 // from happening (by setting 0s none in the class name). If this is the case
1045 // we need to apply the classes before the first rAF so we know to continue if
1046 // there actually is a detected transition or keyframe animation
1047 if (options.applyClassesEarly && addRemoveClassName.length) {
1048 applyAnimationClasses(element, options);
1051 var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
1052 var fullClassName = classes + ' ' + preparationClasses;
1053 var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
1054 var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
1055 var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
1057 // there is no way we can trigger an animation if no styles and
1058 // no classes are being applied which would then trigger a transition,
1059 // unless there a is raw keyframe value that is applied to the element.
1060 if (!containsKeyframeAnimation
1062 && !preparationClasses) {
1063 return closeAndReturnNoopAnimator();
1066 var cacheKey, stagger;
1067 if (options.stagger > 0) {
1068 var staggerVal = parseFloat(options.stagger);
1070 transitionDelay: staggerVal,
1071 animationDelay: staggerVal,
1072 transitionDuration: 0,
1073 animationDuration: 0
1076 cacheKey = gcsHashFn(node, fullClassName);
1077 stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
1080 if (!options.$$skipPreparationClasses) {
1081 $$jqLite.addClass(element, preparationClasses);
1084 var applyOnlyDuration;
1086 if (options.transitionStyle) {
1087 var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
1088 applyInlineStyle(node, transitionStyle);
1089 temporaryStyles.push(transitionStyle);
1092 if (options.duration >= 0) {
1093 applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
1094 var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
1096 // we set the duration so that it will be picked up by getComputedStyle later
1097 applyInlineStyle(node, durationStyle);
1098 temporaryStyles.push(durationStyle);
1101 if (options.keyframeStyle) {
1102 var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
1103 applyInlineStyle(node, keyframeStyle);
1104 temporaryStyles.push(keyframeStyle);
1107 var itemIndex = stagger
1108 ? options.staggerIndex >= 0
1109 ? options.staggerIndex
1110 : gcsLookup.count(cacheKey)
1113 var isFirst = itemIndex === 0;
1115 // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
1116 // without causing any combination of transitions to kick in. By adding a negative delay value
1117 // it forces the setup class' transition to end immediately. We later then remove the negative
1118 // transition delay to allow for the transition to naturally do it's thing. The beauty here is
1119 // that if there is no transition defined then nothing will happen and this will also allow
1120 // other transitions to be stacked on top of each other without any chopping them out.
1121 if (isFirst && !options.skipBlocking) {
1122 blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
1125 var timings = computeTimings(node, fullClassName, cacheKey);
1126 var relativeDelay = timings.maxDelay;
1127 maxDelay = Math.max(relativeDelay, 0);
1128 maxDuration = timings.maxDuration;
1131 flags.hasTransitions = timings.transitionDuration > 0;
1132 flags.hasAnimations = timings.animationDuration > 0;
1133 flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';
1134 flags.applyTransitionDuration = hasToStyles && (
1135 (flags.hasTransitions && !flags.hasTransitionAll)
1136 || (flags.hasAnimations && !flags.hasTransitions));
1137 flags.applyAnimationDuration = options.duration && flags.hasAnimations;
1138 flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
1139 flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations;
1140 flags.recalculateTimingStyles = addRemoveClassName.length > 0;
1142 if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
1143 maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
1145 if (flags.applyTransitionDuration) {
1146 flags.hasTransitions = true;
1147 timings.transitionDuration = maxDuration;
1148 applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
1149 temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
1152 if (flags.applyAnimationDuration) {
1153 flags.hasAnimations = true;
1154 timings.animationDuration = maxDuration;
1155 temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
1159 if (maxDuration === 0 && !flags.recalculateTimingStyles) {
1160 return closeAndReturnNoopAnimator();
1163 if (options.delay != null) {
1165 if (typeof options.delay !== "boolean") {
1166 delayStyle = parseFloat(options.delay);
1167 // number in options.delay means we have to recalculate the delay for the closing timeout
1168 maxDelay = Math.max(delayStyle, 0);
1171 if (flags.applyTransitionDelay) {
1172 temporaryStyles.push(getCssDelayStyle(delayStyle));
1175 if (flags.applyAnimationDelay) {
1176 temporaryStyles.push(getCssDelayStyle(delayStyle, true));
1180 // we need to recalculate the delay value since we used a pre-emptive negative
1181 // delay value and the delay value is required for the final event checking. This
1182 // property will ensure that this will happen after the RAF phase has passed.
1183 if (options.duration == null && timings.transitionDuration > 0) {
1184 flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
1187 maxDelayTime = maxDelay * ONE_SECOND;
1188 maxDurationTime = maxDuration * ONE_SECOND;
1189 if (!options.skipBlocking) {
1190 flags.blockTransition = timings.transitionDuration > 0;
1191 flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
1192 stagger.animationDelay > 0 &&
1193 stagger.animationDuration === 0;
1197 if (options.cleanupStyles) {
1198 registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
1200 applyAnimationFromStyles(element, options);
1203 if (flags.blockTransition || flags.blockKeyframeAnimation) {
1204 applyBlocking(maxDuration);
1205 } else if (!options.skipBlocking) {
1206 blockTransitions(node, false);
1209 // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
1211 $$willAnimate: true,
1214 if (animationClosed) return;
1219 resume: null, //this will be set during the start() phase
1223 runner = new $$AnimateRunner(runnerHost);
1225 waitUntilQuiet(start);
1227 // we don't have access to pause/resume the animation
1228 // since it hasn't run yet. AnimateRunner will therefore
1229 // set noop functions for resume and pause and they will
1230 // later be overridden once the animation is triggered
1239 function cancelFn() {
1243 function close(rejected) { // jshint ignore:line
1244 // if the promise has been called already then we shouldn't close
1245 // the animation again
1246 if (animationClosed || (animationCompleted && animationPaused)) return;
1247 animationClosed = true;
1248 animationPaused = false;
1250 if (!options.$$skipPreparationClasses) {
1251 $$jqLite.removeClass(element, preparationClasses);
1253 $$jqLite.removeClass(element, activeClasses);
1255 blockKeyframeAnimations(node, false);
1256 blockTransitions(node, false);
1258 forEach(temporaryStyles, function(entry) {
1259 // There is only one way to remove inline style properties entirely from elements.
1260 // By using `removeProperty` this works, but we need to convert camel-cased CSS
1261 // styles down to hyphenated values.
1262 node.style[entry[0]] = '';
1265 applyAnimationClasses(element, options);
1266 applyAnimationStyles(element, options);
1268 if (Object.keys(restoreStyles).length) {
1269 forEach(restoreStyles, function(value, prop) {
1270 value ? node.style.setProperty(prop, value)
1271 : node.style.removeProperty(prop);
1275 // the reason why we have this option is to allow a synchronous closing callback
1276 // that is fired as SOON as the animation ends (when the CSS is removed) or if
1277 // the animation never takes off at all. A good example is a leave animation since
1278 // the element must be removed just after the animation is over or else the element
1279 // will appear on screen for one animation frame causing an overbearing flicker.
1280 if (options.onDone) {
1284 if (events && events.length) {
1285 // Remove the transitionend / animationend listener(s)
1286 element.off(events.join(' '), onAnimationProgress);
1289 //Cancel the fallback closing timeout and remove the timer data
1290 var animationTimerData = element.data(ANIMATE_TIMER_KEY);
1291 if (animationTimerData) {
1292 $timeout.cancel(animationTimerData[0].timer);
1293 element.removeData(ANIMATE_TIMER_KEY);
1296 // if the preparation function fails then the promise is not setup
1298 runner.complete(!rejected);
1302 function applyBlocking(duration) {
1303 if (flags.blockTransition) {
1304 blockTransitions(node, duration);
1307 if (flags.blockKeyframeAnimation) {
1308 blockKeyframeAnimations(node, !!duration);
1312 function closeAndReturnNoopAnimator() {
1313 runner = new $$AnimateRunner({
1318 // should flush the cache animation
1319 waitUntilQuiet(noop);
1323 $$willAnimate: false,
1331 function onAnimationProgress(event) {
1332 event.stopPropagation();
1333 var ev = event.originalEvent || event;
1335 // we now always use `Date.now()` due to the recent changes with
1336 // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
1337 var timeStamp = ev.$manualTimeStamp || Date.now();
1339 /* Firefox (or possibly just Gecko) likes to not round values up
1340 * when a ms measurement is used for the animation */
1341 var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1343 /* $manualTimeStamp is a mocked timeStamp value which is set
1344 * within browserTrigger(). This is only here so that tests can
1345 * mock animations properly. Real events fallback to event.timeStamp,
1346 * or, if they don't, then a timeStamp is automatically created for them.
1347 * We're checking to see if the timeStamp surpasses the expected delay,
1348 * but we're using elapsedTime instead of the timeStamp on the 2nd
1349 * pre-condition since animationPauseds sometimes close off early */
1350 if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1351 // we set this flag to ensure that if the transition is paused then, when resumed,
1352 // the animation will automatically close itself since transitions cannot be paused.
1353 animationCompleted = true;
1359 if (animationClosed) return;
1360 if (!node.parentNode) {
1365 // even though we only pause keyframe animations here the pause flag
1366 // will still happen when transitions are used. Only the transition will
1367 // not be paused since that is not possible. If the animation ends when
1368 // paused then it will not complete until unpaused or cancelled.
1369 var playPause = function(playAnimation) {
1370 if (!animationCompleted) {
1371 animationPaused = !playAnimation;
1372 if (timings.animationDuration) {
1373 var value = blockKeyframeAnimations(node, animationPaused);
1375 ? temporaryStyles.push(value)
1376 : removeFromArray(temporaryStyles, value);
1378 } else if (animationPaused && playAnimation) {
1379 animationPaused = false;
1384 // checking the stagger duration prevents an accidentally cascade of the CSS delay style
1385 // being inherited from the parent. If the transition duration is zero then we can safely
1386 // rely that the delay value is an intentional stagger delay style.
1387 var maxStagger = itemIndex > 0
1388 && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
1389 (timings.animationDuration && stagger.animationDuration === 0))
1390 && Math.max(stagger.animationDelay, stagger.transitionDelay);
1392 $timeout(triggerAnimationStart,
1393 Math.floor(maxStagger * itemIndex * ONE_SECOND),
1396 triggerAnimationStart();
1399 // this will decorate the existing promise runner with pause/resume methods
1400 runnerHost.resume = function() {
1404 runnerHost.pause = function() {
1408 function triggerAnimationStart() {
1409 // just incase a stagger animation kicks in when the animation
1410 // itself was cancelled entirely
1411 if (animationClosed) return;
1413 applyBlocking(false);
1415 forEach(temporaryStyles, function(entry) {
1417 var value = entry[1];
1418 node.style[key] = value;
1421 applyAnimationClasses(element, options);
1422 $$jqLite.addClass(element, activeClasses);
1424 if (flags.recalculateTimingStyles) {
1425 fullClassName = node.className + ' ' + preparationClasses;
1426 cacheKey = gcsHashFn(node, fullClassName);
1428 timings = computeTimings(node, fullClassName, cacheKey);
1429 relativeDelay = timings.maxDelay;
1430 maxDelay = Math.max(relativeDelay, 0);
1431 maxDuration = timings.maxDuration;
1433 if (maxDuration === 0) {
1438 flags.hasTransitions = timings.transitionDuration > 0;
1439 flags.hasAnimations = timings.animationDuration > 0;
1442 if (flags.applyAnimationDelay) {
1443 relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
1444 ? parseFloat(options.delay)
1447 maxDelay = Math.max(relativeDelay, 0);
1448 timings.animationDelay = relativeDelay;
1449 delayStyle = getCssDelayStyle(relativeDelay, true);
1450 temporaryStyles.push(delayStyle);
1451 node.style[delayStyle[0]] = delayStyle[1];
1454 maxDelayTime = maxDelay * ONE_SECOND;
1455 maxDurationTime = maxDuration * ONE_SECOND;
1457 if (options.easing) {
1458 var easeProp, easeVal = options.easing;
1459 if (flags.hasTransitions) {
1460 easeProp = TRANSITION_PROP + TIMING_KEY;
1461 temporaryStyles.push([easeProp, easeVal]);
1462 node.style[easeProp] = easeVal;
1464 if (flags.hasAnimations) {
1465 easeProp = ANIMATION_PROP + TIMING_KEY;
1466 temporaryStyles.push([easeProp, easeVal]);
1467 node.style[easeProp] = easeVal;
1471 if (timings.transitionDuration) {
1472 events.push(TRANSITIONEND_EVENT);
1475 if (timings.animationDuration) {
1476 events.push(ANIMATIONEND_EVENT);
1479 startTime = Date.now();
1480 var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
1481 var endTime = startTime + timerTime;
1483 var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
1484 var setupFallbackTimer = true;
1485 if (animationsData.length) {
1486 var currentTimerData = animationsData[0];
1487 setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
1488 if (setupFallbackTimer) {
1489 $timeout.cancel(currentTimerData.timer);
1491 animationsData.push(close);
1495 if (setupFallbackTimer) {
1496 var timer = $timeout(onAnimationExpired, timerTime, false);
1497 animationsData[0] = {
1499 expectedEndTime: endTime
1501 animationsData.push(close);
1502 element.data(ANIMATE_TIMER_KEY, animationsData);
1505 if (events.length) {
1506 element.on(events.join(' '), onAnimationProgress);
1510 if (options.cleanupStyles) {
1511 registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
1513 applyAnimationToStyles(element, options);
1517 function onAnimationExpired() {
1518 var animationsData = element.data(ANIMATE_TIMER_KEY);
1520 // this will be false in the event that the element was
1521 // removed from the DOM (via a leave animation or something
1523 if (animationsData) {
1524 for (var i = 1; i < animationsData.length; i++) {
1525 animationsData[i]();
1527 element.removeData(ANIMATE_TIMER_KEY);
1535 var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
1536 $$animationProvider.drivers.push('$$animateCssDriver');
1538 var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
1539 var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
1541 var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
1542 var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
1544 function isDocumentFragment(node) {
1545 return node.parentNode && node.parentNode.nodeType === 11;
1548 this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
1549 function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) {
1551 // only browsers that support these properties can render animations
1552 if (!$sniffer.animations && !$sniffer.transitions) return noop;
1554 var bodyNode = $document[0].body;
1555 var rootNode = getDomNode($rootElement);
1557 var rootBodyElement = jqLite(
1558 // this is to avoid using something that exists outside of the body
1559 // we also special case the doc fragment case because our unit test code
1560 // appends the $rootElement to the body after the app has been bootstrapped
1561 isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
1564 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1566 return function initDriverFn(animationDetails) {
1567 return animationDetails.from && animationDetails.to
1568 ? prepareFromToAnchorAnimation(animationDetails.from,
1569 animationDetails.to,
1570 animationDetails.classes,
1571 animationDetails.anchors)
1572 : prepareRegularAnimation(animationDetails);
1575 function filterCssClasses(classes) {
1576 //remove all the `ng-` stuff
1577 return classes.replace(/\bng-\S+\b/g, '');
1580 function getUniqueValues(a, b) {
1581 if (isString(a)) a = a.split(' ');
1582 if (isString(b)) b = b.split(' ');
1583 return a.filter(function(val) {
1584 return b.indexOf(val) === -1;
1588 function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
1589 var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
1590 var startingClasses = filterCssClasses(getClassVal(clone));
1592 outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1593 inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1595 clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
1597 rootBodyElement.append(clone);
1599 var animatorIn, animatorOut = prepareOutAnimation();
1601 // the user may not end up using the `out` animation and
1602 // only making use of the `in` animation or vice-versa.
1603 // In either case we should allow this and not assume the
1604 // animation is over unless both animations are not used.
1606 animatorIn = prepareInAnimation();
1612 var startingAnimator = animatorOut || animatorIn;
1618 var currentAnimation = startingAnimator.start();
1619 currentAnimation.done(function() {
1620 currentAnimation = null;
1622 animatorIn = prepareInAnimation();
1624 currentAnimation = animatorIn.start();
1625 currentAnimation.done(function() {
1626 currentAnimation = null;
1630 return currentAnimation;
1633 // in the event that there is no `in` animation
1638 runner = new $$AnimateRunner({
1646 if (currentAnimation) {
1647 currentAnimation.end();
1653 function calculateAnchorStyles(anchor) {
1656 var coords = getDomNode(anchor).getBoundingClientRect();
1658 // we iterate directly since safari messes up and doesn't return
1659 // all the keys for the coords object when iterated
1660 forEach(['width','height','top','left'], function(key) {
1661 var value = coords[key];
1664 value += bodyNode.scrollTop;
1667 value += bodyNode.scrollLeft;
1670 styles[key] = Math.floor(value) + 'px';
1675 function prepareOutAnimation() {
1676 var animator = $animateCss(clone, {
1677 addClass: NG_OUT_ANCHOR_CLASS_NAME,
1679 from: calculateAnchorStyles(outAnchor)
1682 // read the comment within `prepareRegularAnimation` to understand
1683 // why this check is necessary
1684 return animator.$$willAnimate ? animator : null;
1687 function getClassVal(element) {
1688 return element.attr('class') || '';
1691 function prepareInAnimation() {
1692 var endingClasses = filterCssClasses(getClassVal(inAnchor));
1693 var toAdd = getUniqueValues(endingClasses, startingClasses);
1694 var toRemove = getUniqueValues(startingClasses, endingClasses);
1696 var animator = $animateCss(clone, {
1697 to: calculateAnchorStyles(inAnchor),
1698 addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
1699 removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
1703 // read the comment within `prepareRegularAnimation` to understand
1704 // why this check is necessary
1705 return animator.$$willAnimate ? animator : null;
1710 outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1711 inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1715 function prepareFromToAnchorAnimation(from, to, classes, anchors) {
1716 var fromAnimation = prepareRegularAnimation(from, noop);
1717 var toAnimation = prepareRegularAnimation(to, noop);
1719 var anchorAnimations = [];
1720 forEach(anchors, function(anchor) {
1721 var outElement = anchor['out'];
1722 var inElement = anchor['in'];
1723 var animator = prepareAnchoredAnimation(classes, outElement, inElement);
1725 anchorAnimations.push(animator);
1729 // no point in doing anything when there are no elements to animate
1730 if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
1734 var animationRunners = [];
1736 if (fromAnimation) {
1737 animationRunners.push(fromAnimation.start());
1741 animationRunners.push(toAnimation.start());
1744 forEach(anchorAnimations, function(animation) {
1745 animationRunners.push(animation.start());
1748 var runner = new $$AnimateRunner({
1750 cancel: endFn // CSS-driven animations cannot be cancelled, only ended
1753 $$AnimateRunner.all(animationRunners, function(status) {
1754 runner.complete(status);
1760 forEach(animationRunners, function(runner) {
1768 function prepareRegularAnimation(animationDetails) {
1769 var element = animationDetails.element;
1770 var options = animationDetails.options || {};
1772 if (animationDetails.structural) {
1773 options.event = animationDetails.event;
1774 options.structural = true;
1775 options.applyClassesEarly = true;
1777 // we special case the leave animation since we want to ensure that
1778 // the element is removed as soon as the animation is over. Otherwise
1779 // a flicker might appear or the element may not be removed at all
1780 if (animationDetails.event === 'leave') {
1781 options.onDone = options.domOperation;
1785 // We assign the preparationClasses as the actual animation event since
1786 // the internals of $animateCss will just suffix the event token values
1787 // with `-active` to trigger the animation.
1788 if (options.preparationClasses) {
1789 options.event = concatWithSpace(options.event, options.preparationClasses);
1792 var animator = $animateCss(element, options);
1794 // the driver lookup code inside of $$animation attempts to spawn a
1795 // driver one by one until a driver returns a.$$willAnimate animator object.
1796 // $animateCss will always return an object, however, it will pass in
1797 // a flag as a hint as to whether an animation was detected or not
1798 return animator.$$willAnimate ? animator : null;
1803 // TODO(matsko): use caching here to speed things up for detection
1804 // TODO(matsko): add documentation
1807 var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1808 this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
1809 function($injector, $$AnimateRunner, $$jqLite) {
1811 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1812 // $animateJs(element, 'enter');
1813 return function(element, event, classes, options) {
1814 var animationClosed = false;
1816 // the `classes` argument is optional and if it is not used
1817 // then the classes will be resolved from the element's className
1818 // property as well as options.addClass/options.removeClass.
1819 if (arguments.length === 3 && isObject(classes)) {
1824 options = prepareAnimationOptions(options);
1826 classes = element.attr('class') || '';
1827 if (options.addClass) {
1828 classes += ' ' + options.addClass;
1830 if (options.removeClass) {
1831 classes += ' ' + options.removeClass;
1835 var classesToAdd = options.addClass;
1836 var classesToRemove = options.removeClass;
1838 // the lookupAnimations function returns a series of animation objects that are
1839 // matched up with one or more of the CSS classes. These animation objects are
1840 // defined via the module.animation factory function. If nothing is detected then
1841 // we don't return anything which then makes $animation query the next driver.
1842 var animations = lookupAnimations(classes);
1844 if (animations.length) {
1845 var afterFn, beforeFn;
1846 if (event == 'leave') {
1848 afterFn = 'afterLeave'; // TODO(matsko): get rid of this
1850 beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
1854 if (event !== 'enter' && event !== 'move') {
1855 before = packageAnimations(element, event, options, animations, beforeFn);
1857 after = packageAnimations(element, event, options, animations, afterFn);
1860 // no matching animations
1861 if (!before && !after) return;
1863 function applyOptions() {
1864 options.domOperation();
1865 applyAnimationClasses(element, options);
1869 animationClosed = true;
1871 applyAnimationStyles(element, options);
1877 $$willAnimate: true,
1883 runner = new $$AnimateRunner();
1884 runner.complete(true);
1893 runner = new $$AnimateRunner();
1894 var closeActiveAnimations;
1898 chain.push(function(fn) {
1899 closeActiveAnimations = before(fn);
1904 chain.push(function(fn) {
1913 chain.push(function(fn) {
1914 closeActiveAnimations = after(fn);
1922 cancel: function() {
1923 endAnimations(true);
1927 $$AnimateRunner.chain(chain, onComplete);
1930 function onComplete(success) {
1932 runner.complete(success);
1935 function endAnimations(cancelled) {
1936 if (!animationClosed) {
1937 (closeActiveAnimations || noop)(cancelled);
1938 onComplete(cancelled);
1944 function executeAnimationFn(fn, element, event, options, onDone) {
1948 args = [element, options.from, options.to, onDone];
1952 args = [element, classesToAdd, classesToRemove, onDone];
1956 args = [element, classesToAdd, onDone];
1960 args = [element, classesToRemove, onDone];
1964 args = [element, onDone];
1970 var value = fn.apply(fn, args);
1972 if (isFunction(value.start)) {
1973 value = value.start();
1976 if (value instanceof $$AnimateRunner) {
1978 } else if (isFunction(value)) {
1979 // optional onEnd / onCancel callback
1987 function groupEventedAnimations(element, event, options, animations, fnName) {
1988 var operations = [];
1989 forEach(animations, function(ani) {
1990 var animation = ani[fnName];
1991 if (!animation) return;
1993 // note that all of these animations will run in parallel
1994 operations.push(function() {
1998 var resolved = false;
1999 var onAnimationComplete = function(rejected) {
2002 (endProgressCb || noop)(rejected);
2003 runner.complete(!rejected);
2007 runner = new $$AnimateRunner({
2009 onAnimationComplete();
2011 cancel: function() {
2012 onAnimationComplete(true);
2016 endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
2017 var cancelled = result === false;
2018 onAnimationComplete(cancelled);
2028 function packageAnimations(element, event, options, animations, fnName) {
2029 var operations = groupEventedAnimations(element, event, options, animations, fnName);
2030 if (operations.length === 0) {
2032 if (fnName === 'beforeSetClass') {
2033 a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
2034 b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
2035 } else if (fnName === 'setClass') {
2036 a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
2037 b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
2041 operations = operations.concat(a);
2044 operations = operations.concat(b);
2048 if (operations.length === 0) return;
2050 // TODO(matsko): add documentation
2051 return function startAnimation(callback) {
2053 if (operations.length) {
2054 forEach(operations, function(animateFn) {
2055 runners.push(animateFn());
2059 runners.length ? $$AnimateRunner.all(runners, callback) : callback();
2061 return function endFn(reject) {
2062 forEach(runners, function(runner) {
2063 reject ? runner.cancel() : runner.end();
2070 function lookupAnimations(classes) {
2071 classes = isArray(classes) ? classes : classes.split(' ');
2072 var matches = [], flagMap = {};
2073 for (var i=0; i < classes.length; i++) {
2074 var klass = classes[i],
2075 animationFactory = $animateProvider.$$registeredAnimations[klass];
2076 if (animationFactory && !flagMap[klass]) {
2077 matches.push($injector.get(animationFactory));
2078 flagMap[klass] = true;
2086 var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
2087 $$animationProvider.drivers.push('$$animateJsDriver');
2088 this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
2089 return function initDriverFn(animationDetails) {
2090 if (animationDetails.from && animationDetails.to) {
2091 var fromAnimation = prepareAnimation(animationDetails.from);
2092 var toAnimation = prepareAnimation(animationDetails.to);
2093 if (!fromAnimation && !toAnimation) return;
2097 var animationRunners = [];
2099 if (fromAnimation) {
2100 animationRunners.push(fromAnimation.start());
2104 animationRunners.push(toAnimation.start());
2107 $$AnimateRunner.all(animationRunners, done);
2109 var runner = new $$AnimateRunner({
2110 end: endFnFactory(),
2111 cancel: endFnFactory()
2116 function endFnFactory() {
2118 forEach(animationRunners, function(runner) {
2119 // at this point we cannot cancel animations for groups just yet. 1.5+
2125 function done(status) {
2126 runner.complete(status);
2131 return prepareAnimation(animationDetails);
2135 function prepareAnimation(animationDetails) {
2136 // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
2137 var element = animationDetails.element;
2138 var event = animationDetails.event;
2139 var options = animationDetails.options;
2140 var classes = animationDetails.classes;
2141 return $$animateJs(element, event, classes, options);
2146 var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
2147 var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
2148 var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2149 var PRE_DIGEST_STATE = 1;
2150 var RUNNING_STATE = 2;
2151 var ONE_SPACE = ' ';
2153 var rules = this.rules = {
2159 function makeTruthyCssClassMap(classString) {
2164 var keys = classString.split(ONE_SPACE);
2165 var map = Object.create(null);
2167 forEach(keys, function(key) {
2173 function hasMatchingClasses(newClassString, currentClassString) {
2174 if (newClassString && currentClassString) {
2175 var currentClassMap = makeTruthyCssClassMap(currentClassString);
2176 return newClassString.split(ONE_SPACE).some(function(className) {
2177 return currentClassMap[className];
2182 function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
2183 return rules[ruleType].some(function(fn) {
2184 return fn(element, currentAnimation, previousAnimation);
2188 function hasAnimationClasses(animation, and) {
2189 var a = (animation.addClass || '').length > 0;
2190 var b = (animation.removeClass || '').length > 0;
2191 return and ? a && b : a || b;
2194 rules.join.push(function(element, newAnimation, currentAnimation) {
2195 // if the new animation is class-based then we can just tack that on
2196 return !newAnimation.structural && hasAnimationClasses(newAnimation);
2199 rules.skip.push(function(element, newAnimation, currentAnimation) {
2200 // there is no need to animate anything if no classes are being added and
2201 // there is no structural animation that will be triggered
2202 return !newAnimation.structural && !hasAnimationClasses(newAnimation);
2205 rules.skip.push(function(element, newAnimation, currentAnimation) {
2206 // why should we trigger a new structural animation if the element will
2207 // be removed from the DOM anyway?
2208 return currentAnimation.event == 'leave' && newAnimation.structural;
2211 rules.skip.push(function(element, newAnimation, currentAnimation) {
2212 // if there is an ongoing current animation then don't even bother running the class-based animation
2213 return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
2216 rules.cancel.push(function(element, newAnimation, currentAnimation) {
2217 // there can never be two structural animations running at the same time
2218 return currentAnimation.structural && newAnimation.structural;
2221 rules.cancel.push(function(element, newAnimation, currentAnimation) {
2222 // if the previous animation is already running, but the new animation will
2223 // be triggered, but the new animation is structural
2224 return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
2227 rules.cancel.push(function(element, newAnimation, currentAnimation) {
2228 var nA = newAnimation.addClass;
2229 var nR = newAnimation.removeClass;
2230 var cA = currentAnimation.addClass;
2231 var cR = currentAnimation.removeClass;
2233 // early detection to save the global CPU shortage :)
2234 if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
2238 return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
2241 this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
2242 '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
2243 function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
2244 $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
2246 var activeAnimationsLookup = new $$HashMap();
2247 var disabledElementsLookup = new $$HashMap();
2248 var animationsEnabled = null;
2250 function postDigestTaskFactory() {
2251 var postDigestCalled = false;
2252 return function(fn) {
2253 // we only issue a call to postDigest before
2254 // it has first passed. This prevents any callbacks
2255 // from not firing once the animation has completed
2256 // since it will be out of the digest cycle.
2257 if (postDigestCalled) {
2260 $rootScope.$$postDigest(function() {
2261 postDigestCalled = true;
2268 // Wait until all directive and route-related templates are downloaded and
2269 // compiled. The $templateRequest.totalPendingRequests variable keeps track of
2270 // all of the remote templates being currently downloaded. If there are no
2271 // templates currently downloading then the watcher will still fire anyway.
2272 var deregisterWatch = $rootScope.$watch(
2273 function() { return $templateRequest.totalPendingRequests === 0; },
2275 if (!isEmpty) return;
2278 // Now that all templates have been downloaded, $animate will wait until
2279 // the post digest queue is empty before enabling animations. By having two
2280 // calls to $postDigest calls we can ensure that the flag is enabled at the
2281 // very end of the post digest queue. Since all of the animations in $animate
2282 // use $postDigest, it's important that the code below executes at the end.
2283 // This basically means that the page is fully downloaded and compiled before
2284 // any animations are triggered.
2285 $rootScope.$$postDigest(function() {
2286 $rootScope.$$postDigest(function() {
2287 // we check for null directly in the event that the application already called
2288 // .enabled() with whatever arguments that it provided it with
2289 if (animationsEnabled === null) {
2290 animationsEnabled = true;
2297 var callbackRegistry = {};
2299 // remember that the classNameFilter is set during the provider/config
2300 // stage therefore we can optimize here and setup a helper function
2301 var classNameFilter = $animateProvider.classNameFilter();
2302 var isAnimatableClassName = !classNameFilter
2303 ? function() { return true; }
2304 : function(className) {
2305 return classNameFilter.test(className);
2308 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2310 function normalizeAnimationDetails(element, animation) {
2311 return mergeAnimationDetails(element, animation, {});
2314 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2315 var contains = Node.prototype.contains || function(arg) {
2316 // jshint bitwise: false
2317 return this === arg || !!(this.compareDocumentPosition(arg) & 16);
2318 // jshint bitwise: true
2321 function findCallbacks(parent, element, event) {
2322 var targetNode = getDomNode(element);
2323 var targetParentNode = getDomNode(parent);
2326 var entries = callbackRegistry[event];
2328 forEach(entries, function(entry) {
2329 if (contains.call(entry.node, targetNode)) {
2330 matches.push(entry.callback);
2331 } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
2332 matches.push(entry.callback);
2341 on: function(event, container, callback) {
2342 var node = extractElementNode(container);
2343 callbackRegistry[event] = callbackRegistry[event] || [];
2344 callbackRegistry[event].push({
2350 off: function(event, container, callback) {
2351 var entries = callbackRegistry[event];
2352 if (!entries) return;
2354 callbackRegistry[event] = arguments.length === 1
2356 : filterFromRegistry(entries, container, callback);
2358 function filterFromRegistry(list, matchContainer, matchCallback) {
2359 var containerNode = extractElementNode(matchContainer);
2360 return list.filter(function(entry) {
2361 var isMatch = entry.node === containerNode &&
2362 (!matchCallback || entry.callback === matchCallback);
2368 pin: function(element, parentElement) {
2369 assertArg(isElement(element), 'element', 'not an element');
2370 assertArg(isElement(parentElement), 'parentElement', 'not an element');
2371 element.data(NG_ANIMATE_PIN_DATA, parentElement);
2374 push: function(element, event, options, domOperation) {
2375 options = options || {};
2376 options.domOperation = domOperation;
2377 return queueAnimation(element, event, options);
2380 // this method has four signatures:
2381 // () - global getter
2382 // (bool) - global setter
2383 // (element) - element getter
2384 // (element, bool) - element setter<F37>
2385 enabled: function(element, bool) {
2386 var argCount = arguments.length;
2388 if (argCount === 0) {
2389 // () - Global getter
2390 bool = !!animationsEnabled;
2392 var hasElement = isElement(element);
2395 // (bool) - Global setter
2396 bool = animationsEnabled = !!element;
2398 var node = getDomNode(element);
2399 var recordExists = disabledElementsLookup.get(node);
2401 if (argCount === 1) {
2402 // (element) - Element getter
2403 bool = !recordExists;
2405 // (element, bool) - Element setter
2406 disabledElementsLookup.put(node, !bool);
2415 function queueAnimation(element, event, initialOptions) {
2416 // we always make a copy of the options since
2417 // there should never be any side effects on
2418 // the input data when running `$animateCss`.
2419 var options = copy(initialOptions);
2422 element = stripCommentsFromElement(element);
2424 node = getDomNode(element);
2425 parent = element.parent();
2428 options = prepareAnimationOptions(options);
2430 // we create a fake runner with a working promise.
2431 // These methods will become available after the digest has passed
2432 var runner = new $$AnimateRunner();
2434 // this is used to trigger callbacks in postDigest mode
2435 var runInNextPostDigestOrNow = postDigestTaskFactory();
2437 if (isArray(options.addClass)) {
2438 options.addClass = options.addClass.join(' ');
2441 if (options.addClass && !isString(options.addClass)) {
2442 options.addClass = null;
2445 if (isArray(options.removeClass)) {
2446 options.removeClass = options.removeClass.join(' ');
2449 if (options.removeClass && !isString(options.removeClass)) {
2450 options.removeClass = null;
2453 if (options.from && !isObject(options.from)) {
2454 options.from = null;
2457 if (options.to && !isObject(options.to)) {
2461 // there are situations where a directive issues an animation for
2462 // a jqLite wrapper that contains only comment nodes... If this
2463 // happens then there is no way we can perform an animation
2469 var className = [node.className, options.addClass, options.removeClass].join(' ');
2470 if (!isAnimatableClassName(className)) {
2475 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2477 // this is a hard disable of all animations for the application or on
2478 // the element itself, therefore there is no need to continue further
2479 // past this point if not enabled
2480 // Animations are also disabled if the document is currently hidden (page is not visible
2481 // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
2482 var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
2483 var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2484 var hasExistingAnimation = !!existingAnimation.state;
2486 // there is no point in traversing the same collection of parent ancestors if a followup
2487 // animation will be run on the same element that already did all that checking work
2488 if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
2489 skipAnimations = !areAnimationsAllowed(element, parent, event);
2492 if (skipAnimations) {
2498 closeChildAnimations(element);
2501 var newAnimation = {
2502 structural: isStructural,
2505 addClass: options.addClass,
2506 removeClass: options.removeClass,
2512 if (hasExistingAnimation) {
2513 var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
2514 if (skipAnimationFlag) {
2515 if (existingAnimation.state === RUNNING_STATE) {
2519 mergeAnimationDetails(element, existingAnimation, newAnimation);
2520 return existingAnimation.runner;
2523 var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
2524 if (cancelAnimationFlag) {
2525 if (existingAnimation.state === RUNNING_STATE) {
2526 // this will end the animation right away and it is safe
2527 // to do so since the animation is already running and the
2528 // runner callback code will run in async
2529 existingAnimation.runner.end();
2530 } else if (existingAnimation.structural) {
2531 // this means that the animation is queued into a digest, but
2532 // hasn't started yet. Therefore it is safe to run the close
2533 // method which will call the runner methods in async.
2534 existingAnimation.close();
2536 // this will merge the new animation options into existing animation options
2537 mergeAnimationDetails(element, existingAnimation, newAnimation);
2539 return existingAnimation.runner;
2542 // a joined animation means that this animation will take over the existing one
2543 // so an example would involve a leave animation taking over an enter. Then when
2544 // the postDigest kicks in the enter will be ignored.
2545 var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
2546 if (joinAnimationFlag) {
2547 if (existingAnimation.state === RUNNING_STATE) {
2548 normalizeAnimationDetails(element, newAnimation);
2550 applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
2552 event = newAnimation.event = existingAnimation.event;
2553 options = mergeAnimationDetails(element, existingAnimation, newAnimation);
2555 //we return the same runner since only the option values of this animation will
2556 //be fed into the `existingAnimation`.
2557 return existingAnimation.runner;
2562 // normalization in this case means that it removes redundant CSS classes that
2563 // already exist (addClass) or do not exist (removeClass) on the element
2564 normalizeAnimationDetails(element, newAnimation);
2567 // when the options are merged and cleaned up we may end up not having to do
2568 // an animation at all, therefore we should check this before issuing a post
2569 // digest callback. Structural animations will always run no matter what.
2570 var isValidAnimation = newAnimation.structural;
2571 if (!isValidAnimation) {
2572 // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
2573 isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
2574 || hasAnimationClasses(newAnimation);
2577 if (!isValidAnimation) {
2579 clearElementAnimationState(element);
2583 // the counter keeps track of cancelled animations
2584 var counter = (existingAnimation.counter || 0) + 1;
2585 newAnimation.counter = counter;
2587 markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
2589 $rootScope.$$postDigest(function() {
2590 var animationDetails = activeAnimationsLookup.get(node);
2591 var animationCancelled = !animationDetails;
2592 animationDetails = animationDetails || {};
2594 // if addClass/removeClass is called before something like enter then the
2595 // registered parent element may not be present. The code below will ensure
2596 // that a final value for parent element is obtained
2597 var parentElement = element.parent() || [];
2599 // animate/structural/class-based animations all have requirements. Otherwise there
2600 // is no point in performing an animation. The parent node must also be set.
2601 var isValidAnimation = parentElement.length > 0
2602 && (animationDetails.event === 'animate'
2603 || animationDetails.structural
2604 || hasAnimationClasses(animationDetails));
2606 // this means that the previous animation was cancelled
2607 // even if the follow-up animation is the same event
2608 if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
2609 // if another animation did not take over then we need
2610 // to make sure that the domOperation and options are
2611 // handled accordingly
2612 if (animationCancelled) {
2613 applyAnimationClasses(element, options);
2614 applyAnimationStyles(element, options);
2617 // if the event changed from something like enter to leave then we do
2618 // it, otherwise if it's the same then the end result will be the same too
2619 if (animationCancelled || (isStructural && animationDetails.event !== event)) {
2620 options.domOperation();
2624 // in the event that the element animation was not cancelled or a follow-up animation
2625 // isn't allowed to animate from here then we need to clear the state of the element
2626 // so that any future animations won't read the expired animation data.
2627 if (!isValidAnimation) {
2628 clearElementAnimationState(element);
2634 // this combined multiple class to addClass / removeClass into a setClass event
2635 // so long as a structural event did not take over the animation
2636 event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
2638 : animationDetails.event;
2640 markElementAnimationState(element, RUNNING_STATE);
2641 var realRunner = $$animation(element, event, animationDetails.options);
2643 realRunner.done(function(status) {
2645 var animationDetails = activeAnimationsLookup.get(node);
2646 if (animationDetails && animationDetails.counter === counter) {
2647 clearElementAnimationState(getDomNode(element));
2649 notifyProgress(runner, event, 'close', {});
2652 // this will update the runner's flow-control events based on
2653 // the `realRunner` object.
2654 runner.setHost(realRunner);
2655 notifyProgress(runner, event, 'start', {});
2660 function notifyProgress(runner, event, phase, data) {
2661 runInNextPostDigestOrNow(function() {
2662 var callbacks = findCallbacks(parent, element, event);
2663 if (callbacks.length) {
2664 // do not optimize this call here to RAF because
2665 // we don't know how heavy the callback code here will
2666 // be and if this code is buffered then this can
2667 // lead to a performance regression.
2669 forEach(callbacks, function(callback) {
2670 callback(element, phase, data);
2675 runner.progress(event, phase, data);
2678 function close(reject) { // jshint ignore:line
2679 clearGeneratedClasses(element, options);
2680 applyAnimationClasses(element, options);
2681 applyAnimationStyles(element, options);
2682 options.domOperation();
2683 runner.complete(!reject);
2687 function closeChildAnimations(element) {
2688 var node = getDomNode(element);
2689 var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
2690 forEach(children, function(child) {
2691 var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
2692 var animationDetails = activeAnimationsLookup.get(child);
2693 if (animationDetails) {
2696 animationDetails.runner.end();
2698 case PRE_DIGEST_STATE:
2699 activeAnimationsLookup.remove(child);
2706 function clearElementAnimationState(element) {
2707 var node = getDomNode(element);
2708 node.removeAttribute(NG_ANIMATE_ATTR_NAME);
2709 activeAnimationsLookup.remove(node);
2712 function isMatchingElement(nodeOrElmA, nodeOrElmB) {
2713 return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
2717 * This fn returns false if any of the following is true:
2718 * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
2719 * b) a parent element has an ongoing structural animation, and animateChildren is false
2720 * c) the element is not a child of the body
2721 * d) the element is not a child of the $rootElement
2723 function areAnimationsAllowed(element, parentElement, event) {
2724 var bodyElement = jqLite($document[0].body);
2725 var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
2726 var rootElementDetected = isMatchingElement(element, $rootElement);
2727 var parentAnimationDetected = false;
2728 var animateChildren;
2729 var elementDisabled = disabledElementsLookup.get(getDomNode(element));
2731 var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2733 parentElement = parentHost;
2736 while (parentElement && parentElement.length) {
2737 if (!rootElementDetected) {
2738 // angular doesn't want to attempt to animate elements outside of the application
2739 // therefore we need to ensure that the rootElement is an ancestor of the current element
2740 rootElementDetected = isMatchingElement(parentElement, $rootElement);
2743 var parentNode = parentElement[0];
2744 if (parentNode.nodeType !== ELEMENT_NODE) {
2745 // no point in inspecting the #document element
2749 var details = activeAnimationsLookup.get(parentNode) || {};
2750 // either an enter, leave or move animation will commence
2751 // therefore we can't allow any animations to take place
2752 // but if a parent animation is class-based then that's ok
2753 if (!parentAnimationDetected) {
2754 var parentElementDisabled = disabledElementsLookup.get(parentNode);
2756 if (parentElementDisabled === true && elementDisabled !== false) {
2757 // disable animations if the user hasn't explicitly enabled animations on the
2759 elementDisabled = true;
2760 // element is disabled via parent element, no need to check anything else
2762 } else if (parentElementDisabled === false) {
2763 elementDisabled = false;
2765 parentAnimationDetected = details.structural;
2768 if (isUndefined(animateChildren) || animateChildren === true) {
2769 var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
2770 if (isDefined(value)) {
2771 animateChildren = value;
2775 // there is no need to continue traversing at this point
2776 if (parentAnimationDetected && animateChildren === false) break;
2778 if (!bodyElementDetected) {
2779 // we also need to ensure that the element is or will be a part of the body element
2780 // otherwise it is pointless to even issue an animation to be rendered
2781 bodyElementDetected = isMatchingElement(parentElement, bodyElement);
2784 if (bodyElementDetected && rootElementDetected) {
2785 // If both body and root have been found, any other checks are pointless,
2786 // as no animation data should live outside the application
2790 if (!rootElementDetected) {
2791 // If no rootElement is detected, check if the parentElement is pinned to another element
2792 parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2794 // The pin target element becomes the next parent element
2795 parentElement = parentHost;
2800 parentElement = parentElement.parent();
2803 var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
2804 return allowAnimation && rootElementDetected && bodyElementDetected;
2807 function markElementAnimationState(element, state, details) {
2808 details = details || {};
2809 details.state = state;
2811 var node = getDomNode(element);
2812 node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
2814 var oldValue = activeAnimationsLookup.get(node);
2815 var newValue = oldValue
2816 ? extend(oldValue, details)
2818 activeAnimationsLookup.put(node, newValue);
2823 var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2824 var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2826 var drivers = this.drivers = [];
2828 var RUNNER_STORAGE_KEY = '$$animationRunner';
2830 function setRunner(element, runner) {
2831 element.data(RUNNER_STORAGE_KEY, runner);
2834 function removeRunner(element) {
2835 element.removeData(RUNNER_STORAGE_KEY);
2838 function getRunner(element) {
2839 return element.data(RUNNER_STORAGE_KEY);
2842 this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
2843 function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
2845 var animationQueue = [];
2846 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2848 function sortAnimations(animations) {
2849 var tree = { children: [] };
2850 var i, lookup = new $$HashMap();
2852 // this is done first beforehand so that the hashmap
2853 // is filled with a list of the elements that will be animated
2854 for (i = 0; i < animations.length; i++) {
2855 var animation = animations[i];
2856 lookup.put(animation.domNode, animations[i] = {
2857 domNode: animation.domNode,
2863 for (i = 0; i < animations.length; i++) {
2864 processNode(animations[i]);
2867 return flatten(tree);
2869 function processNode(entry) {
2870 if (entry.processed) return entry;
2871 entry.processed = true;
2873 var elementNode = entry.domNode;
2874 var parentNode = elementNode.parentNode;
2875 lookup.put(elementNode, entry);
2878 while (parentNode) {
2879 parentEntry = lookup.get(parentNode);
2881 if (!parentEntry.processed) {
2882 parentEntry = processNode(parentEntry);
2886 parentNode = parentNode.parentNode;
2889 (parentEntry || tree).children.push(entry);
2893 function flatten(tree) {
2898 for (i = 0; i < tree.children.length; i++) {
2899 queue.push(tree.children[i]);
2902 var remainingLevelEntries = queue.length;
2903 var nextLevelEntries = 0;
2906 for (i = 0; i < queue.length; i++) {
2907 var entry = queue[i];
2908 if (remainingLevelEntries <= 0) {
2909 remainingLevelEntries = nextLevelEntries;
2910 nextLevelEntries = 0;
2915 entry.children.forEach(function(childEntry) {
2917 queue.push(childEntry);
2919 remainingLevelEntries--;
2930 // TODO(matsko): document the signature in a better way
2931 return function(element, event, options) {
2932 options = prepareAnimationOptions(options);
2933 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2935 // there is no animation at the current moment, however
2936 // these runner methods will get later updated with the
2937 // methods leading into the driver's end/cancel methods
2938 // for now they just stop the animation from starting
2939 var runner = new $$AnimateRunner({
2940 end: function() { close(); },
2941 cancel: function() { close(true); }
2944 if (!drivers.length) {
2949 setRunner(element, runner);
2951 var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
2952 var tempClasses = options.tempClasses;
2954 classes += ' ' + tempClasses;
2955 options.tempClasses = null;
2958 var prepareClassName;
2960 prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
2961 $$jqLite.addClass(element, prepareClassName);
2964 animationQueue.push({
2965 // this data is used by the postDigest code and passed into
2966 // the driver step function
2970 structural: isStructural,
2972 beforeStart: beforeStart,
2976 element.on('$destroy', handleDestroyedElement);
2978 // we only want there to be one function called within the post digest
2979 // block. This way we can group animations for all the animations that
2980 // were apart of the same postDigest flush call.
2981 if (animationQueue.length > 1) return runner;
2983 $rootScope.$$postDigest(function() {
2984 var animations = [];
2985 forEach(animationQueue, function(entry) {
2986 // the element was destroyed early on which removed the runner
2987 // form its storage. This means we can't animate this element
2988 // at all and it already has been closed due to destruction.
2989 if (getRunner(entry.element)) {
2990 animations.push(entry);
2996 // now any future animations will be in another postDigest
2997 animationQueue.length = 0;
2999 var groupedAnimations = groupAnimations(animations);
3000 var toBeSortedAnimations = [];
3002 forEach(groupedAnimations, function(animationEntry) {
3003 toBeSortedAnimations.push({
3004 domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
3005 fn: function triggerAnimationStart() {
3006 // it's important that we apply the `ng-animate` CSS class and the
3007 // temporary classes before we do any driver invoking since these
3008 // CSS classes may be required for proper CSS detection.
3009 animationEntry.beforeStart();
3011 var startAnimationFn, closeFn = animationEntry.close;
3013 // in the event that the element was removed before the digest runs or
3014 // during the RAF sequencing then we should not trigger the animation.
3015 var targetElement = animationEntry.anchors
3016 ? (animationEntry.from.element || animationEntry.to.element)
3017 : animationEntry.element;
3019 if (getRunner(targetElement)) {
3020 var operation = invokeFirstDriver(animationEntry);
3022 startAnimationFn = operation.start;
3026 if (!startAnimationFn) {
3029 var animationRunner = startAnimationFn();
3030 animationRunner.done(function(status) {
3033 updateAnimationRunners(animationEntry, animationRunner);
3039 // we need to sort each of the animations in order of parent to child
3040 // relationships. This ensures that the child classes are applied at the
3042 $$rAFScheduler(sortAnimations(toBeSortedAnimations));
3047 // TODO(matsko): change to reference nodes
3048 function getAnchorNodes(node) {
3049 var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
3050 var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
3052 : node.querySelectorAll(SELECTOR);
3054 forEach(items, function(node) {
3055 var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
3056 if (attr && attr.length) {
3063 function groupAnimations(animations) {
3064 var preparedAnimations = [];
3066 forEach(animations, function(animation, index) {
3067 var element = animation.element;
3068 var node = getDomNode(element);
3069 var event = animation.event;
3070 var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
3071 var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
3073 if (anchorNodes.length) {
3074 var direction = enterOrMove ? 'to' : 'from';
3076 forEach(anchorNodes, function(anchor) {
3077 var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
3078 refLookup[key] = refLookup[key] || {};
3079 refLookup[key][direction] = {
3081 element: jqLite(anchor)
3085 preparedAnimations.push(animation);
3089 var usedIndicesLookup = {};
3090 var anchorGroups = {};
3091 forEach(refLookup, function(operations, key) {
3092 var from = operations.from;
3093 var to = operations.to;
3096 // only one of these is set therefore we can't have an
3097 // anchor animation since all three pieces are required
3098 var index = from ? from.animationID : to.animationID;
3099 var indexKey = index.toString();
3100 if (!usedIndicesLookup[indexKey]) {
3101 usedIndicesLookup[indexKey] = true;
3102 preparedAnimations.push(animations[index]);
3107 var fromAnimation = animations[from.animationID];
3108 var toAnimation = animations[to.animationID];
3109 var lookupKey = from.animationID.toString();
3110 if (!anchorGroups[lookupKey]) {
3111 var group = anchorGroups[lookupKey] = {
3113 beforeStart: function() {
3114 fromAnimation.beforeStart();
3115 toAnimation.beforeStart();
3118 fromAnimation.close();
3119 toAnimation.close();
3121 classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
3122 from: fromAnimation,
3124 anchors: [] // TODO(matsko): change to reference nodes
3127 // the anchor animations require that the from and to elements both have at least
3128 // one shared CSS class which effectively marries the two elements together to use
3129 // the same animation driver and to properly sequence the anchor animation.
3130 if (group.classes.length) {
3131 preparedAnimations.push(group);
3133 preparedAnimations.push(fromAnimation);
3134 preparedAnimations.push(toAnimation);
3138 anchorGroups[lookupKey].anchors.push({
3139 'out': from.element, 'in': to.element
3143 return preparedAnimations;
3146 function cssClassesIntersection(a,b) {
3151 for (var i = 0; i < a.length; i++) {
3153 if (aa.substring(0,3) === 'ng-') continue;
3155 for (var j = 0; j < b.length; j++) {
3163 return matches.join(' ');
3166 function invokeFirstDriver(animationDetails) {
3167 // we loop in reverse order since the more general drivers (like CSS and JS)
3168 // may attempt more elements, but custom drivers are more particular
3169 for (var i = drivers.length - 1; i >= 0; i--) {
3170 var driverName = drivers[i];
3171 if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
3173 var factory = $injector.get(driverName);
3174 var driver = factory(animationDetails);
3181 function beforeStart() {
3182 element.addClass(NG_ANIMATE_CLASSNAME);
3184 $$jqLite.addClass(element, tempClasses);
3186 if (prepareClassName) {
3187 $$jqLite.removeClass(element, prepareClassName);
3188 prepareClassName = null;
3192 function updateAnimationRunners(animation, newRunner) {
3193 if (animation.from && animation.to) {
3194 update(animation.from.element);
3195 update(animation.to.element);
3197 update(animation.element);
3200 function update(element) {
3201 getRunner(element).setHost(newRunner);
3205 function handleDestroyedElement() {
3206 var runner = getRunner(element);
3207 if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
3212 function close(rejected) { // jshint ignore:line
3213 element.off('$destroy', handleDestroyedElement);
3214 removeRunner(element);
3216 applyAnimationClasses(element, options);
3217 applyAnimationStyles(element, options);
3218 options.domOperation();
3221 $$jqLite.removeClass(element, tempClasses);
3224 element.removeClass(NG_ANIMATE_CLASSNAME);
3225 runner.complete(!rejected);
3233 * @name ngAnimateSwap
3239 * ngAnimateSwap is a animation-oriented directive that allows for the container to
3240 * be removed and entered in whenever the associated expression changes. A
3241 * common usecase for this directive is a rotating banner component which
3242 * contains one image being present at a time. When the active image changes
3243 * then the old image will perform a `leave` animation and the new element
3244 * will be inserted via an `enter` animation.
3247 * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
3248 * deps="angular-animate.js"
3249 * animations="true" fixBase="true">
3250 * <file name="index.html">
3251 * <div class="container" ng-controller="AppCtrl">
3252 * <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
3257 * <file name="script.js">
3258 * angular.module('ngAnimateSwapExample', ['ngAnimate'])
3259 * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
3260 * $scope.number = 0;
3261 * $interval(function() {
3265 * var colors = ['red','blue','green','yellow','orange'];
3266 * $scope.colorClass = function(number) {
3267 * return colors[number % colors.length];
3271 * <file name="animations.css">
3275 * position:relative;
3277 * border:2px solid black;
3279 * .container .cell {
3281 * text-align:center;
3282 * line-height:250px;
3283 * position:absolute;
3287 * border-bottom:2px solid black;
3289 * .swap-animation.ng-enter, .swap-animation.ng-leave {
3290 * transition:0.5s linear all;
3292 * .swap-animation.ng-enter {
3295 * .swap-animation.ng-enter-active {
3298 * .swap-animation.ng-leave {
3301 * .swap-animation.ng-leave-active {
3304 * .red { background:red; }
3305 * .green { background:green; }
3306 * .blue { background:blue; }
3307 * .yellow { background:yellow; }
3308 * .orange { background:orange; }
3312 var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
3315 transclude: 'element',
3317 priority: 600, // we use 600 here to ensure that the directive is caught before others
3318 link: function(scope, $element, attrs, ctrl, $transclude) {
3319 var previousElement, previousScope;
3320 scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
3321 if (previousElement) {
3322 $animate.leave(previousElement);
3324 if (previousScope) {
3325 previousScope.$destroy();
3326 previousScope = null;
3328 if (value || value === 0) {
3329 previousScope = scope.$new();
3330 $transclude(previousScope, function(element) {
3331 previousElement = element;
3332 $animate.enter(element, null, $element);
3340 /* global angularAnimateModule: true,
3342 ngAnimateSwapDirective,
3343 $$AnimateAsyncRunFactory,
3344 $$rAFSchedulerFactory,
3345 $$AnimateChildrenDirective,
3346 $$AnimateQueueProvider,
3347 $$AnimationProvider,
3348 $AnimateCssProvider,
3349 $$AnimateCssDriverProvider,
3350 $$AnimateJsProvider,
3351 $$AnimateJsDriverProvider,
3359 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
3360 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
3362 * <div doc-module-components="ngAnimate"></div>
3365 * 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
3366 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
3367 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
3368 * the HTML element that the animation will be triggered on.
3370 * ## Directive Support
3371 * The following directives are "animation aware":
3373 * | Directive | Supported Animations |
3374 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
3375 * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
3376 * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
3377 * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
3378 * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
3379 * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
3380 * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
3381 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
3382 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
3383 * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
3384 * | {@link module:ngMessages#animations ngMessage} | enter and leave |
3386 * (More information can be found by visiting each the documentation associated with each directive.)
3388 * ## CSS-based Animations
3390 * 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
3391 * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
3393 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
3396 * <div ng-if="bool" class="fade">
3399 * <button ng-click="bool=true">Fade In!</button>
3400 * <button ng-click="bool=false">Fade Out!</button>
3403 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
3406 * /* The starting CSS styles for the enter animation */
3408 * transition:0.5s linear all;
3412 * /* The finishing CSS styles for the enter animation */
3413 * .fade.ng-enter.ng-enter-active {
3418 * 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
3419 * 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
3420 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
3422 * 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:
3425 * /* now the element will fade out before it is removed from the DOM */
3427 * transition:0.5s linear all;
3430 * .fade.ng-leave.ng-leave-active {
3435 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
3438 * /* there is no need to define anything inside of the destination
3439 * CSS class since the keyframe will take charge of the animation */
3441 * animation: my_fade_animation 0.5s linear;
3442 * -webkit-animation: my_fade_animation 0.5s linear;
3445 * @keyframes my_fade_animation {
3446 * from { opacity:1; }
3450 * @-webkit-keyframes my_fade_animation {
3451 * from { opacity:1; }
3456 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
3458 * ### CSS Class-based Animations
3460 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
3461 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
3464 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
3467 * <div ng-show="bool" class="fade">
3470 * <button ng-click="bool=true">Toggle</button>
3474 * transition:0.5s linear all;
3480 * 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
3481 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
3483 * 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
3487 * <div ng-class="{on:onOff}" class="highlight">
3488 * Highlight this box
3490 * <button ng-click="onOff=!onOff">Toggle</button>
3494 * transition:0.5s linear all;
3496 * .highlight.on-add {
3500 * background:yellow;
3502 * .highlight.on-remove {
3508 * We can also make use of CSS keyframes by placing them within the CSS classes.
3511 * ### CSS Staggering Animations
3512 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
3513 * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
3514 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
3515 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
3516 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
3519 * .my-animation.ng-enter {
3520 * /* standard transition code */
3521 * transition: 1s linear all;
3524 * .my-animation.ng-enter-stagger {
3525 * /* this will have a 100ms delay between each successive leave animation */
3526 * transition-delay: 0.1s;
3528 * /* As of 1.4.4, this must always be set: it signals ngAnimate
3529 * to not accidentally inherit a delay property from another CSS class */
3530 * transition-duration: 0s;
3532 * .my-animation.ng-enter.ng-enter-active {
3533 * /* standard transition styles */
3538 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
3539 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
3540 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
3541 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
3543 * The following code will issue the **ng-leave-stagger** event on the element provided:
3546 * var kids = parent.children();
3548 * $animate.leave(kids[0]); //stagger index=0
3549 * $animate.leave(kids[1]); //stagger index=1
3550 * $animate.leave(kids[2]); //stagger index=2
3551 * $animate.leave(kids[3]); //stagger index=3
3552 * $animate.leave(kids[4]); //stagger index=4
3554 * window.requestAnimationFrame(function() {
3555 * //stagger has reset itself
3556 * $animate.leave(kids[5]); //stagger index=0
3557 * $animate.leave(kids[6]); //stagger index=1
3563 * Stagger animations are currently only supported within CSS-defined animations.
3565 * ### The `ng-animate` CSS class
3567 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
3568 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
3570 * Therefore, animations can be applied to an element using this temporary class directly via CSS.
3573 * .zipper.ng-animate {
3574 * transition:0.5s linear all;
3576 * .zipper.ng-enter {
3579 * .zipper.ng-enter.ng-enter-active {
3582 * .zipper.ng-leave {
3585 * .zipper.ng-leave.ng-leave-active {
3590 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
3591 * the CSS class once an animation has completed.)
3594 * ### The `ng-[event]-prepare` class
3596 * This is a special class that can be used to prevent unwanted flickering / flash of content before
3597 * the actual animation starts. The class is added as soon as an animation is initialized, but removed
3598 * before the actual animation starts (after waiting for a $digest).
3599 * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
3601 * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
3602 * into elements that have class-based animations such as `ngClass`.
3605 * <div ng-class="{red: myProp}">
3606 * <div ng-class="{blue: myProp}">
3607 * <div class="message" ng-if="myProp"></div>
3612 * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
3613 * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
3616 * .message.ng-enter-prepare {
3622 * ## JavaScript-based Animations
3624 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
3625 * 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
3626 * `module.animation()` module function we can register the animation.
3628 * Let's see an example of a enter/leave animation using `ngRepeat`:
3631 * <div ng-repeat="item in items" class="slide">
3636 * 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`:
3639 * myModule.animation('.slide', [function() {
3641 * // make note that other events (like addClass/removeClass)
3642 * // have different function input parameters
3643 * enter: function(element, doneFn) {
3644 * jQuery(element).fadeIn(1000, doneFn);
3646 * // remember to call doneFn so that angular
3647 * // knows that the animation has concluded
3650 * move: function(element, doneFn) {
3651 * jQuery(element).fadeIn(1000, doneFn);
3654 * leave: function(element, doneFn) {
3655 * jQuery(element).fadeOut(1000, doneFn);
3661 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
3662 * greensock.js and velocity.js.
3664 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
3665 * our animations inside of the same registered animation, however, the function input arguments are a bit different:
3668 * <div ng-class="color" class="colorful">
3671 * <button ng-click="color='red'">Change to red</button>
3672 * <button ng-click="color='blue'">Change to blue</button>
3673 * <button ng-click="color='green'">Change to green</button>
3677 * myModule.animation('.colorful', [function() {
3679 * addClass: function(element, className, doneFn) {
3680 * // do some cool animation and call the doneFn
3682 * removeClass: function(element, className, doneFn) {
3683 * // do some cool animation and call the doneFn
3685 * setClass: function(element, addedClass, removedClass, doneFn) {
3686 * // do some cool animation and call the doneFn
3692 * ## CSS + JS Animations Together
3694 * 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,
3695 * 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
3696 * charge of the animation**:
3699 * <div ng-if="bool" class="slide">
3705 * myModule.animation('.slide', [function() {
3707 * enter: function(element, doneFn) {
3708 * jQuery(element).slideIn(1000, doneFn);
3716 * transition:0.5s linear all;
3717 * transform:translateY(-100px);
3719 * .slide.ng-enter.ng-enter-active {
3720 * transform:translateY(0);
3724 * 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
3725 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
3726 * our own JS-based animation code:
3729 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3731 * enter: function(element) {
3732 * // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
3733 * return $animateCss(element, {
3742 * 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.
3744 * 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
3745 * 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
3746 * data into `$animateCss` directly:
3749 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3751 * enter: function(element) {
3752 * return $animateCss(element, {
3755 * addClass: 'maroon-setting',
3756 * from: { height:0 },
3757 * to: { height: 200 }
3764 * Now we can fill in the rest via our transition CSS code:
3767 * /* the transition tells ngAnimate to make the animation happen */
3768 * .slide.ng-enter { transition:0.5s linear all; }
3770 * /* this extra CSS class will be absorbed into the transition
3771 * since the $animateCss code is adding the class */
3772 * .maroon-setting { background:red; }
3775 * 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.
3777 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
3779 * ## Animation Anchoring (via `ng-animate-ref`)
3781 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
3782 * structural areas of an application (like views) by pairing up elements using an attribute
3783 * called `ng-animate-ref`.
3785 * Let's say for example we have two views that are managed by `ng-view` and we want to show
3786 * that there is a relationship between two components situated in within these views. By using the
3787 * `ng-animate-ref` attribute we can identify that the two components are paired together and we
3788 * can then attach an animation, which is triggered when the view changes.
3790 * Say for example we have the following template code:
3793 * <!-- index.html -->
3794 * <div ng-view class="view-animation">
3797 * <!-- home.html -->
3798 * <a href="#/banner-page">
3799 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3802 * <!-- banner-page.html -->
3803 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3806 * Now, when the view changes (once the link is clicked), ngAnimate will examine the
3807 * HTML contents to see if there is a match reference between any components in the view
3808 * that is leaving and the view that is entering. It will scan both the view which is being
3809 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
3810 * contain a matching ref value.
3812 * The two images match since they share the same ref value. ngAnimate will now create a
3813 * transport element (which is a clone of the first image element) and it will then attempt
3814 * to animate to the position of the second image element in the next view. For the animation to
3815 * work a special CSS class called `ng-anchor` will be added to the transported element.
3817 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
3818 * ngAnimate will handle the entire transition for us as well as the addition and removal of
3819 * any changes of CSS classes between the elements:
3822 * .banner.ng-anchor {
3823 * /* this animation will last for 1 second since there are
3824 * two phases to the animation (an `in` and an `out` phase) */
3825 * transition:0.5s linear all;
3829 * We also **must** include animations for the views that are being entered and removed
3830 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
3833 * .view-animation.ng-enter, .view-animation.ng-leave {
3834 * transition:0.5s linear all;
3840 * .view-animation.ng-enter {
3841 * transform:translateX(100%);
3843 * .view-animation.ng-leave,
3844 * .view-animation.ng-enter.ng-enter-active {
3845 * transform:translateX(0%);
3847 * .view-animation.ng-leave.ng-leave-active {
3848 * transform:translateX(-100%);
3852 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
3853 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
3854 * from its origin. Once that animation is over then the `in` stage occurs which animates the
3855 * element to its destination. The reason why there are two animations is to give enough time
3856 * for the enter animation on the new element to be ready.
3858 * The example above sets up a transition for both the in and out phases, but we can also target the out or
3859 * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
3862 * .banner.ng-anchor-out {
3863 * transition: 0.5s linear all;
3865 * /* the scale will be applied during the out animation,
3866 * but will be animated away when the in animation runs */
3867 * transform: scale(1.2);
3870 * .banner.ng-anchor-in {
3871 * transition: 1s linear all;
3878 * ### Anchoring Demo
3880 <example module="anchoringExample"
3881 name="anchoringExample"
3882 id="anchoringExample"
3883 deps="angular-animate.js;angular-route.js"
3885 <file name="index.html">
3886 <a href="#/">Home</a>
3888 <div class="view-container">
3889 <div ng-view class="view"></div>
3892 <file name="script.js">
3893 angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])
3894 .config(['$routeProvider', function($routeProvider) {
3895 $routeProvider.when('/', {
3896 templateUrl: 'home.html',
3897 controller: 'HomeController as home'
3899 $routeProvider.when('/profile/:id', {
3900 templateUrl: 'profile.html',
3901 controller: 'ProfileController as profile'
3904 .run(['$rootScope', function($rootScope) {
3905 $rootScope.records = [
3906 { id:1, title: "Miss Beulah Roob" },
3907 { id:2, title: "Trent Morissette" },
3908 { id:3, title: "Miss Ava Pouros" },
3909 { id:4, title: "Rod Pouros" },
3910 { id:5, title: "Abdul Rice" },
3911 { id:6, title: "Laurie Rutherford Sr." },
3912 { id:7, title: "Nakia McLaughlin" },
3913 { id:8, title: "Jordon Blanda DVM" },
3914 { id:9, title: "Rhoda Hand" },
3915 { id:10, title: "Alexandrea Sauer" }
3918 .controller('HomeController', [function() {
3921 .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {
3922 var index = parseInt($routeParams.id, 10);
3923 var record = $rootScope.records[index - 1];
3925 this.title = record.title;
3926 this.id = record.id;
3929 <file name="home.html">
3930 <h2>Welcome to the home page</h1>
3931 <p>Please click on an element</p>
3933 ng-href="#/profile/{{ record.id }}"
3934 ng-animate-ref="{{ record.id }}"
3935 ng-repeat="record in records">
3939 <file name="profile.html">
3940 <div class="profile record" ng-animate-ref="{{ profile.id }}">
3944 <file name="animations.css">
3957 .view-container > .view.ng-animate {
3964 .view.ng-enter, .view.ng-leave,
3966 transition:0.5s linear all;
3969 transform:translateX(100%);
3971 .view.ng-enter.ng-enter-active, .view.ng-leave {
3972 transform:translateX(0%);
3974 .view.ng-leave.ng-leave-active {
3975 transform:translateX(-100%);
3977 .record.ng-anchor-out {
3983 * ### How is the element transported?
3985 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
3986 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
3987 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
3988 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
3989 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
3990 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
3991 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
3992 * will become visible since the shim class will be removed.
3994 * ### How is the morphing handled?
3996 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
3997 * what CSS classes differ between the starting element and the destination element. These different CSS classes
3998 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
3999 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
4000 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
4001 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
4002 * the cloned element is placed inside of root element which is likely close to the body element).
4004 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.
4007 * ## Using $animate in your directive code
4009 * 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?
4010 * 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
4011 * imagine we have a greeting box that shows and hides itself when the data changes
4014 * <greeting-box active="onOrOff">Hi there</greeting-box>
4018 * ngModule.directive('greetingBox', ['$animate', function($animate) {
4019 * return function(scope, element, attrs) {
4020 * attrs.$observe('active', function(value) {
4021 * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
4027 * 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
4028 * in our HTML code then we can trigger a CSS or JS animation to happen.
4031 * /* normally we would create a CSS class to reference on the element */
4032 * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
4035 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
4036 * possible be sure to visit the {@link ng.$animate $animate service API page}.
4039 * ### Preventing Collisions With Third Party Libraries
4041 * Some third-party frameworks place animation duration defaults across many element or className
4042 * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
4043 * is expecting actual animations on these elements and has to wait for their completion.
4045 * You can prevent this unwanted behavior by using a prefix on all your animation classes:
4048 * /* prefixed with animate- */
4049 * .animate-fade-add.animate-fade-add-active {
4050 * transition:1s linear all;
4055 * You then configure `$animate` to enforce this prefix:
4058 * $animateProvider.classNameFilter(/animate-/);
4061 * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
4062 * will be evaluated for animation when any DOM changes occur in the application.
4064 * ## Callbacks and Promises
4066 * 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
4067 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
4068 * ended by chaining onto the returned promise that animation method returns.
4071 * // somewhere within the depths of the directive
4072 * $animate.enter(element, parent).then(function() {
4073 * //the animation has completed
4077 * (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
4080 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
4081 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
4082 * routing controller to hook into that:
4085 * ngModule.controller('HomePageController', ['$animate', function($animate) {
4086 * $animate.on('enter', ngViewElement, function(element) {
4087 * // the animation for this route has completed
4092 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
4101 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
4103 * Click here {@link ng.$animate to learn more about animations with `$animate`}.
4105 angular.module('ngAnimate', [])
4106 .directive('ngAnimateSwap', ngAnimateSwapDirective)
4108 .directive('ngAnimateChildren', $$AnimateChildrenDirective)
4109 .factory('$$rAFScheduler', $$rAFSchedulerFactory)
4111 .provider('$$animateQueue', $$AnimateQueueProvider)
4112 .provider('$$animation', $$AnimationProvider)
4114 .provider('$animateCss', $AnimateCssProvider)
4115 .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
4117 .provider('$$animateJs', $$AnimateJsProvider)
4118 .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
4121 })(window, window.angular);