From 92ce98187222b6cca4ea11fdfd2b2433bd521cd9 Mon Sep 17 00:00:00 2001 From: Peter Deak Date: Sun, 10 Nov 2013 21:36:47 +0000 Subject: [PATCH] fix(ngAnimate): Correctly retain existing styles while animating, and also ensure that outdated styles aren't restored on completion. Fixes #4869. --- src/ngAnimate/animate.js | 30 ++++++++++++------------ test/ngAnimate/animateSpec.js | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 3b3c29b92990..e29867925256 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -842,13 +842,6 @@ angular.module('ngAnimate', ['ng']) }, 10, false); } - function applyStyle(node, style) { - var oldStyle = node.getAttribute('style') || ''; - var newStyle = (oldStyle.length > 0 ? '; ' : '') + style; - node.setAttribute('style', newStyle); - return oldStyle; - } - function getElementAnimationDetails(element, cacheKey) { var data = cacheKey ? lookupCache[cacheKey] : null; if(!data) { @@ -1002,10 +995,9 @@ angular.module('ngAnimate', ['ng']) var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000; var startTime = Date.now(); var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT; - var formerStyle; var ii = data.ii; - var applyFallbackStyle, style = ''; + var applyFallbackStyle, style = '', appliedStyles = []; if(timings.transitionDuration > 0) { node.style[TRANSITION_PROP + PROPERTY_KEY] = ''; @@ -1015,6 +1007,8 @@ angular.module('ngAnimate', ['ng']) var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip'; style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; '; style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; '; + appliedStyles.push(CSS_PREFIX + 'transition-property'); + appliedStyles.push(CSS_PREFIX + 'transition-duration'); } } @@ -1027,16 +1021,19 @@ angular.module('ngAnimate', ['ng']) style += CSS_PREFIX + 'transition-delay: ' + prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; '; + appliedStyles.push(CSS_PREFIX + 'transition-delay'); } if(stagger.animationDelay > 0 && stagger.animationDuration === 0) { style += CSS_PREFIX + 'animation-delay: ' + prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; '; + appliedStyles.push(CSS_PREFIX + 'animation-delay'); } } - if(style.length > 0) { - formerStyle = applyStyle(node, style); + if(appliedStyles.length > 0) { + var oldStyle = node.getAttribute('style') || ''; + node.setAttribute('style', oldStyle + '; ' + style); } element.on(css3AnimationEvents, onAnimationProgress); @@ -1049,10 +1046,13 @@ angular.module('ngAnimate', ['ng']) element.off(css3AnimationEvents, onAnimationProgress); element.removeClass(activeClassName); animateClose(element, className); - if(formerStyle != null) { - formerStyle.length > 0 ? - node.setAttribute('style', formerStyle) : - node.removeAttribute('style'); + for (var i in appliedStyles) { + + // removeProperty is not suppoerted in IE < 9, and FF + // doesn't allow clearing style properties via indexing + node.style.removeProperty ? + node.style.removeProperty(appliedStyles[i]) : + node.style[appliedStyles[i]] = ""; } }; diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 8ad7ed796686..3d0e24366356 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -420,6 +420,22 @@ describe("ngAnimate", function() { expect(element.children().length).toBe(0); })); + it("should retain existing styles of the animated element", + inject(function($animate, $rootScope, $sniffer, $timeout) { + + element.append(child); + child.attr('style', 'width: 20px'); + + $animate.addClass(child, 'ng-hide'); + $animate.leave(child); + if($sniffer.transitions) { + $rootScope.$digest(); + $timeout.flush(); + } + + expect(child.attr('style')).toMatch(/width: 20px/i); + })); + it("should call the cancel callback when another animation is called on the same element", inject(function($animate, $rootScope, $sniffer, $timeout) { @@ -975,6 +991,33 @@ describe("ngAnimate", function() { expect(element).toBeShown(); })); + it("should NOT overwrite styles with outdated values when animation completes", + inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { + + var style = '-webkit-transition-duration: 1s, 2000ms, 1s;' + + '-webkit-transition-property: height, left, opacity;' + + 'transition-duration: 1s, 2000ms, 1s;' + + 'transition-property: height, left, opacity;'; + + ss.addRule('.ng-hide-add', style); + ss.addRule('.ng-hide-remove', style); + + element = $compile(html('
foo
'))($rootScope); + element.addClass('ng-hide'); + + $animate.removeClass(element, 'ng-hide'); + + if ($sniffer.transitions) { + $timeout.flush(); + var now = Date.now(); + browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); + browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); + element.css('width', '200px'); + browserTrigger(element,'transitionend', { timeStamp: now + 2000, elapsedTime: 2 }); + expect(element.css('width')).toBe("200px"); + } + })); + it("should animate for the highest duration", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-transition:1s linear all 2s;' +