Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix($animate): prevent race conditions for class-based animations whe…
Browse files Browse the repository at this point in the history
…n animating on the same CSS class

Closes #5588
  • Loading branch information
matsko committed Jan 14, 2014
1 parent 7d5d62d commit 4aa9df7
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 5 deletions.
19 changes: 15 additions & 4 deletions src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -659,12 +659,23 @@ angular.module('ngAnimate', ['ng'])
cleanup(element);
cancelAnimations(ngAnimateState.animations);

//in the event that the CSS is class is quickly added and removed back
//then we don't want to wait until after the reflow to add/remove the CSS
//class since both class animations may run into a race condition.
//The code below will check to see if that is occurring and will
//immediately remove the former class before the reflow so that the
//animation can snap back to the original animation smoothly
var isFullyClassBasedAnimation = isClassBased && !ngAnimateState.structural;
var isRevertingClassAnimation = isFullyClassBasedAnimation &&
ngAnimateState.className == className &&
animationEvent != ngAnimateState.event;

//if the class is removed during the reflow then it will revert the styles temporarily
//back to the base class CSS styling causing a jump-like effect to occur. This check
//here ensures that the domOperation is only performed after the reflow has commenced
if(ngAnimateState.beforeComplete) {
if(ngAnimateState.beforeComplete || isRevertingClassAnimation) {
(ngAnimateState.done || noop)(true);
} else if(isClassBased && !ngAnimateState.structural) {
} else if(isFullyClassBasedAnimation) {
//class-based animations will compare element className values after cancelling the
//previous animation to see if the element properties already contain the final CSS
//class and if so then the animation will be skipped. Since the domOperation will
Expand Down Expand Up @@ -812,10 +823,10 @@ angular.module('ngAnimate', ['ng'])
function cancelAnimations(animations) {
var isCancelledFlag = true;
forEach(animations, function(animation) {
if(!animations.beforeComplete) {
if(!animation.beforeComplete) {
(animation.beforeEnd || noop)(isCancelledFlag);
}
if(!animations.afterComplete) {
if(!animation.afterComplete) {
(animation.afterEnd || noop)(isCancelledFlag);
}
});
Expand Down
56 changes: 55 additions & 1 deletion test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2673,10 +2673,16 @@ describe("ngAnimate", function() {
beforeAddClass : function(element, className, done) {
currentAnimation = 'addClass';
currentFn = done;
return function(cancelled) {
currentAnimation = cancelled ? null : currentAnimation;
}
},
beforeRemoveClass : function(element, className, done) {
currentAnimation = 'removeClass';
currentFn = done;
return function(cancelled) {
currentAnimation = cancelled ? null : currentAnimation;
}
}
};
});
Expand All @@ -2690,10 +2696,12 @@ describe("ngAnimate", function() {
expect(currentAnimation).toBe('addClass');
currentFn();

currentAnimation = null;

$animate.removeClass(element, 'on');
$animate.addClass(element, 'on');

expect(currentAnimation).toBe('addClass');
expect(currentAnimation).toBe(null);
});
});

Expand Down Expand Up @@ -3113,5 +3121,51 @@ describe("ngAnimate", function() {
$timeout.flush(1);
expect(ready).toBe(true);
}));

it('should avoid skip animations if the same CSS class is added / removed synchronously before the reflow kicks in',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {

if (!$sniffer.transitions) return;

ss.addRule('.water-class', '-webkit-transition:2s linear all;' +
'transition:2s linear all;');

$animate.enabled(true);

var element = $compile('<div class="water-class on"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);

var signature = '';
$animate.removeClass(element, 'on', function() {
signature += 'A';
});
$animate.addClass(element, 'on', function() {
signature += 'B';
});

$timeout.flush(1);
expect(signature).toBe('AB');

signature = '';
$animate.removeClass(element, 'on', function() {
signature += 'A';
});
$animate.addClass(element, 'on', function() {
signature += 'B';
});
$animate.removeClass(element, 'on', function() {
signature += 'C';
});

$timeout.flush(1);
expect(signature).toBe('AB');

$timeout.flush(10);
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2000 });
$timeout.flush(1);

expect(signature).toBe('ABC');
}));
});
});

0 comments on commit 4aa9df7

Please sign in to comment.