diff options
| -rw-r--r-- | css/angular.css | 11 | ||||
| -rw-r--r-- | src/ngAnimate/animate.js | 51 | ||||
| -rw-r--r-- | test/ngAnimate/animateSpec.js | 58 | 
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(); | 
