Skip to content

Commit de9777d

Browse files
Narretzmatsko
authored andcommitted
fix($animateCss): respect transition styles already on the element
Previously, $animateCss wouldn't use transition styles that were on the element before the animation process started. Precisely, transition property, timing-function and delay were overwritten in the process. Closes angular#12656 Closes angular#13333
1 parent 1b06f33 commit de9777d

File tree

4 files changed

+119
-44
lines changed

4 files changed

+119
-44
lines changed

src/ngAnimate/.jshintrc

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
"TRANSITION_DURATION_PROP": false,
3535
"TRANSITION_DELAY_PROP": false,
36+
"TRANSITION_TIMING_PROP": false,
37+
"TRANSITION_PROPERTY_PROP": false,
3638
"TRANSITION_PROP": false,
3739
"PROPERTY_KEY": false,
3840
"DURATION_KEY": false,

src/ngAnimate/animateCss.js

+27-32
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,13 @@ var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
223223
var CLOSING_TIME_BUFFER = 1.5;
224224

225225
var DETECT_CSS_PROPERTIES = {
226-
transitionDuration: TRANSITION_DURATION_PROP,
227-
transitionDelay: TRANSITION_DELAY_PROP,
228-
transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
229-
animationDuration: ANIMATION_DURATION_PROP,
230-
animationDelay: ANIMATION_DELAY_PROP,
231-
animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
226+
transitionDuration: TRANSITION_DURATION_PROP,
227+
transitionDelay: TRANSITION_DELAY_PROP,
228+
transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
229+
transitionTimingFunction: TRANSITION_PROP + TIMING_KEY,
230+
animationDuration: ANIMATION_DURATION_PROP,
231+
animationDelay: ANIMATION_DELAY_PROP,
232+
animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
232233
};
233234

234235
var DETECT_STAGGER_CSS_PROPERTIES = {
@@ -292,14 +293,14 @@ function truthyTimingValue(val) {
292293
return val === 0 || val != null;
293294
}
294295

295-
function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
296+
function getCssTransitionStyle(timings, duration) {
296297
var style = TRANSITION_PROP;
297298
var value = duration + 's';
298-
if (applyOnlyDuration) {
299-
style += DURATION_KEY;
300-
} else {
301-
value += ' linear all';
302-
}
299+
300+
value += ' ' + timings[TRANSITION_TIMING_PROP];
301+
value += ' ' + timings[TRANSITION_PROPERTY_PROP];
302+
value += timings[TRANSITION_DELAY_PROP] ? ' ' + timings[TRANSITION_DELAY_PROP] + 's' : '';
303+
303304
return [style, value];
304305
}
305306

@@ -555,15 +556,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
555556
temporaryStyles.push(transitionStyle);
556557
}
557558

558-
if (options.duration >= 0) {
559-
applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
560-
var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
561-
562-
// we set the duration so that it will be picked up by getComputedStyle later
563-
applyInlineStyle(node, durationStyle);
564-
temporaryStyles.push(durationStyle);
565-
}
566-
567559
if (options.keyframeStyle) {
568560
var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
569561
applyInlineStyle(node, keyframeStyle);
@@ -576,8 +568,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
576568
: gcsLookup.count(cacheKey)
577569
: 0;
578570

579-
var isFirst = itemIndex === 0;
571+
var timings = computeTimings(node, fullClassName, cacheKey);
572+
573+
if (options.duration > 0) {
574+
// Duration in options overwrites duration set in style
575+
timings.transitionDuration = options.duration;
576+
}
580577

578+
var relativeDelay = timings.maxDelay;
579+
maxDelay = Math.max(relativeDelay, 0);
580+
maxDuration = timings.maxDuration;
581+
582+
var isFirst = itemIndex === 0;
581583
// this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
582584
// without causing any combination of transitions to kick in. By adding a negative delay value
583585
// it forces the setup class' transition to end immediately. We later then remove the negative
@@ -588,17 +590,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
588590
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
589591
}
590592

