From 5fbb981f7e15cfa769757ffecd39ed047a3574df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 26 Feb 2014 13:49:18 -0500 Subject: [PATCH] fix($animate): only block keyframes if a stagger is set to occur Transitions must be blocked so that the initial CSS class can be applied without triggering an animation. Keyframes do not need to be blocked since animations are always triggered on the starting CSS class, however, if a stagger animation is set to occur then all elements for index > 0 should be blocked. This is to prevent the animation from occuring early on before the stagger delay for the given element has passed. With ngAnimate and keyframe animations, IE10 and Safari will render a slight flicker effect caused by the blocking. This fix resolves this issue. Closes #4225 --- src/ngAnimate/animate.js | 6 +++- test/ngAnimate/animateSpec.js | 57 ++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 6b1eedfc393a..10ff555c24f5 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -1247,7 +1247,11 @@ angular.module('ngAnimate', ['ng']) if(transitionDuration > 0) { blockTransitions(element, className, isCurrentlyAnimating); } - if(animationDuration > 0) { + + //keyframes don't need to be blocked unless staggered and if a stagger + //is present then only the items that are of current index > 0 are blocked (since + //the element at index==0 will never have a stagger delay at all). + if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) { blockKeyframeAnimations(element); } diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index a30b5fe99beb..c9174f3ca477 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -841,6 +841,49 @@ describe("ngAnimate", function() { })); + it("should block and unblock keyframe animations when a stagger animation kicks in while skipping the first element", + inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { + + if(!$sniffer.animations) return; + + $animate.enabled(true); + + ss.addRule('.blocked-animation.ng-enter', + '-webkit-animation:my_animation 1s;' + + 'animation:my_animation 1s;'); + + ss.addRule('.blocked-animation.ng-enter-stagger', + '-webkit-animation-delay:0.2s;' + + 'animation-delay:0.2s;'); + + 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(); + + expect(elements[0].attr('style')).toBeFalsy(); + expect(elements[1].attr('style')).toMatch(/animation:.*?none/); + expect(elements[2].attr('style')).toMatch(/animation:.*?none/); + expect(elements[3].attr('style')).toMatch(/animation:.*?none/); + + $animate.triggerReflow(); + + expect(elements[0].attr('style')).toBeFalsy(); + expect(elements[1].attr('style')).not.toMatch(/animation:.*?none/); + expect(elements[1].attr('style')).toMatch(/animation-delay: 0.2\d*s/); + expect(elements[2].attr('style')).not.toMatch(/animation:.*?none/); + expect(elements[2].attr('style')).toMatch(/animation-delay: 0.4\d*s/); + expect(elements[3].attr('style')).not.toMatch(/animation:.*?none/); + expect(elements[3].attr('style')).toMatch(/animation-delay: 0.6\d*s/); + })); + it("should stagger items when multiple animation durations/delays are defined", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { @@ -3013,7 +3056,7 @@ describe("ngAnimate", function() { })); - it('should block and unblock keyframe animations around the reflow operation', + it('should not block keyframe animations around the reflow operation', inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) { if (!$sniffer.animations) return; @@ -3032,15 +3075,19 @@ describe("ngAnimate", function() { $animate.addClass(element, 'trigger-class'); - expect(node.style[animationKey]).toContain('none'); + expect(node.style[animationKey]).not.toContain('none'); $animate.triggerReflow(); expect(node.style[animationKey]).not.toContain('none'); + + browserTrigger(element, 'animationend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); + + expect(node.style[animationKey]).not.toContain('none'); })); - it('should block and unblock keyframe animations before the followup JS animation occurs', function() { + it('should not block keyframe animations at anytime before a followup JS animation occurs', function() { module(function($animateProvider) { $animateProvider.register('.special', function($sniffer, $window) { var prop = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation'; @@ -3076,8 +3123,8 @@ describe("ngAnimate", function() { var prop = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation'; - expect(element[0].style[prop]).toContain('none'); - expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('0s'); + expect(element[0].style[prop]).not.toContain('none'); + expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('1s'); $animate.triggerReflow(); });