Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit e71e7b6

Browse files
committed
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
1 parent c914cd9 commit e71e7b6

File tree

2 files changed

+60
-6
lines changed

2 files changed

+60
-6
lines changed

src/ngAnimate/animate.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1252,7 +1252,14 @@ angular.module('ngAnimate', ['ng'])
12521252
if(transitionDuration > 0) {
12531253
blockTransitions(element, className, isCurrentlyAnimating);
12541254
}
1255-
if(animationDuration > 0) {
1255+
1256+
//staggering keyframe animations work by adjusting the `animation-delay` CSS property
1257+
//on the given element, however, the delay value can only calculated after the reflow
1258+
//since by that time $animate knows how many elements are being animated. Therefore,
1259+
//until the reflow occurs the element needs to be blocked (where the keyframe animation
1260+
//is set to `none 0s`). This blocking mechanism should only be set for when a stagger
1261+
//animation is detected and when the element item index is greater than 0.
1262+
if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) {
12561263
blockKeyframeAnimations(element);
12571264
}
12581265

test/ngAnimate/animateSpec.js

+52-5
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,49 @@ describe("ngAnimate", function() {
841841
}));
842842

843843

844+
it("should block and unblock keyframe animations when a stagger animation kicks in while skipping the first element",
845+
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
846+
847+
if(!$sniffer.animations) return;
848+
849+
$animate.enabled(true);
850+
851+
ss.addRule('.blocked-animation.ng-enter',
852+
'-webkit-animation:my_animation 1s;' +
853+
'animation:my_animation 1s;');
854+
855+
ss.addRule('.blocked-animation.ng-enter-stagger',
856+
'-webkit-animation-delay:0.2s;' +
857+
'animation-delay:0.2s;');
858+
859+
var container = $compile(html('<div></div>'))($rootScope);
860+
861+
var elements = [];
862+
for(var i = 0; i < 4; i++) {
863+
var newScope = $rootScope.$new();
864+
var element = $compile('<div class="blocked-animation"></div>')(newScope);
865+
$animate.enter(element, container);
866+
elements.push(element);
867+
};
868+
869+
$rootScope.$digest();
870+
871+
expect(elements[0].attr('style')).toBeUndefined();
872+
expect(elements[1].attr('style')).toMatch(/animation:.*?none/);
873+
expect(elements[2].attr('style')).toMatch(/animation:.*?none/);
874+
expect(elements[3].attr('style')).toMatch(/animation:.*?none/);
875+
876+
$animate.triggerReflow();
877+
878+
expect(elements[0].attr('style')).toBeUndefined();
879+
expect(elements[1].attr('style')).not.toMatch(/animation:.*?none/);
880+
expect(elements[1].attr('style')).toMatch(/animation-delay: 0.2\d*s/);
881+
expect(elements[2].attr('style')).not.toMatch(/animation:.*?none/);
882+
expect(elements[2].attr('style')).toMatch(/animation-delay: 0.4\d*s/);
883+
expect(elements[3].attr('style')).not.toMatch(/animation:.*?none/);
884+
expect(elements[3].attr('style')).toMatch(/animation-delay: 0.6\d*s/);
885+
}));
886+
844887
it("should stagger items when multiple animation durations/delays are defined",
845888
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
846889

@@ -3013,7 +3056,7 @@ describe("ngAnimate", function() {
30133056
}));
30143057

30153058

3016-
it('should block and unblock keyframe animations around the reflow operation',
3059+
it('should not block keyframe animations around the reflow operation',
30173060
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) {
30183061

30193062
if (!$sniffer.animations) return;
@@ -3032,15 +3075,19 @@ describe("ngAnimate", function() {
30323075

30333076
$animate.addClass(element, 'trigger-class');
30343077

3035-
expect(node.style[animationKey]).toContain('none');
3078+
expect(node.style[animationKey]).not.toContain('none');
30363079

30373080
$animate.triggerReflow();
30383081

30393082
expect(node.style[animationKey]).not.toContain('none');
3083+
3084+
browserTrigger(element, 'animationend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
3085+
3086+
expect(node.style[animationKey]).not.toContain('none');
30403087
}));
30413088

30423089

3043-
it('should block and unblock keyframe animations before the followup JS animation occurs', function() {
3090+
it('should not block keyframe animations at anytime before a followup JS animation occurs', function() {
30443091
module(function($animateProvider) {
30453092
$animateProvider.register('.special', function($sniffer, $window) {
30463093
var prop = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation';
@@ -3076,8 +3123,8 @@ describe("ngAnimate", function() {
30763123

30773124
var prop = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation';
30783125

3079-
expect(element[0].style[prop]).toContain('none');
3080-
expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('0s');
3126+
expect(element[0].style[prop]).not.toContain('none');
3127+
expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('1s');
30813128

30823129
$animate.triggerReflow();
30833130
});

0 commit comments

Comments
 (0)