aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatias Niemelä2013-10-31 14:09:49 -0700
committerMatias Niemelä2013-11-05 21:45:36 -0500
commitb89584db10b63f346cbfd03f67fb92504e5bf362 (patch)
tree05fc8dfd1ca1aa49223be3c03e0ba762acfdc5f2
parent41a2d5b30f4feb90651eb577cf44852a6d2be72c (diff)
downloadangular.js-b89584db10b63f346cbfd03f67fb92504e5bf362.tar.bz2
fix($animate): avoid hanging animations if the active CSS transition class is missing
Closes #4732 Closes #4490
-rw-r--r--css/angular.css11
-rw-r--r--src/ngAnimate/animate.js51
-rw-r--r--test/ngAnimate/animateSpec.js58
3 files changed, 103 insertions, 17 deletions
diff --git a/css/angular.css b/css/angular.css
index b88e61e4..46fee32d 100644
--- a/css/angular.css
+++ b/css/angular.css
@@ -9,3 +9,14 @@
ng\:form {
display: block;
}
+
+/* The styles below ensure that the CSS transition will ALWAYS
+ * animate and close. A nasty bug occurs with CSS transitions where
+ * when the active class isn't set, or if the active class doesn't
+ * contain any styles to transition to, then, if ngAnimate is used,
+ * it will appear as if the webpage is broken due to the forever hanging
+ * animations. The clip (!ie) and zoom (ie) CSS properties are used
+ * since they trigger a transition without making the browser
+ * animate anything and they're both highly underused CSS properties */
+.ng-animate { clip:rect(1px, auto, auto, 0); -ms-zoom:1.0001; }
+.ng-animate-active { clip:rect(0, auto, auto, 0); -ms-zoom:1; }
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 3bb6ff31..78be2143 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -790,16 +790,22 @@ angular.module('ngAnimate', ['ng'])
if(!data) {
var transitionDuration = 0, transitionDelay = 0,
animationDuration = 0, animationDelay = 0,
- transitionDelayStyle, animationDelayStyle;
+ transitionDelayStyle, animationDelayStyle,
+ transitionDurationStyle,
+ transitionPropertyStyle;
//we want all the styles defined before and after
forEach(element, function(element) {
if (element.nodeType == ELEMENT_NODE) {
var elementStyles = $window.getComputedStyle(element) || {};
- transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
+ transitionDurationStyle = elementStyles[transitionProp + durationKey];
+
+ transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
if(!onlyCheckTransition) {
+ transitionPropertyStyle = elementStyles[transitionProp + propertyKey];
+
transitionDelayStyle = elementStyles[transitionProp + delayKey];
transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
@@ -820,12 +826,14 @@ angular.module('ngAnimate', ['ng'])
});
data = {
total : 0,
+ transitionPropertyStyle: transitionPropertyStyle,
+ transitionDurationStyle: transitionDurationStyle,
transitionDelayStyle: transitionDelayStyle,
- transitionDelay : transitionDelay,
- transitionDuration : transitionDuration,
+ transitionDelay: transitionDelay,
+ transitionDuration: transitionDuration,
animationDelayStyle: animationDelayStyle,
- animationDelay : animationDelay,
- animationDuration : animationDuration
+ animationDelay: animationDelay,
+ animationDuration: animationDuration
};
if(cacheKey) {
lookupCache[cacheKey] = data;
@@ -896,7 +904,7 @@ angular.module('ngAnimate', ['ng'])
node.style[transitionProp + propertyKey] = 'none';
}
- var activeClassName = '';
+ var activeClassName = 'ng-animate-active ';
forEach(className.split(' '), function(klass, i) {
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
});
@@ -910,25 +918,38 @@ angular.module('ngAnimate', ['ng'])
return;
}
+ var applyFallbackStyle, style = '';
if(timings.transitionDuration > 0) {
node.style[transitionProp + propertyKey] = '';
+
+ var propertyStyle = timings.transitionPropertyStyle;
+ if(propertyStyle.indexOf('all') == -1) {
+ applyFallbackStyle = true;
+ var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip';
+ style += prefix + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
+ style += prefix + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
+ }
}
if(ii > 0) {
- var staggerStyle = '';
if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
- staggerStyle += prefix + 'transition-delay: ' +
- prepareStaggerDelay(timings.transitionDelayStyle, stagger.transitionDelay, ii) + '; ';
+ var delayStyle = timings.transitionDelayStyle;
+ if(applyFallbackStyle) {
+ delayStyle += ', ' + timings.transitionDelay + 's';
+ }
+
+ style += prefix + 'transition-delay: ' +
+ prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
}
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
- staggerStyle += prefix + 'animation-delay: ' +
- prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
+ style += prefix + 'animation-delay: ' +
+ prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
}
+ }
- if(staggerStyle.length > 0) {
- formerStyle = applyStyle(node, staggerStyle);
- }
+ if(style.length > 0) {
+ formerStyle = applyStyle(node, style);
}
element.addClass(activeClassName);
diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js
index f3523692..3248445a 100644
--- a/test/ngAnimate/animateSpec.js
+++ b/test/ngAnimate/animateSpec.js
@@ -784,6 +784,59 @@ describe("ngAnimate", function() {
});
describe("Transitions", function() {
+ it("should only apply the fallback transition property unless all properties are being animated",
+ inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
+
+ if (!$sniffer.animations) return;
+
+ ss.addRule('.all.ng-enter', '-webkit-transition:1s linear all;' +
+ 'transition:1s linear all');
+
+ ss.addRule('.one.ng-enter', '-webkit-transition:1s linear color;' +
+ 'transition:1s linear color');
+
+ var element = $compile('<div></div>')($rootScope);
+ var child = $compile('<div class="all">...</div>')($rootScope);
+ $rootElement.append(element);
+ var body = jqLite($document[0].body);
+ body.append($rootElement);
+
+ $animate.enter(child, element);
+ $rootScope.$digest();
+ $timeout.flush();
+
+ expect(child.attr('style') || '').not.toContain('transition-property');
+ expect(child.hasClass('ng-animate')).toBe(true);
+ expect(child.hasClass('ng-animate-active')).toBe(true);
+
+ browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 });
+ $timeout.flush();
+
+ expect(child.hasClass('ng-animate')).toBe(false);
+ expect(child.hasClass('ng-animate-active')).toBe(false);
+
+ child.remove();
+
+ var child2 = $compile('<div class="one">...</div>')($rootScope);
+
+ $animate.enter(child2, element);
+ $rootScope.$digest();
+ $timeout.flush();
+
+ //IE removes the -ms- prefix when placed on the style
+ var fallbackProperty = $sniffer.msie ? 'zoom' : 'clip';
+ var regExp = new RegExp("transition-property:\\s+color\\s*,\\s*" + fallbackProperty + "\\s*;");
+ expect(child2.attr('style') || '').toMatch(regExp);
+ expect(child2.hasClass('ng-animate')).toBe(true);
+ expect(child2.hasClass('ng-animate-active')).toBe(true);
+
+ browserTrigger(child2,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 });
+ $timeout.flush();
+
+ expect(child2.hasClass('ng-animate')).toBe(false);
+ expect(child2.hasClass('ng-animate-active')).toBe(false);
+ }));
+
it("should skip transitions if disabled and run when enabled",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
@@ -1013,7 +1066,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
$timeout.flush();
- expect(elements[0].attr('style')).toBeFalsy();
+ expect(elements[0].attr('style')).not.toContain('transition-delay');
expect(elements[1].attr('style')).toMatch(/transition-delay: 2\.1\d*s,\s*4\.1\d*s/);
expect(elements[2].attr('style')).toMatch(/transition-delay: 2\.2\d*s,\s*4\.2\d*s/);
expect(elements[3].attr('style')).toMatch(/transition-delay: 2\.3\d*s,\s*4\.3\d*s/);
@@ -1030,7 +1083,7 @@ describe("ngAnimate", function() {
ss.addRule('.ani.ng-enter, .ani.ng-leave',
'-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' +
'animation:my_animation 1s 1s, your_animation 1s 2s;' +
- '-webkit-transition:1s linear all 0s;' +
+ '-webkit-transition:1s linear all 1s;' +
'transition:1s linear all 1s;');
ss.addRule('.ani.ng-enter-stagger, .ani.ng-leave-stagger',
@@ -1731,6 +1784,7 @@ describe("ngAnimate", function() {
expect(child.css(propertyKey)).toBe('background-color');
child.remove();
+ child = $compile('<div class="ani">...</div>')($rootScope);
child.attr('class','trans');
$animate.enter(child, element);
$rootScope.$digest();