Skip to content

Commit bcf3878

Browse files
committed
fix($animateCss): the transitions options delay value should be applied before class application
Applying a CSS transition delay after a reflow has hit occured where a CSS class is added/removed doesn't work. This feature does however work with keyframe animations. This patch ensures that the provided `options.delay` value is applied beforehand. Closes angular#12584
1 parent b497f3e commit bcf3878

File tree

2 files changed

+85
-49
lines changed

2 files changed

+85
-49
lines changed

src/ngAnimate/animateCss.js

+17-16
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
598598
return closeAndReturnNoopAnimator();
599599
}
600600

601+
if (options.delay != null) {
602+
var delayStyle = parseFloat(options.delay);
603+
604+
if (flags.applyTransitionDelay) {
605+
temporaryStyles.push(getCssDelayStyle(delayStyle));
606+
}
607+
608+
if (flags.applyAnimationDelay) {
609+
temporaryStyles.push(getCssDelayStyle(delayStyle, true));
610+
}
611+
}
612+
601613
// we need to recalculate the delay value since we used a pre-emptive negative
602614
// delay value and the delay value is required for the final event checking. This
603615
// property will ensure that this will happen after the RAF phase has passed.
@@ -809,27 +821,16 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
809821
flags.hasAnimations = timings.animationDuration > 0;
810822
}
811823

