Is There a Callback on Completion of a Css3 Animation

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/

Callback on CSS animation end

$('#my_object').animate_scale().fadeOut(2000);

if you want .fadeOut() to wait for animate_scale() to finish, animate_scale needs to be queued:

Queue your plugin:

Usually, when you chain fx methods like i.e:

$("#ball").animate({left:200}).fadeOut();

you'll see the ball animate, and only once that animation is finished --- it'll fade out.

Why? Cause jQuery will stach animate and than fadeOut into a queue array and wait each to resolve before triggering the next Method.

To replicate the same behavior within your plugin:

jsFiddle demo (Queue in action!)

$.fn.animate_scale = function( callback ) {
var $this = this;
return $this.queue(function() {
$this.addClass('animate_scale').on("animationend", function() {
$this.dequeue();
if (typeof callback == 'function') callback.call( $this );
});
});
};


$('#my_object').animate_scale(function() {
console.log( "Scale is done!" );
}).fadeOut( 2000 ); // fadeOut will wait for animate_scale to dequeue (complete)

I don't need queue stacking

If you want your plugin to unobstructively (simultaneously) process other chained fx Methods,

use just the callback:

jsFiddle demo (no Queue)

$.fn.animate_scale = function( callback ) {
var $this = $(this);
return $this.addClass('animate_scale').on("animationend", function() {
if (typeof callback == 'function') callback.call( this );
});
};

$('#my_object').animate_scale(function(){
console.log("Scale done.");
// use $(this).fadeOut(2000); here!! cause otherwise...
}).fadeOut(2000); // ...if chained here, will fade immediately!!!!!

Callback on CSS transition

I know that Safari implements a webkitTransitionEnd callback that you can attach directly to the element with the transition.

Their example (reformatted to multiple lines):

box.addEventListener( 
'webkitTransitionEnd',
function( event ) {
alert( "Finished transition!" );
}, false );

Adding fallback for functionality related to CSS3 animation completion

Will animation end event be fired if browser doesn't support CSS animations?

Generally if the browser does not support CSS3 animations then the animationEnd event would also not be fired and so the callback function which gets executed upon completion of animation will not be called at all.

What is the solution to execute the callback function?

One way to make sure that the callback function gets executed even on browsers that do not support CSS3 animations would be to test for animation support and (a) execute the callback function after the completion of animation if it is supported or (b) execute callback function immediately if animation isn't supported.

In the below snippet, I have used the code provided by MDN to test for animation support. In browsers that support animation, the below snippet would produce the shake effect and at the end of it change background color to red whereas in browsers that don't support animations, it will change background color to red immediately.

This has been tested in IE9 (doesn't support CSS3 animation), IE10, IE11, Edge, Mozilla and Chrome. In Opera, the code works when tested separately in a Fiddle or CodePen but the color doesn't change after animation in the snippet here. I assume that this must be something to do with Stack Snippet and not the code itself.

/* Code from MDN */
var animation = false, animationstring = 'animation', keyframeprefix = '', domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), pfx = '', elm = document.createElement('div');
if (elm.style.animationName !== undefined) { animation = true;}
if (animation === false) { for (var i = 0; i < domPrefixes.length; i++) { if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) { pfx = domPrefixes[i]; animationstring = pfx + 'Animation'; keyframeprefix = '-' + pfx.toLowerCase() + '-'; animation = true; break; } }}
/* End of Code from MDN */
if (animation) { // check if animation is supported var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend'; $('.animated-element').one(animationEnd, function() { // if supported attach animationEnd handler callback(); })} else { // if animation is not supported execute callback immediately callback();}
function callback() { console.log('Hello World!'); $('.animated-element').css('background-color', 'red');}
.animated-element {  display: inline-block;  width: 200px;  height: 200px;  border: 1px solid;  animation: shake 1s 2s linear forwards;}@keyframes shake {  0% {    transform: translateX(10px);  }  25% {    transform: translateX(-10px);  }  50% {    transform: translateX(10px);  }  75% {    transform: translateX(-10px);  }  100% {    transform: translateX(0px);  }}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><div class='animated-element'>Hello!</div>

Bind CSS3 Animation Callback using Knockout.js

From my experience there is no 'knockout way' to handle those sorts of events.

Knockout's author suggests using the KO event binding for simple bindings. But for more complex and/or unobtrusive event binding scenarios suggests using jQuery: http://knockoutjs.com/documentation/unobtrusive-event-handling.html

I'm working on a project now using Knockout and I followed his suggestion. Using some KO event bindings (for form submits) and some jQuery event bindings (for a window resize event in my case).

Of course, you don't need to use jQuery, but going outside the KO library in your case is probably the correct route. Have fun!

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>'


Related Topics



Leave a reply



Submit