The $animateCss
service is a useful utility to trigger customized CSS-based transitions/keyframes
from a JavaScript-based animation or directly from a directive. The purpose of $animateCss
is NOT
to side-step how $animate
and ngAnimate work, but the goal is to allow pre-existing animations or
directives to create more complex animations that can be purely driven using CSS code.
Note that only browsers that support CSS transitions and/or keyframe animations are capable of
rendering animations triggered via $animateCss
(bad news for IE9 and lower).
Once again, $animateCss
is designed to be used inside of a registered JavaScript animation that
is powered by ngAnimate. It is possible to use $animateCss
directly inside of a directive, however,
any automatic control over cancelling animations and/or preventing animations from being run on
child elements will not be handled by Angular. For this to work as expected, please use $animate
to
trigger the animation and then setup a JavaScript animation that injects $animateCss
to trigger
the CSS animation.
The example below shows how we can create a folding animation on an element using ng-if
:
<!-- notice the `fold-animation` CSS class -->
<div ng-if="onOff" class="fold-animation">
This element will go BOOM
</div>
<button ng-click="onOff=true">Fold In</button>
Now we create the JavaScript animation that will trigger the CSS transition:
ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
return {
enter: function(element, doneFn) {
var height = element[0].offsetHeight;
return $animateCss(element, {
from: { height:'0px' },
to: { height:height + 'px' },
duration: 1 // one second
});
}
}
}]);
$animateCss
is the underlying code that ngAnimate uses to power CSS-based animations behind the scenes. Therefore CSS hooks
like .ng-EVENT
, .ng-EVENT-active
, .ng-EVENT-stagger
are all features that can be triggered using $animateCss
via JavaScript code.
This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
$animateCss
. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
to provide a working animation that will run in CSS.
The example below showcases a more advanced version of the .fold-animation
from the example above:
ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
return {
enter: function(element, doneFn) {
var height = element[0].offsetHeight;
return $animateCss(element, {
addClass: 'red large-text pulse-twice',
easing: 'ease-out',
from: { height:'0px' },
to: { height:height + 'px' },
duration: 1 // one second
});
}
}
}]);
Since we're adding/removing CSS classes then the CSS transition will also pick those up:
/* since a hardcoded duration value of 1 was provided in the JavaScript animation code,
the CSS classes below will be transitioned despite them being defined as regular CSS classes */
.red { background:red; }
.large-text { font-size:20px; }
/* we can also use a keyframe animation and $animateCss will make it work alongside the transition */
.pulse-twice {
animation: 0.5s pulse linear 2;
-webkit-animation: 0.5s pulse linear 2;
}
@keyframes pulse {
from { transform: scale(0.5); }
to { transform: scale(1.5); }
}
@-webkit-keyframes pulse {
from { -webkit-transform: scale(0.5); }
to { -webkit-transform: scale(1.5); }
}
Given this complex combination of CSS classes, styles and options, $animateCss
will figure everything out and make the animation happen.
$animateCss
is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
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
styles using the from
and to
properties.
var animator = $animateCss(element, {
from: { background:'red' },
to: { background:'blue' }
});
animator.start();
.rotating-animation {
animation:0.5s rotate linear;
-webkit-animation:0.5s rotate linear;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@-webkit-keyframes rotate {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(360deg); }
}
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
going to be detected from what the keyframe styles on the CSS class are. In this event, $animateCss
will automatically create an inline transition
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
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
and spread across the transition and keyframe animation.
$animateCss
works in two stages: a preparation phase and an animation phase. Therefore when $animateCss
is first called it will NOT actually
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
added and removed on the element). Once $animateCss
is called it will return an object with the following properties:
var animator = $animateCss(element, { ... });
Now what do the contents of our animator
variable look like:
{
// starts the animation
start: Function,
// ends (aborts) the animation
end: Function
}
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.
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
applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
and that changing them will not reconfigure the parameters of the animation.
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
runner called .done(callbackFn)
. The done method works the same as .finally(callbackFn)
, however, it does not trigger a digest to occur.
Therefore, for performance reasons, it's always best to use runner.done(callback)
instead of runner.then()
, runner.catch()
or runner.finally()
unless you really need a digest to kick off afterwards.
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
(so there is no need to call runner.done(doneFn)
inside of your JavaScript animation code).
Check the animation code above to see how this works.
$animateCss(element, options);
Param | Type | Details |
---|---|---|
element | DOMElement |
the element that will be animated |
options | object |
the animation-related options that will be applied during the animation
|
object | an object with start and end methods and details about the animation.
|