aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatias Niemelä2014-02-26 13:49:18 -0500
committerMatias Niemelä2014-02-26 17:08:51 -0500
commite71e7b6cae57f25c5837dda98551c8e0a5cb720d (patch)
treef814810c8a7f8996aaa95db98d890641eb8058ea
parentc914cd99b3aaf932e3c0e2a585eead7b76621f1b (diff)
downloadangular.js-e71e7b6cae57f25c5837dda98551c8e0a5cb720d.tar.bz2
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
-rw-r--r--src/ngAnimate/animate.js9
-rw-r--r--test/ngAnimate/animateSpec.js57
2 files changed, 60 insertions, 6 deletions
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 0e5fda98..4b35d5fd 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -1252,7 +1252,14 @@ angular.module('ngAnimate', ['ng'])
if(transitionDuration > 0) {
blockTransitions(element, className, isCurrentlyAnimating);
}
- if(animationDuration > 0) {
+
+ //staggering keyframe animations work by adjusting the `animation-delay` CSS property
+ //on the given element, however, the delay value can only calculated after the reflow
+ //since by that time $animate knows how many elements are being animated. Therefore,
+ //until the reflow occurs the element needs to be blocked (where the keyframe animation
+ //is set to `none 0s`). This blocking mechanism should only be set for when a stagger
+ //animation is detected and when the element item index is greater than 0.
+ if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) {
blockKeyframeAnimations(element);
}
diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js
index 9179eb2b..eaed757e 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('<div></div>'))($rootScope);
+
+ var elements = [];
+ for(var i = 0; i < 4; i++) {
+ var newScope = $rootScope.$new();
+ var element = $compile('<div class="blocked-animation"></div>')(newScope);
+ $animate.enter(element, container);
+ elements.push(element);
+ };
+
+ $rootScope.$digest();
+
+ expect(elements[0].attr('style')).toBeUndefined();
+ 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')).toBeUndefined();
+ 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();
});