812-
if (flags.applyTransitionDelay || flags.applyAnimationDelay) {
824+
if (flags.applyAnimationDelay) {
813825
relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
814826
? parseFloat(options.delay)
815827
: relativeDelay;
816828

817829
maxDelay = Math.max(relativeDelay, 0);
818-
819-
var delayStyle;
820-
if (flags.applyTransitionDelay) {
821-
timings.transitionDelay = relativeDelay;
822-
delayStyle = getCssDelayStyle(relativeDelay);
823-
temporaryStyles.push(delayStyle);
824-
node.style[delayStyle[0]] = delayStyle[1];
825-
}
826-
827-
if (flags.applyAnimationDelay) {
828-
timings.animationDelay = relativeDelay;
829-
delayStyle = getCssDelayStyle(relativeDelay, true);
830-
temporaryStyles.push(delayStyle);
831-
node.style[delayStyle[0]] = delayStyle[1];
832-
}
830+
timings.animationDelay = relativeDelay;
831+
delayStyle = getCssDelayStyle(relativeDelay, true);
832+
temporaryStyles.push(delayStyle);
833+
node.style[delayStyle[0]] = delayStyle[1];
833834
}
834835

835836
maxDelayTime = maxDelay * ONE_SECOND;

test/ngAnimate/animateCssSpec.js

+68-33
Original file line numberDiff line numberDiff line change
@@ -1671,11 +1671,13 @@ describe("ngAnimate $animateCss", function() {
16711671

16721672
describe("options", function() {
16731673
var element;
1674-
beforeEach(inject(function($rootElement, $$body) {
1675-
$$body.append($rootElement);
1674+
beforeEach(module(function() {
1675+
return function($rootElement, $$body) {
1676+
$$body.append($rootElement);
16761677

1677-
element = jqLite('<div></div>');
1678-
$rootElement.append(element);
1678+
element = jqLite('<div></div>');
1679+
$rootElement.append(element);
1680+
};
16791681
}));
16801682

16811683
describe("[$$skipPreparationClasses]", function() {
@@ -1862,7 +1864,6 @@ describe("ngAnimate $animateCss", function() {
18621864
animator.start();
18631865
triggerAnimationStartFrame();
18641866

1865-
18661867
var prop = element.css('transition-delay');
18671868
expect(prop).toEqual('500s');
18681869
}));
@@ -1978,6 +1979,53 @@ describe("ngAnimate $animateCss", function() {
19781979
expect(element.css('transition-delay')).toEqual('10s');
19791980
}));
19801981

1982+
it("should apply the keyframe and transition duration value before the CSS classes are applied", function() {
1983+
var classSpy = jasmine.createSpy();
1984+
module(function($provide) {
1985+
$provide.value('$$jqLite', {
1986+
addClass: function() {
1987+
classSpy();
1988+
},
1989+
removeClass: function() {
1990+
classSpy();
1991+
}
1992+
});
1993+
});
1994+
inject(function($animateCss, $rootElement) {
1995+
element.addClass('element');
1996+
ss.addRule('.element', '-webkit-animation:3s keyframe_animation;' +
1997+
'animation:3s keyframe_animation;' +
1998+
'transition:5s linear all;');
1999+
2000+
var options = {
2001+
delay: 2,
2002+
duration: 2,
2003+
addClass: 'superman',
2004+
$$skipPreparationClasses: true,
2005+
structural: true
2006+
};
2007+
var animator = $animateCss(element, options);
2008+
2009+
expect(element.attr('style') || '').not.toContain('animation-delay');
2010+
expect(element.attr('style') || '').not.toContain('transition-delay');
2011+
expect(classSpy).not.toHaveBeenCalled();
2012+
2013+
//redefine the classSpy to assert that the delay values have been
2014+
//applied before the classes are added
2015+
var assertionsRun = false;
2016+
classSpy = function() {
2017+
assertionsRun = true;
2018+
expect(element.css(prefix + 'animation-delay')).toEqual('2s');
2019+
expect(element.css('transition-delay')).toEqual('2s');
2020+
expect(element).not.toHaveClass('superman');
2021+
};
2022+
2023+
animator.start();
2024+
triggerAnimationStartFrame();
2025+
expect(assertionsRun).toBe(true);
2026+
});
2027+
});
2028+
19812029
it("should apply blocking before the animation starts, but then apply the detected delay when options.delay is true",
19822030
inject(function($animateCss, $rootElement) {
19832031

@@ -1995,41 +2043,28 @@ describe("ngAnimate $animateCss", function() {
19952043
animator.start();
19962044
triggerAnimationStartFrame();
19972045

1998-
expect(element.css('transition-delay')).toEqual('1s');
2046+
expect(element.attr('style')).not.toContain('transition-delay');
19992047
}));
20002048

2001-
they("should consider a negative value when delay:true is used with a $prop animation", {
2002-
'transition': function() {
2003-
return {
2004-
prop: 'transition-delay',
2005-
css: 'transition:2s linear all; transition-delay: -1s'
2006-
};
2007-
},
2008-
'keyframe': function(prefix) {
2009-
return {
2010-
prop: prefix + 'animation-delay',
2011-
css: prefix + 'animation:2s keyframe_animation; ' + prefix + 'animation-delay: -1s;'
2012-
};
2013-
}
2014-
}, function(testDetailsFactory) {
2049+
it("should consider a negative value when delay:true is used with a keyframe animation",
20152050
inject(function($animateCss, $rootElement) {
2016-
var testDetails = testDetailsFactory(prefix);
20172051

2018-
ss.addRule('.ng-enter', testDetails.css);
2019-
var options = {
2020-
delay: true,
2021-
event: 'enter',
2022-
structural: true
2023-
};
2052+
ss.addRule('.ng-enter', prefix + 'animation:2s keyframe_animation; ' +
2053+
prefix + 'animation-delay: -1s;');
20242054

2025-
var animator = $animateCss(element, options);
2055+
var options = {
2056+
delay: true,
2057+
event: 'enter',
2058+
structural: true
2059+
};
20262060

2027-
animator.start();
2028-
triggerAnimationStartFrame();
2061+
var animator = $animateCss(element, options);
20292062

2030-
expect(element.css(testDetails.prop)).toContain('-1s');
2031-
});
2032-
});
2063+
animator.start();
2064+
triggerAnimationStartFrame();
2065+
2066+
expect(element.css(prefix + 'animation-delay')).toContain('-1s');
2067+
}));
20332068

20342069
they("should consider a negative value when a negative option delay is provided for a $prop animation", {
20352070
'transition': function() {

0 commit comments

Comments
 (0)