Skip to content

Commit f1fda8f

Browse files
committed
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 4bcb307 commit f1fda8f

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

@@ -557,15 +558,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
557558
temporaryStyles.push(transitionStyle);
558559
}
559560

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

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

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

593-
var timings = computeTimings(node, fullClassName, cacheKey);
594-
var relativeDelay = timings.maxDelay;
595-
maxDelay = Math.max(relativeDelay, 0);
596-
maxDuration = timings.maxDuration;
597-
598595
var flags = {};
599596
flags.hasTransitions = timings.transitionDuration > 0;
600597
flags.hasAnimations = timings.animationDuration > 0;
601-
flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';
602-
flags.applyTransitionDuration = hasToStyles && (
603-
(flags.hasTransitions && !flags.hasTransitionAll)
598+
flags.applyTransitionDuration = options.duration > 0 || hasToStyles && (flags.hasTransitions
604599
|| (flags.hasAnimations && !flags.hasTransitions));
605600
flags.applyAnimationDuration = options.duration && flags.hasAnimations;
606601
flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
@@ -613,15 +608,15 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
613608
if (flags.applyTransitionDuration) {
614609
flags.hasTransitions = true;
615610
timings.transitionDuration = maxDuration;
616-
applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
617-
temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
611+
temporaryStyles.push(getCssTransitionStyle(timings, maxDuration));
618612
}
619613

620614
if (flags.applyAnimationDuration) {
621615
flags.hasAnimations = true;
622616
timings.animationDuration = maxDuration;
623617
temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
624618
}
619+
625620
}
626621

627622
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() {
@@ -2052,7 +2132,7 @@ describe("ngAnimate $animateCss", function() {
20522132

20532133
var style = element.attr('style');
20542134
expect(style).toContain('3000s');
2055-
expect(style).toContain('linear');
2135+
expect(element.css('transition-timing-function')).toBeOneOf('ease', 'cubic-bezier(0.25, 0.1, 0.25, 1)');
20562136
}));
20572137

20582138
it("should be applied to a CSS keyframe animation directly if keyframes are detected within the CSS class",
@@ -2158,7 +2238,7 @@ describe("ngAnimate $animateCss", function() {
21582238
expect(style).toMatch(/animation(?:-duration)?:\s*4s/);
21592239
expect(element.css('transition-duration')).toMatch('4s');
21602240
expect(element.css('transition-property')).toMatch('all');
2161-
expect(style).toContain('linear');
2241+
expect(element.css('transition-timing-function')).toBeOneOf('linear', 'cubic-bezier(0, 0, 1, 1)');
21622242
}));
21632243
});
21642244

@@ -2322,7 +2402,7 @@ describe("ngAnimate $animateCss", function() {
23222402
var animator = $animateCss(element, options);
23232403

23242404
expect(element.attr('style') || '').not.toContain('animation-delay');
2325-
expect(element.attr('style') || '').not.toContain('transition-delay');
2405+
expect(element.css('transition-delay')).toEqual('-2s');
23262406
expect(classSpy).not.toHaveBeenCalled();
23272407

23282408
//redefine the classSpy to assert that the delay values have been
@@ -2500,10 +2580,9 @@ describe("ngAnimate $animateCss", function() {
25002580
animator.start();
25012581
triggerAnimationStartFrame();
25022582

2503-
var style = element.attr('style');
25042583
expect(element.css('transition-duration')).toMatch('4s');
25052584
expect(element.css('transition-property')).toMatch('color');
2506-
expect(style).toContain('ease-in');
2585+
expect(element.css('transition-timing-function')).toBeOneOf('ease-in', 'cubic-bezier(0.42, 0, 1, 1)');
25072586
}));
25082587

25092588
it("should give priority to the provided delay value, but only update the delay style itself",
@@ -2754,11 +2833,9 @@ describe("ngAnimate $animateCss", function() {
27542833
animator.start();
27552834
triggerAnimationStartFrame();
27562835

2757-
2758-
var style = element.attr('style');
27592836
expect(element.css('transition-duration')).toMatch('2.5s');
27602837
expect(element.css('transition-property')).toMatch('all');
2761-
expect(style).toContain('linear');
2838+
expect(element.css('transition-timing-function')).toBeOneOf('ease', 'cubic-bezier(0.25, 0.1, 0.25, 1)');
27622839
}));
27632840

27642841
it("should remove all inline transition styling when an animation completes",
@@ -2889,7 +2966,7 @@ describe("ngAnimate $animateCss", function() {
28892966
it("should apply a transition duration if the existing transition duration's property value is not 'all'",
28902967
inject(function($animateCss, $rootElement) {
28912968

2892-
ss.addRule('.ng-enter', 'transition: 1s linear color');
2969+
ss.addRule('.ng-enter', 'transition: 1s cubic-bezier(0.25, 0.1, 0.25, 1) color');
28932970

28942971
var emptyObject = {};
28952972
var options = {
@@ -2903,10 +2980,9 @@ describe("ngAnimate $animateCss", function() {
29032980
triggerAnimationStartFrame();
29042981

29052982

2906-
var style = element.attr('style');
29072983
expect(element.css('transition-duration')).toMatch('1s');
2908-
expect(element.css('transition-property')).toMatch('all');
2909-
expect(style).toContain('linear');
2984+
expect(element.css('transition-property')).toMatch('color');
2985+
expect(element.css('transition-timing-function')).toBe('cubic-bezier(0.25, 0.1, 0.25, 1)');
29102986
}));
29112987

29122988
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)