586b6e8b73
- Synced "animation aware" directives tables in API docs and "Animation" guide. - Sorted tables alphabetically. - Added info about `ngAnimateSwap` directive. References #16561 Closes #16581
496 lines
21 KiB
Plaintext
496 lines
21 KiB
Plaintext
@ngdoc overview
|
|
@name Animations
|
|
@sortOrder 310
|
|
@description
|
|
|
|
|
|
# Animations
|
|
|
|
AngularJS provides animation hooks for common directives such as
|
|
{@link ng.directive:ngRepeat ngRepeat}, {@link ng.directive:ngSwitch ngSwitch}, and
|
|
{@link ngRoute.directive:ngView ngView}, as well as custom directives via the `$animate` service.
|
|
These animation hooks are set in place to trigger animations during the life cycle of various
|
|
directives and when triggered, will attempt to perform a CSS Transition, CSS Keyframe Animation or a
|
|
JavaScript callback Animation (depending on whether an animation is placed on the given directive).
|
|
Animations can be placed using vanilla CSS by following the naming conventions set in place by
|
|
AngularJS or with JavaScript code, defined as a factory.
|
|
|
|
<div class="alert alert-info">
|
|
Note that we have used non-prefixed CSS transition properties in our examples as the major
|
|
browsers now support non-prefixed properties. If you intend to support older browsers or certain
|
|
mobile browsers then you will need to include prefixed versions of the transition properties. Take
|
|
a look at http://caniuse.com/#feat=css-transitions for what browsers require prefixes, and
|
|
https://github.com/postcss/autoprefixer for a tool that can automatically generate the prefixes
|
|
for you.
|
|
</div>
|
|
|
|
Animations are not available unless you include the {@link ngAnimate `ngAnimate` module} as a
|
|
dependency of your application.
|
|
|
|
Below is a quick example of animations being enabled for `ngShow` and `ngHide`:
|
|
|
|
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="animate-ng-show">
|
|
<file name="index.html">
|
|
<div ng-init="checked = true">
|
|
<label>
|
|
<input type="checkbox" ng-model="checked" />
|
|
Is visible
|
|
</label>
|
|
<div class="content-area sample-show-hide" ng-show="checked">
|
|
Content...
|
|
</div>
|
|
</div>
|
|
</file>
|
|
<file name="animations.css">
|
|
.content-area {
|
|
border: 1px solid black;
|
|
margin-top: 10px;
|
|
padding: 10px;
|
|
}
|
|
|
|
.sample-show-hide {
|
|
transition: all linear 0.5s;
|
|
}
|
|
.sample-show-hide.ng-hide {
|
|
opacity: 0;
|
|
}
|
|
</file>
|
|
</example>
|
|
|
|
## Installation
|
|
|
|
See the {@link ngAnimate API docs for `ngAnimate`} for instructions on installing the module.
|
|
|
|
You may also want to setup a separate CSS file for defining CSS-based animations.
|
|
|
|
## How they work
|
|
|
|
Animations in AngularJS are completely based on CSS classes. As long as you have a CSS class
|
|
attached to an HTML element within your application, you can apply animations to it. Let's say for
|
|
example that we have an HTML template with a repeater like so:
|
|
|
|
```html
|
|
<div ng-repeat="item in items" class="repeated-item">
|
|
{{ item.id }}
|
|
</div>
|
|
```
|
|
|
|
As you can see, the `repeated-item` class is present on the element that will be repeated and this
|
|
class will be used as a reference within our application's CSS and/or JavaScript animation code to
|
|
tell AngularJS to perform an animation.
|
|
|
|
As `ngRepeat` does its thing, each time a new item is added into the list, `ngRepeat` will add an
|
|
`ng-enter` class to the element that is being added. When removed it will apply an `ng-leave` class
|
|
and when moved around it will apply an `ng-move` class.
|
|
|
|
Taking a look at the following CSS code, we can see some transition and keyframe animation code set
|
|
up for each of those events that occur when `ngRepeat` triggers them:
|
|
|
|
```css
|
|
/*
|
|
We are using CSS transitions for when the enter and move events
|
|
are triggered for the element that has the `repeated-item` class
|
|
*/
|
|
.repeated-item.ng-enter, .repeated-item.ng-move {
|
|
transition: all 0.5s linear;
|
|
opacity: 0;
|
|
}
|
|
|
|
/*
|
|
`.ng-enter-active` and `.ng-move-active` are where the transition destination
|
|
properties are set so that the animation knows what to animate
|
|
*/
|
|
.repeated-item.ng-enter.ng-enter-active,
|
|
.repeated-item.ng-move.ng-move-active {
|
|
opacity: 1;
|
|
}
|
|
|
|
/*
|
|
We are using CSS keyframe animations for when the `leave` event
|
|
is triggered for the element that has the `repeated-item` class
|
|
*/
|
|
.repeated-item.ng-leave {
|
|
animation: 0.5s my_animation;
|
|
}
|
|
|
|
@keyframes my_animation {
|
|
from { opacity: 1; }
|
|
to { opacity: 0; }
|
|
}
|
|
```
|
|
|
|
The same approach to animation can be used using JavaScript code
|
|
(**for simplicity, we rely on jQuery to perform animations here**):
|
|
|
|
```js
|
|
myModule.animation('.repeated-item', function() {
|
|
return {
|
|
enter: function(element, done) {
|
|
// Initialize the element's opacity
|
|
element.css('opacity', 0);
|
|
|
|
// Animate the element's opacity
|
|
// (`element.animate()` is provided by jQuery)
|
|
element.animate({opacity: 1}, done);
|
|
|
|
// Optional `onDone`/`onCancel` callback function
|
|
// to handle any post-animation cleanup operations
|
|
return function(isCancelled) {
|
|
if (isCancelled) {
|
|
// Abort the animation if cancelled
|
|
// (`element.stop()` is provided by jQuery)
|
|
element.stop();
|
|
}
|
|
};
|
|
},
|
|
leave: function(element, done) {
|
|
// Initialize the element's opacity
|
|
element.css('opacity', 1);
|
|
|
|
// Animate the element's opacity
|
|
// (`element.animate()` is provided by jQuery)
|
|
element.animate({opacity: 0}, done);
|
|
|
|
// Optional `onDone`/`onCancel` callback function
|
|
// to handle any post-animation cleanup operations
|
|
return function(isCancelled) {
|
|
if (isCancelled) {
|
|
// Abort the animation if cancelled
|
|
// (`element.stop()` is provided by jQuery)
|
|
element.stop();
|
|
}
|
|
};
|
|
},
|
|
|
|
// We can also capture the following animation events:
|
|
move: function(element, done) {},
|
|
addClass: function(element, className, done) {},
|
|
removeClass: function(element, className, done) {}
|
|
}
|
|
});
|
|
```
|
|
|
|
With these generated CSS class names present on the element at the time, AngularJS automatically
|
|
figures out whether to perform a CSS and/or JavaScript animation. Note that you can't have both CSS
|
|
and JavaScript animations based on the same CSS class. See
|
|
{@link ngAnimate#css-js-animations-together here} for more details.
|
|
|
|
## Class and `ngClass` animation hooks
|
|
|
|
AngularJS also pays attention to CSS class changes on elements by triggering the **add** and
|
|
**remove** hooks. This means that if a CSS class is added to or removed from an element then an
|
|
animation can be executed in between, before the CSS class addition or removal is finalized.
|
|
(Keep in mind that AngularJS will only be able to capture class changes if an
|
|
**interpolated expression** or the **ng-class** directive is used on the element.)
|
|
|
|
The example below shows how to perform animations during class changes:
|
|
|
|
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="animate-css-class">
|
|
<file name="index.html">
|
|
<p>
|
|
<button ng-click="myCssVar='css-class'">Set</button>
|
|
<button ng-click="myCssVar=''">Clear</button>
|
|
<br>
|
|
<span ng-class="myCssVar">CSS-Animated Text</span>
|
|
</p>
|
|
</file>
|
|
<file name="style.css">
|
|
.css-class-add, .css-class-remove {
|
|
transition: all 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940);
|
|
}
|
|
|
|
.css-class,
|
|
.css-class-add.css-class-add-active {
|
|
color: red;
|
|
font-size: 3em;
|
|
}
|
|
|
|
.css-class-remove.css-class-remove-active {
|
|
font-size: 1em;
|
|
color: black;
|
|
}
|
|
</file>
|
|
</example>
|
|
|
|
Although the CSS is a little different than what we saw before, the idea is the same.
|
|
|
|
## Which directives support animations?
|
|
|
|
A handful of common AngularJS directives support and trigger animation hooks whenever any major
|
|
event occurs during their life cycle. The table below explains in detail which animation events are
|
|
triggered:
|
|
|
|
| Directive | Supported Animations |
|
|
|-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
|
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
|
| {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
|
| {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
|
| {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
|
| {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
|
| {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
|
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
|
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
|
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
|
| {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
|
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
|
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
|
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
|
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
|
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
|
|
|
(More information can be found by visiting the documentation associated with each directive.)
|
|
|
|
For a full breakdown of the steps involved during each animation event, refer to the
|
|
{@link ng.$animate `$animate` API docs}.
|
|
|
|
## How do I use animations in my own directives?
|
|
|
|
Animations within custom directives can also be established by injecting `$animate` directly into
|
|
your directive and making calls to it when needed.
|
|
|
|
```js
|
|
myModule.directive('my-directive', ['$animate', function($animate) {
|
|
return function(scope, element) {
|
|
element.on('click', function() {
|
|
if (element.hasClass('clicked')) {
|
|
$animate.removeClass(element, 'clicked');
|
|
} else {
|
|
$animate.addClass(element, 'clicked');
|
|
}
|
|
});
|
|
};
|
|
}]);
|
|
```
|
|
|
|
## Animations on app bootstrap / page load
|
|
|
|
By default, animations are disabled when the AngularJS app {@link guide/bootstrap bootstraps}. If you
|
|
are using the {@link ngApp} directive, this happens in the `DOMContentLoaded` event, so immediately
|
|
after the page has been loaded. Animations are disabled, so that UI and content are instantly
|
|
visible. Otherwise, with many animations on the page, the loading process may become too visually
|
|
overwhelming, and the performance may suffer.
|
|
|
|
Internally, `ngAnimate` waits until all template downloads that are started right after bootstrap
|
|
have finished. Then, it waits for the currently running {@link ng.$rootScope.Scope#$digest $digest}
|
|
and one more after that, to finish. This ensures that the whole app has been compiled fully before
|
|
animations are attempted.
|
|
|
|
If you do want your animations to play when the app bootstraps, you can enable animations globally
|
|
in your main module's {@link angular.Module#run run} function:
|
|
|
|
```js
|
|
myModule.run(function($animate) {
|
|
$animate.enabled(true);
|
|
});
|
|
```
|
|
|
|
## How to (selectively) enable, disable and skip animations
|
|
|
|
There are several different ways to disable animations, both globally and for specific animations.
|
|
Disabling specific animations can help to speed up the render performance, for example for large
|
|
`ngRepeat` lists that don't actually have animations. Because `ngAnimate` checks at runtime if
|
|
animations are present, performance will take a hit even if an element has no animation.
|
|
|
|
### During the config: {@link $animateProvider#customFilter $animateProvider.customFilter()}
|
|
|
|
This function can be called during the {@link angular.Module#config config} phase of an app. It
|
|
takes a filter function as the only argument, which will then be used to "filter" animations (based
|
|
on the animated element, the event type, and the animation options). Only when the filter function
|
|
returns `true`, will the animation be performed. This allows great flexibility - you can easily
|
|
create complex rules, such as allowing specific events only or enabling animations on specific
|
|
subtrees of the DOM, and dynamically modify them, for example disabling animations at certain points
|
|
in time or under certain circumstances.
|
|
|
|
```js
|
|
app.config(function($animateProvider) {
|
|
$animateProvider.customFilter(function(node, event, options) {
|
|
// Example: Only animate `enter` and `leave` operations.
|
|
return event === 'enter' || event === 'leave';
|
|
});
|
|
});
|
|
```
|
|
|
|
The `customFilter` approach generally gives a big speed boost compared to other strategies, because
|
|
the matching is done before other animation disabling strategies are checked.
|
|
|
|
<div class="alert alert-success">
|
|
**Best Practice:**
|
|
Keep the filtering function as lean as possible, because it will be called for each DOM
|
|
action (e.g. insertion, removal, class change) performed by "animation-aware" directives.
|
|
See {@link guide/animations#which-directives-support-animations- here} for a list of built-in
|
|
directives that support animations.
|
|
Performing computationally expensive or time-consuming operations on each call of the
|
|
filtering function can make your animations sluggish.
|
|
</div>
|
|
|
|
### During the config: {@link $animateProvider#classNameFilter $animateProvider.classNameFilter()}
|
|
|
|
This function too can be called during the {@link angular.Module#config config} phase of an app. It
|
|
takes a regex as the only argument, which will then be matched against the classes of any element
|
|
that is about to be animated. The regex allows a lot of flexibility - you can either allow
|
|
animations for specific classes only (useful when you are working with 3rd party animations), or
|
|
exclude specific classes from getting animated.
|
|
|
|
```js
|
|
app.config(function($animateProvider) {
|
|
$animateProvider.classNameFilter(/animate-/);
|
|
});
|
|
```
|
|
|
|
```css
|
|
/* prefixed with `animate-` */
|
|
.animate-fade-add.animate-fade-add-active {
|
|
transition: all 1s linear;
|
|
opacity: 0;
|
|
}
|
|
```
|
|
|
|
The `classNameFilter` approach generally gives a big speed boost compared to other strategies,
|
|
because the matching is done before other animation disabling strategies are checked. However, that
|
|
also means it is not possible to override class name matching with the two following strategies.
|
|
It's of course still possible to enable / disable animations by changing an element's class name at
|
|
runtime.
|
|
|
|
### At runtime: {@link ng.$animate#enabled $animate.enabled()}
|
|
|
|
This function can be used to enable / disable animations in two different ways:
|
|
|
|
With a single `boolean` argument, it enables / disables animations globally:
|
|
`$animate.enabled(false)` disables all animations in your app.
|
|
|
|
When the first argument is a native DOM or jqLite/jQuery element, the function enables / disables
|
|
animations on this element *and all its children*: `$animate.enabled(myElement, false)`. You can
|
|
still use it to re-enable animations for a child element, even if you have disabled them on a parent
|
|
element. And compared to the `classNameFilter`, you can change the animation status at runtime
|
|
instead of during the config phase.
|
|
|
|
Note however that the `$animate.enabled()` state for individual elements does not overwrite
|
|
disabling rules that have been set in the {@link $animateProvider#classNameFilter classNameFilter}.
|
|
|
|
### Via CSS styles: overwriting styles in the `ng-animate` CSS class
|
|
|
|
Whenever an animation is started, `ngAnimate` applies the `ng-animate` class to the element for the
|
|
whole duration of the animation. By applying CSS transition / animation styling to that class, you
|
|
can skip an animation:
|
|
|
|
```css
|
|
.my-class {
|
|
transition: transform 2s;
|
|
}
|
|
|
|
.my-class:hover {
|
|
transform: translateX(50px);
|
|
}
|
|
|
|
my-class.ng-animate {
|
|
transition: 0s;
|
|
}
|
|
```
|
|
|
|
By setting `transition: 0s`, `ngAnimate` will ignore the existing transition styles, and not try to
|
|
animate them (Javascript animations will still execute, though). This can be used to prevent
|
|
{@link guide/animations#preventing-collisions-with-existing-animations-and-third-party-libraries
|
|
issues with existing animations interfering with `ngAnimate`}.
|
|
|
|
|
|
## Preventing flicker before an animation starts
|
|
|
|
When nesting elements with structural animations, such as `ngIf`, into elements that have
|
|
class-based animations such as `ngClass`, it sometimes happens that before the actual animation
|
|
starts, there is a brief flicker or flash of content where the animated element is briefly visible.
|
|
|
|
To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as
|
|
an animation is initialized, but removed before the actual animation starts (after waiting for a
|
|
`$digest`). This class is only added for *structural* animations (`enter`, `move`, and `leave`).
|
|
|
|
Here's an example where you might see flickering:
|
|
|
|
```html
|
|
<div ng-class="{red: myProp}">
|
|
<div ng-class="{blue: myProp}">
|
|
<div class="message" ng-if="myProp"></div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
It is possible that during the `enter` event, the `.message` div will be briefly visible before it
|
|
starts animating. In that case, you can add styles to the CSS that make sure the element stays
|
|
hidden before the animation starts:
|
|
|
|
```css
|
|
.message.ng-enter-prepare {
|
|
opacity: 0;
|
|
}
|
|
|
|
/* Other animation styles ... */
|
|
```
|
|
|
|
## Preventing collisions with existing animations and third-party libraries
|
|
|
|
By default, any `ngAnimate`-enabled directives will assume that `transition` / `animation` styles on
|
|
the element are part of an `ngAnimate` animation. This can lead to problems when the styles are
|
|
actually for animations that are independent of `ngAnimate`.
|
|
|
|
For example, an element acts as a loading spinner. It has an infinite css animation on it, and also
|
|
an {@link ngIf `ngIf`} directive, for which no animations are defined:
|
|
|
|
```css
|
|
.spinner {
|
|
animation: rotating 2s linear infinite;
|
|
}
|
|
|
|
@keyframes rotating {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
```
|
|
|
|
Now, when the `ngIf` expression changes, `ngAnimate` will see the spinner animation and use it to
|
|
animate the `enter`/`leave` event, which doesn't work because the animation is infinite. The element
|
|
will still be added / removed after a timeout, but there will be a noticeable delay.
|
|
|
|
This might also happen because some third-party frameworks place animation duration defaults across
|
|
many element or className selectors in order to make their code small and reusable.
|
|
|
|
You can prevent this unwanted behavior by adding CSS to the `.ng-animate` class, that is added for
|
|
the whole duration of each animation. Simply overwrite the transition / animation duration. In the
|
|
case of the spinner, this would be:
|
|
|
|
```css
|
|
.spinner.ng-animate {
|
|
animation: 0s none;
|
|
transition: 0s none;
|
|
}
|
|
```
|
|
|
|
If you do have CSS transitions / animations defined for the animation events, make sure they have a
|
|
higher priority than any styles that are not related to `ngAnimate`.
|
|
|
|
You can also use one of the other
|
|
{@link guide/animations#how-to-selectively-enable-disable-and-skip-animations
|
|
strategies to disable animations}.
|
|
|
|
|
|
## Enable animations outside of the application DOM tree: {@link ng.$animate#pin $animate.pin()}
|
|
|
|
Before animating, `ngAnimate` checks if the animated element is inside the application DOM tree. If
|
|
not, no animation is run. Usually, this is not a problem since most apps use the `html` or `body`
|
|
elements as their root.
|
|
|
|
Problems arise when the application is bootstrapped on a different element, and animations are
|
|
attempted on elements that are outside the application tree, e.g. when libraries append popup or
|
|
modal elements to the body tag.
|
|
|
|
You can use {@link ng.$animate#pin `$animate.pin(element, parentHost)`} to associate an element with
|
|
another element that belongs to your application. Simply call it before the element is added to the
|
|
DOM / before the animation starts, with the element you want to animate, and the element which
|
|
should be its assumed parent.
|
|
|
|
|
|
## More about animations
|
|
|
|
For a full breakdown of each method available on `$animate`, see the
|
|
{@link ng.$animate API documentation}.
|
|
|
|
To see a complete demo, see the {@link tutorial/step_14 animation step in the phonecat tutorial}.
|