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

Commit

Permalink
fix($animate): correctly detect and handle CSS transition changes dur…
Browse files Browse the repository at this point in the history
…ing class addition and removal

When a CSS class containing transition code is added to an element then an animation should kick off.
ngAnimate doesn't do this. It only respects transition styles that are already present on the element
or on the setup class (but not the addClass animation).
  • Loading branch information
matsko committed Jan 14, 2014
1 parent 524650a commit 7d5d62d
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 8 deletions.
41 changes: 35 additions & 6 deletions src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,7 @@ angular.module('ngAnimate', ['ng'])
return parentID + '-' + extractElementNode(element).className;
}

function animateSetup(element, className) {
function animateSetup(element, className, calculationDecorator) {
var cacheKey = getCacheKey(element);
var eventCacheKey = cacheKey + ' ' + className;
var stagger = {};
Expand All @@ -1061,9 +1061,16 @@ angular.module('ngAnimate', ['ng'])
applyClasses && element.removeClass(staggerClassName);
}

/* the animation itself may need to add/remove special CSS classes
* before calculating the anmation styles */
calculationDecorator = calculationDecorator ||
function(fn) { return fn(); };

element.addClass(className);

var timings = getElementAnimationDetails(element, eventCacheKey);
var timings = calculationDecorator(function() {
return getElementAnimationDetails(element, eventCacheKey);
});

/* there is no point in performing a reflow if the animation
timeout is empty (this would cause a flicker bug normally
Expand Down Expand Up @@ -1228,8 +1235,8 @@ angular.module('ngAnimate', ['ng'])
return style;
}

function animateBefore(element, className) {
if(animateSetup(element, className)) {
function animateBefore(element, className, calculationDecorator) {
if(animateSetup(element, className, calculationDecorator)) {
return function(cancelled) {
cancelled && animateClose(element, className);
};
Expand Down Expand Up @@ -1324,7 +1331,18 @@ angular.module('ngAnimate', ['ng'])
},

beforeAddClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'), function(fn) {

/* when a CSS class is added to an element then the transition style that
* is applied is the transition defined on the element when the CSS class
* is added at the time of the animation. This is how CSS3 functions
* outside of ngAnimate. */
element.addClass(className);
var timings = fn();
element.removeClass(className);
return timings;
});

if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
Expand All @@ -1341,7 +1359,18 @@ angular.module('ngAnimate', ['ng'])
},

beforeRemoveClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'), function(fn) {
/* when classes are removed from an element then the transition style
* that is applied is the transition defined on the element without the
* CSS class being there. This is how CSS3 functions outside of ngAnimate.
* http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
var klass = element.attr('class');
element.removeClass(className);
var timings = fn();
element.attr('class', klass);
return timings;
});

if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
Expand Down
69 changes: 67 additions & 2 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2801,14 +2801,14 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'base-class one two');

//still true since we're before the reflow
expect(element.hasClass('base-class')).toBe(true);
expect(element.hasClass('base-class')).toBe(false);

//this will cancel the remove animation
$animate.addClass(element, 'base-class one two');

//the cancellation was a success and the class was added right away
//since there was no successive animation for the after animation
expect(element.hasClass('base-class')).toBe(true);
expect(element.hasClass('base-class')).toBe(false);

//the reflow...
$timeout.flush();
Expand Down Expand Up @@ -3048,5 +3048,70 @@ describe("ngAnimate", function() {
expect(leaveDone).toBe(true);
});
});

it('should respect the most relevant CSS transition property if defined in multiple classes',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {

if (!$sniffer.transitions) return;

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

ss.addRule('.base-class.on', '-webkit-transition:5s linear all;' +
'transition:5s linear all;');

$animate.enabled(true);

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

var ready = false;
$animate.addClass(element, 'on', function() {
ready = true;
});

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

browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 5 });
$timeout.flush(1);
expect(ready).toBe(true);

ready = false;
$animate.removeClass(element, 'on', function() {
ready = true;
});

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

it('should not apply a transition upon removal of a class that has a transition',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {

if (!$sniffer.transitions) return;

ss.addRule('.base-class.on', '-webkit-transition:5s linear all;' +
'transition:5s linear all;');

$animate.enabled(true);

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

var ready = false;
$animate.removeClass(element, 'on', function() {
ready = true;
});

$timeout.flush(1);
expect(ready).toBe(true);
}));
});
});

0 comments on commit 7d5d62d

Please sign in to comment.