591-
var timings = computeTimings(node, fullClassName, cacheKey);
592-
var relativeDelay = timings.maxDelay;
593-
maxDelay = Math.max(relativeDelay, 0);
594-
maxDuration = timings.maxDuration;
595-
596593
var flags = {};
597594
flags.hasTransitions = timings.transitionDuration > 0;
598595
flags.hasAnimations = timings.animationDuration > 0;
599-
flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';
600-
flags.applyTransitionDuration = hasToStyles && (
601-
(flags.hasTransitions && !flags.hasTransitionAll)
596+
flags.applyTransitionDuration = options.duration > 0 || hasToStyles && (flags.hasTransitions
602597
|| (flags.hasAnimations && !flags.hasTransitions));
603598
flags.applyAnimationDuration = options.duration && flags.hasAnimations;
604599
flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
@@ -611,15 +606,15 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
611606
if (flags.applyTransitionDuration) {
612607
flags.hasTransitions = true;
613608
timings.transitionDuration = maxDuration;
614-
applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
615-
temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
609+
temporaryStyles.push(getCssTransitionStyle(timings, maxDuration));
616610
}
617611

618612
if (flags.applyAnimationDuration) {
619613
flags.hasAnimations = true;
620614
timings.animationDuration = maxDuration;
621615
temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
622616
}
617+
623618
}
624619

625620
if (maxDuration === 0 && !flags.recalculateTimingStyles) {

src/ngAnimate/shared.js

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
6868
var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
6969
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
7070
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
71+
var TRANSITION_TIMING_PROP = TRANSITION_PROP + TIMING_KEY;
72+
var TRANSITION_PROPERTY_PROP = TRANSITION_PROP + PROPERTY_KEY;
7173

7274
var isPromiseLike = function(p) {
7375
return p && p.then ? true : false;

test/ngAnimate/animateCssSpec.js

+88-12
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,86 @@ describe("ngAnimate $animateCss", function() {
634634
keyframeProgress(element, 1, 20);
635635
assertAnimationComplete(true);
636636
}));
637+
638+
it("should apply all transition shorthand properties that are already on the element",
639+
inject(function($animateCss, $rootElement) {
640+
641+
ss.addRule('.action', 'transition: color 1s cubic-bezier(0.25, 0.1, 0.25, 1) 5s;');
642+
element.addClass('action');
643+
644+
var options = {
645+
to: { background: 'blue' }
646+
};
647+
648+
var animator = $animateCss(element, options);
649+
animator.start();
650+
triggerAnimationStartFrame();
651+
652+
expect(element.css('transition-duration')).toMatch('1s');
653+
expect(element.css('transition-delay')).toMatch('5s');
654+
expect(element.css('transition-property')).toMatch('color');
655+
expect(element.css('transition-timing-function')).toBe('cubic-bezier(0.25, 0.1, 0.25, 1)');
656+
}));
657+
658+
it("should apply all explicit transition properties that are already on the element",
659+
inject(function($animateCss, $rootElement) {
660+
661+
ss.addRule('.action', 'transition-duration: 1s;' +
662+
'transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);' +
663+
'transition-property: color;' +
664+
'transition-delay: 5s');
665+
element.addClass('action');
666+
667+
var options = {
668+
to: { background: 'blue' }
669+
};
670+
671+
var animator = $animateCss(element, options);
672+
animator.start();
673+
triggerAnimationStartFrame();
674+
675+
expect(element.css('transition-duration')).toMatch('1s');
676+
expect(element.css('transition-delay')).toMatch('5s');
677+
expect(element.css('transition-property')).toMatch('color');
678+
expect(element.css('transition-timing-function')).toBe('cubic-bezier(0.25, 0.1, 0.25, 1)');
679+
}));
680+
681+
it("should use the default transition-property (spec: all) if none is supplied in shorthand",
682+
inject(function($animateCss, $rootElement) {
683+
684+
ss.addRule('.action', 'transition: 1s ease');
685+
element.addClass('action');
686+
687+
var options = {
688+
to: { background: 'blue' }
689+
};
690+
691+
var animator = $animateCss(element, options);
692+
animator.start();
693+
triggerAnimationStartFrame();
694+
695+
expect(element.css('transition-property')).toBe('all');
696+
}));
697+
698+
it("should use the default easing (spec: ease) if none is supplied in shorthand",
699+
inject(function($animateCss, $rootElement) {
700+
701+
ss.addRule('.action', 'transition: color 1s');
702+
element.addClass('action');
703+
704+
var options = {
705+
to: { background: 'blue' }
706+
};
707+
708+
var animator = $animateCss(element, options);
709+
animator.start();
710+
triggerAnimationStartFrame();
711+
712+
// IE reports ease in cubic-bezier form
713+
expect(element.css('transition-timing-function')).toBeOneOf('ease', 'cubic-bezier(0.25, 0.1, 0.25, 1)');
714+
}));
715+
716+
637717
});
638718

