aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatias Niemelä2014-01-02 17:27:13 -0500
committerMatias Niemelä2014-01-14 13:19:09 -0500
commit7d5d62dafe11620082c79da35958f8014eeb008c (patch)
treeecfebd64a58308c1749d040dacc738ee05544ed4
parent524650a40ed20f01571e5466475749874ee67288 (diff)
downloadangular.js-7d5d62dafe11620082c79da35958f8014eeb008c.tar.bz2
fix($animate): correctly detect and handle CSS transition changes during 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).
-rw-r--r--src/ngAnimate/animate.js41
-rw-r--r--test/ngAnimate/animateSpec.js69
2 files changed, 102 insertions, 8 deletions
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 62f6381d..26fe982d 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -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 = {};
@@ -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
@@ -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);
};
@@ -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);
@@ -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);
diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js
index db40d544..99527cc4 100644
--- a/test/ngAnimate/animateSpec.js
+++ b/test/ngAnimate/animateSpec.js
@@ -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();
@@ -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);
+ }));
});
});