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

Commit

Permalink
fix($animate): only block keyframes if a stagger is set to occur
Browse files Browse the repository at this point in the history
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
  • Loading branch information
matsko committed Feb 26, 2014
1 parent c914cd9 commit e71e7b6
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 6 deletions.
9 changes: 8 additions & 1 deletion src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
57 changes: 52 additions & 5 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down Expand Up @@ -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;
Expand All @@ -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';
Expand Down Expand Up @@ -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();
});
Expand Down

0 comments on commit e71e7b6

Please sign in to comment.