639719
describe("staggering", function() {
@@ -1953,7 +2033,7 @@ describe("ngAnimate $animateCss", function() {
19532033

19542034
var style = element.attr('style');
19552035
expect(style).toContain('3000s');
1956-
expect(style).toContain('linear');
2036+
expect(element.css('transition-timing-function')).toBeOneOf('ease', 'cubic-bezier(0.25, 0.1, 0.25, 1)');
19572037
}));
19582038

19592039
it("should be applied to a CSS keyframe animation directly if keyframes are detected within the CSS class",
@@ -2059,7 +2139,7 @@ describe("ngAnimate $animateCss", function() {
20592139
expect(style).toMatch(/animation(?:-duration)?:\s*4s/);
20602140
expect(element.css('transition-duration')).toMatch('4s');
20612141
expect(element.css('transition-property')).toMatch('all');
2062-
expect(style).toContain('linear');
2142+
expect(element.css('transition-timing-function')).toBeOneOf('linear', 'cubic-bezier(0, 0, 1, 1)');
20632143
}));
20642144
});
20652145

@@ -2223,7 +2303,7 @@ describe("ngAnimate $animateCss", function() {
22232303
var animator = $animateCss(element, options);
22242304

22252305
expect(element.attr('style') || '').not.toContain('animation-delay');
2226-
expect(element.attr('style') || '').not.toContain('transition-delay');
2306+
expect(element.css('transition-delay')).toEqual('-2s');
22272307
expect(classSpy).not.toHaveBeenCalled();
22282308

22292309
//redefine the classSpy to assert that the delay values have been
@@ -2401,10 +2481,9 @@ describe("ngAnimate $animateCss", function() {
24012481
animator.start();
24022482
triggerAnimationStartFrame();
24032483

2404-
var style = element.attr('style');
24052484
expect(element.css('transition-duration')).toMatch('4s');
24062485
expect(element.css('transition-property')).toMatch('color');
2407-
expect(style).toContain('ease-in');
2486+
expect(element.css('transition-timing-function')).toBeOneOf('ease-in', 'cubic-bezier(0.42, 0, 1, 1)');
24082487
}));
24092488

24102489
it("should give priority to the provided delay value, but only update the delay style itself",
@@ -2655,11 +2734,9 @@ describe("ngAnimate $animateCss", function() {
26552734
animator.start();
26562735
triggerAnimationStartFrame();
26572736

2658-
2659-
var style = element.attr('style');
26602737
expect(element.css('transition-duration')).toMatch('2.5s');
26612738
expect(element.css('transition-property')).toMatch('all');
2662-
expect(style).toContain('linear');
2739+
expect(element.css('transition-timing-function')).toBeOneOf('ease', 'cubic-bezier(0.25, 0.1, 0.25, 1)');
26632740
}));
26642741

26652742
it("should remove all inline transition styling when an animation completes",
@@ -2790,7 +2867,7 @@ describe("ngAnimate $animateCss", function() {
27902867
it("should apply a transition duration if the existing transition duration's property value is not 'all'",
27912868
inject(function($animateCss, $rootElement) {
27922869

2793-
ss.addRule('.ng-enter', 'transition: 1s linear color');
2870+
ss.addRule('.ng-enter', 'transition: 1s cubic-bezier(0.25, 0.1, 0.25, 1) color');
27942871

27952872
var emptyObject = {};
27962873
var options = {
@@ -2804,10 +2881,9 @@ describe("ngAnimate $animateCss", function() {
28042881
triggerAnimationStartFrame();
28052882

28062883

2807-
var style = element.attr('style');
28082884
expect(element.css('transition-duration')).toMatch('1s');
2809-
expect(element.css('transition-property')).toMatch('all');
2810-
expect(style).toContain('linear');
2885+
expect(element.css('transition-property')).toMatch('color');
2886+
expect(element.css('transition-timing-function')).toBe('cubic-bezier(0.25, 0.1, 0.25, 1)');
28112887
}));
28122888

28132889
it("should apply a transition duration and an animation duration if duration + styles options are provided for a matching keyframe animation",

0 commit comments

Comments
 (0)