diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index ad192a5b3e77..3bb6ff31ed96 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -789,7 +789,8 @@ angular.module('ngAnimate', ['ng']) var data = cacheKey ? lookupCache[cacheKey] : null; if(!data) { var transitionDuration = 0, transitionDelay = 0, - animationDuration = 0, animationDelay = 0; + animationDuration = 0, animationDelay = 0, + transitionDelayStyle, animationDelayStyle; //we want all the styles defined before and after forEach(element, function(element) { @@ -799,9 +800,13 @@ angular.module('ngAnimate', ['ng']) transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration); if(!onlyCheckTransition) { - transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay); + transitionDelayStyle = elementStyles[transitionProp + delayKey]; - animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay); + transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay); + + animationDelayStyle = elementStyles[animationProp + delayKey]; + + animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay); var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]); @@ -815,9 +820,11 @@ angular.module('ngAnimate', ['ng']) }); data = { total : 0, + transitionDelayStyle: transitionDelayStyle, transitionDelay : transitionDelay, - animationDelay : animationDelay, transitionDuration : transitionDuration, + animationDelayStyle: animationDelayStyle, + animationDelay : animationDelay, animationDuration : animationDuration }; if(cacheKey) { @@ -905,16 +912,25 @@ angular.module('ngAnimate', ['ng']) if(timings.transitionDuration > 0) { node.style[transitionProp + propertyKey] = ''; - if(ii > 0 && stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { - formerStyle = applyStyle(node, prefix + 'transition-delay: ' + - (ii * stagger.transitionDelay + timings.transitionDelay) + 's'); - } } - if(ii > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) { - formerStyle = applyStyle(node, prefix + 'animation-delay: ' + - (ii * stagger.animationDelay + timings.animationDelay) + 's'); + if(ii > 0) { + var staggerStyle = ''; + if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { + staggerStyle += prefix + 'transition-delay: ' + + prepareStaggerDelay(timings.transitionDelayStyle, stagger.transitionDelay, ii) + '; '; + } + + if(stagger.animationDelay > 0 && stagger.animationDuration === 0) { + staggerStyle += prefix + 'animation-delay: ' + + prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; '; + } + + if(staggerStyle.length > 0) { + formerStyle = applyStyle(node, staggerStyle); + } } + element.addClass(activeClassName); }); @@ -948,6 +964,15 @@ angular.module('ngAnimate', ['ng']) done(); } + function prepareStaggerDelay(delayStyle, staggerDelay, index) { + var style = ''; + angular.forEach(delayStyle.split(','), function(val, i) { + style += (i > 0 ? ',' : '') + + (index * staggerDelay + parseInt(val, 10)) + 's'; + }); + return style; + } + function onAnimationProgress(event) { event.stopPropagation(); var ev = event.originalEvent || event; diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index ec37d8c3edbc..f352369281bc 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -697,7 +697,7 @@ describe("ngAnimate", function() { ss.addRule('.ani.ng-enter, .ani.ng-leave, .ani-fake.ng-enter, .ani-fake.ng-leave', '-webkit-animation:1s my_animation;' + - 'transition:1s my_animation;'); + 'animation:1s my_animation;'); ss.addRule('.ani.ng-enter-stagger, .ani.ng-leave-stagger', '-webkit-animation-delay:0.1s;' + @@ -747,6 +747,40 @@ describe("ngAnimate", function() { expect(elements[3].attr('style')).not.toMatch(/animation-delay: 0\.3\d*s/); expect(elements[4].attr('style')).not.toMatch(/animation-delay: 0\.4\d*s/); })); + + it("should stagger items when multiple animation durations/delays are defined", + inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { + + if(!$sniffer.transitions) return; + + $animate.enabled(true); + + 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;'); + + ss.addRule('.ani.ng-enter-stagger, .ani.ng-leave-stagger', + '-webkit-animation-delay:0.1s;' + + 'animation-delay:0.1s;'); + + var container = $compile(html('
'))($rootScope); + + var elements = []; + for(var i = 0; i < 4; i++) { + var newScope = $rootScope.$new(); + var element = $compile('
')(newScope); + $animate.enter(element, container); + elements.push(element); + }; + + $rootScope.$digest(); + $timeout.flush(); + + expect(elements[0].attr('style')).toBeFalsy(); + expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.1\d*s,\s*2\.1\d*s/); + expect(elements[2].attr('style')).toMatch(/animation-delay: 1\.2\d*s,\s*2\.2\d*s/); + expect(elements[3].attr('style')).toMatch(/animation-delay: 1\.3\d*s,\s*2\.3\d*s/); + })); }); describe("Transitions", function() { @@ -950,7 +984,87 @@ describe("ngAnimate", function() { expect(elements[3].attr('style')).not.toMatch(/transition-delay: 0\.3\d*s/); expect(elements[4].attr('style')).not.toMatch(/transition-delay: 0\.4\d*s/); })); + + it("should stagger items when multiple transition durations/delays are defined", + inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { + + if(!$sniffer.transitions) return; + + $animate.enabled(true); + + ss.addRule('.ani.ng-enter, .ani.ng-leave', + '-webkit-transition:1s linear color 2s, 3s linear font-size 4s;' + + 'transition:1s linear color 2s, 3s linear font-size 4s;'); + + ss.addRule('.ani.ng-enter-stagger, .ani.ng-leave-stagger', + '-webkit-transition-delay:0.1s;' + + 'transition-delay:0.1s;'); + + var container = $compile(html('
'))($rootScope); + + var elements = []; + for(var i = 0; i < 4; i++) { + var newScope = $rootScope.$new(); + var element = $compile('
')(newScope); + $animate.enter(element, container); + elements.push(element); + }; + + $rootScope.$digest(); + $timeout.flush(); + + expect(elements[0].attr('style')).toBeFalsy(); + 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/); + })); }); + + it("should apply staggering to both transitions and keyframe animations when used within the same animation", + inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { + + if(!$sniffer.transitions) return; + + $animate.enabled(true); + + 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;' + + 'transition:1s linear all 1s;'); + + ss.addRule('.ani.ng-enter-stagger, .ani.ng-leave-stagger', + '-webkit-transition-delay:0.1s;' + + 'transition-delay:0.1s;' + + '-webkit-animation-delay:0.2s;' + + 'animation-delay:0.2s;'); + + var container = $compile(html('
'))($rootScope); + + var elements = []; + for(var i = 0; i < 3; i++) { + var newScope = $rootScope.$new(); + var element = $compile('
')(newScope); + $animate.enter(element, container); + elements.push(element); + }; + + $rootScope.$digest(); + $timeout.flush(); + + expect(elements[0].attr('style')).toBeFalsy(); + + expect(elements[1].attr('style')).toMatch(/transition-delay:\s+1.1\d*/); + expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.2\d*s,\s*2\.2\d*s/); + + expect(elements[2].attr('style')).toMatch(/transition-delay:\s+1.2\d*/); + expect(elements[2].attr('style')).toMatch(/animation-delay: 1\.4\d*s,\s*2\.4\d*s/); + + for(var i = 0; i < 3; i++) { + browserTrigger(elements[i],'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22000 }); + expect(elements[i].attr('style')).toBeFalsy(); + } + })); }); describe('animation evaluation', function () {