AngularJS css animation + done callback
Yes you can, using the $animate
service, which would usually be done in a custom directive. A simple case of animation would be to animate an element in some way on click. Say, for example to remove an element on click, with an animation specified using .ng-leave, passing a callback
app.directive('leaveOnClick', function($animate) {
return {
scope: {
'leaveOnClick': '&'
},
link: function (scope, element) {
scope.leaveOnClick = scope.leaveOnClick || (function() {});
element.on('click', function() {
scope.$apply(function() {
$animate.leave(element, scope.leaveOnClick);
});
});
}
};
});
which could be used like:
<div class="my-div" leaveOnClick="done()">Click to remove</div>
With CSS to fade the element out:
.my-div.ng-leave {
opacity: 1;
transition: opacity 1s;
-webkit-transition: opacity 1s;
}
.my-div.ng-leave.ng-leave-active {
opacity: 0;
}
You can see the above animation in action at this Plunker.
However, ngIf doesn't have any hooks to pass a callback in that I know of, so you'll have to write your own directive. What follows is a description of a modified version of ngIf, originally copied from the ngIf source, and renamed to animatedIf
. It can be used by:
<div class="my-div" animated-if="shown" animated-if-leave-callback="leaveDone()" animated-if-enter-callback="enterDone()" >Some content</div>
The way it works is that it uses a manual watcher to react to changes of the expression passed to animated-if
. The key differences to the original ngIf are the addition of a 'scope' parameter to pass the callbacks in:
scope: {
'animatedIf': '=',
'animatedIfEnterCallback': '&',
'animatedIfLeaveCallback': '&'
},
and then modifying the calls to $animate.enter
and $animate.leave
to call these callbacks after the animation:
var callback = !oldValue && $scope.animatedIfEnterCallback ? $scope.animatedIfEnterCallback : (function() {});
$animate.enter(clone, $element.parent(), $element, callback);
$animate.leave(block.clone, ($scope.animatedIfLeaveCallback || (function() {})));
The enter one is a bit more complicated to not call the callback on initial loading of the directive. Because of the scope
parameter, this directive creates an isolated scope, which it then uses when transcluding the contents. So another change that is required is to create and use a scope as a child from the $parent
scope of the directive: the line
childScope = $scope.$new();
must be changed to
childScope = $scope.$parent.$new();
You can see the full source of the modified ngIf directive in this Plunker. This has only been tested extremely briefly.
There may well be a simpler way of doing this, maybe by not recreating the ngIf directive fully, but creating a directive with template that uses the original ngIf with some wrapper divs, such as
template: '<div><div ng-if="localVariable"><div ng-transclude></div></div></div>'
Running code after an AngularJS animation has completed
@michael-charemza answer worked great for me. If you are using Angular 1.3 they changed the promise a little. I got stuck on this for a little bit but here is the change that got it to work:
if (show) {
$animate.removeClass(element, 'ng-hide').then(scope.afterShow);
}
if (!show) {
$animate.addClass(element, 'ng-hide').then(scope.afterHide);
}
Plunker: Code Example
AngularJS animate with callback, wait for the next animation to start
First off, it is important to remember to include which version of AngularJS you are using. However, I will provide both 1.2 and 1.3+ solutions. If you are using a version less than 1.2, you should really consider upgrading.
The $animate Service
The $animate service provides an addClass method and a removeClass method that will take care of what you need easily. The behavior changes based upon what version of AngularJS you are using.
Either way, you start by including the animate module (it's separate from the angular.js main file) and then inject the $animate
service where you need it.
app.directive("myShow", [
'$animate',
function($animate) {
return {
// your directive config
};
}
]);
AngularJS v1.2.x
The $animate (v1.2.26) service's addClass and removeClass methods have three arguments: element, className, and doneCallback. Here, simply, you can use the doneCallback function argument to tell when an animation is done. For example:
$animate.addClass(element, 'draw', function() {
// if we are in here, the animation is complete
});
AngularJS v1.3.x
With AngularJS 1.3, each of the animation methods, on the $animate service, return a promise when called. The promise itself is then resolved once the animation has completed itself, has been cancelled or has been skipped due to animations being disabled. (Note that even if the animation is cancelled it will still call the resolve function of the animation.) See Documentation
With 1.3 and above you need to utilize the promise returned by calling addClass
or removeClass
through the $animate
service. You can do so by chaining the then
function at the end. For example:
$animate.addClass(element, 'draw').then(function() {
// if we are in here, the animation is complete
});
If you are using the AngularJS 1.3 RC (latest unstable as of this post) and you aren't familiar with promises then reference the $q service.
Is there a callback on completion of a CSS3 animation?
Yes, there is. The callback is an event, so you must add an event listener to catch it. This is an example with jQuery:
$("#sun").bind('oanimationend animationend webkitAnimationEnd', function() {
alert("fin")
});
Or pure js:
element.addEventListener("webkitAnimationEnd", callfunction,false);
element.addEventListener("animationend", callfunction,false);
element.addEventListener("oanimationend", callfunction,false);
Live demo:
http://jsfiddle.net/W3y7h/
Animation callbacks runs immediately even without the state being changed
The initial state of animation is a "stateless" state called void
- which is the default state when using animations.
After initializing your state
variable to steady
a non-existing animation is starting from void
-> steady
.
In order to reach your goal, you should use properties fromState
& toState
of AnimationEvent.
import { AnimationEvent, } from '@angular/animations';
public animStart(event: AnimationEvent): void {
if(event.fromState === 'steady') {
// ...
}
}
Try to log AnimationEvent
in your animStart
method in order to better understand this interface
.
public animStart(event: AnimationEvent): void {
console.log(event);
}
Getting done event on CSS Transition with AngularJS 1.2 and angular-animate
I stumbled upon this solution - basically it works:
How do I use transitionend in jQuery?
If someone wants to add a more angular-ish solution it would be great!
Angular ng-view animation callback
replace
.ng-enter, .ng-leave {
-webkit-transition: all 1s ease;
-moz-transition: all 1s ease;
-o-transition: all 1s ease;
transition: all 1s ease;
}
.ng-enter {
opacity: 0;
}
with the following code
.ng-enter {
-webkit-transition: all 1s ease;
-moz-transition: all 1s ease;
-o-transition: all 1s ease;
transition: all 1s ease;
opacity: 0;
}
you don't need to make animation on both events(enter/leave) you can make just on enter and it wont make the simultaneous animation it will hide the first view immediately and then animate the second .
http://plnkr.co/edit/iVSRrg9nBaDTtHUZcDDu
if you need one animation after another you need a callback. To avoid repetition you can find the answer here:
AngularJS css animation + done callback
How do you get an animation callback in AngularJS?
It's unfortunately not as easy as you'd hope. There's no way to pass an animation end callback to ng-show
, so instead you have to implement your own show/hide directive and make use of the $animate
service.
I forked your plunker to demonstrate.
This is the directive I made:
app.directive("myShow", function($animate){
return {
scope:{
// use & so that this will evaluate expressions.
toggle: "&myShow"
},
link: function( scope, element, attrs ){
// watch the toggle function
scope.$watch(scope.toggle, function(newVal, oldVal){
if(newVal && oldVal != undefined){
$animate.removeClass(element, 'ng-hide', function(){
console.log("finished");
})
}
else {
$animate.addClass(element,'ng-hide', function(){
console.log("finished!");
})
}
});
}
}
});
You can see that you call $animate
's addClass
method with ng-hide
and you can use the exact same CSS. Using the $animate
service directly means you can make use of the doneCallback
.
PS this is just to demonstrate $animate
, I haven't checked whether this initialises properly - you might need to make sure ng-hide
is correctly on/off when the page loads.
Related Topics
Detect Urls in Text with JavaScript
Prevent Browser Caching of Ajax Call Result
How to Delete an Item from State Array
Catch Browser's "Zoom" Event in JavaScript
Convert a String to a Template String
Firing a Keyboard Event in Safari, Using JavaScript
Sign PDF with Plain JavaScript
Correct JavaScript Inheritance
Why Does ++[[]][+[]]+[+[]] Return the String "10"
How to Add Hours to a Date Object
Sort Array Elements (String with Numbers), Natural Sort
Run Greasemonkey Script on the Same Page, Multiple Times
How to Define Global Variables in Coffeescript
If a Dom Element Is Removed, Are Its Listeners Also Removed from Memory
In JavaScript, How to Conditionally Add a Member to an Object