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

Commit e52d731

Browse files
committed
feat($animateCss): add support for temporary styles via cleanupStyles
Some animations make use of the `from` and `to` styling only for the lifetime of the animation. This patch allows for those styles to be removed once the animation is closed automatically within `$animateCss`. Closes #12930
1 parent 9b72843 commit e52d731

File tree

4 files changed

+145
-2
lines changed

4 files changed

+145
-2
lines changed

src/ng/animateCss.js

+7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ var $CoreAnimateCssProvider = function() {
4343
};
4444

4545
return function(element, options) {
46+
// there is no point in applying the styles since
47+
// there is no animation that goes on at all in
48+
// this version of $animateCss.
49+
if (options.cleanupStyles) {
50+
options.from = options.to = null;
51+
}
52+
4653
if (options.from) {
4754
element.css(options.from);
4855
options.from = null;

src/ngAnimate/animateCss.js

+41-2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
204204
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
205205
* * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
206206
* * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
207+
* * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
208+
* the animation is closed. This is useful for when the styles are used purely for the sake of
209+
* the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
210+
* By default this value is set to `false`.
207211
*
208212
* @return {object} an object with start and end methods and details about the animation.
209213
*
@@ -324,6 +328,23 @@ function createLocalCacheLookup() {
324328
};
325329
}
326330

331+
// we do not reassign an already present style value since
332+
// if we detect the style property value again we may be
333+
// detecting styles that were added via the `from` styles.
334+
// We make use of `isDefined` here since an empty string
335+
// or null value (which is what getPropertyValue will return
336+
// for a non-existing style) will still be marked as a valid
337+
// value for the style (a falsy value implies that the style
338+
// is to be removed at the end of the animation). If we had a simple
339+
// "OR" statement then it would not be enough to catch that.
340+
function registerRestorableStyles(backup, node, properties) {
341+
forEach(properties, function(prop) {
342+
backup[prop] = isDefined(backup[prop])
343+
? backup[prop]
344+
: node.style.getPropertyValue(prop);
345+
});
346+
}
347+
327348
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
328349
var gcsLookup = createLocalCacheLookup();
329350
var gcsStaggerLookup = createLocalCacheLookup();
@@ -424,6 +445,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
424445
}
425446

426447
return function init(element, options) {
448+
var restoreStyles = {};
427449
var node = getDomNode(element);
428450
if (!node
429451
|| !node.parentNode
@@ -625,7 +647,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
625647
stagger.animationDuration === 0;
626648
}
627649

628-
applyAnimationFromStyles(element, options);
650+
if (options.from) {
651+
if (options.cleanupStyles) {
652+
registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
653+
}
654+
applyAnimationFromStyles(element, options);
655+
}
629656

630657
if (flags.blockTransition || flags.blockKeyframeAnimation) {
631658
applyBlocking(maxDuration);
@@ -692,6 +719,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
692719
applyAnimationClasses(element, options);
693720
applyAnimationStyles(element, options);
694721

722+
if (Object.keys(restoreStyles).length) {
723+
forEach(restoreStyles, function(value, prop) {
724+
value ? node.style.setProperty(prop, value)
725+
: node.style.removeProperty(prop);
726+
});
727+
}
728+
695729
// the reason why we have this option is to allow a synchronous closing callback
696730
// that is fired as SOON as the animation ends (when the CSS is removed) or if
697731
// the animation never takes off at all. A good example is a leave animation since
@@ -886,7 +920,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
886920
}
887921

888922
element.on(events.join(' '), onAnimationProgress);
889-
applyAnimationToStyles(element, options);
923+
if (options.to) {
924+
if (options.cleanupStyles) {
925+
registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
926+
}
927+
applyAnimationToStyles(element, options);
928+
}
890929
}
891930

892931
function onAnimationExpired() {

test/ng/animateCssSpec.js

+32
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,38 @@ describe("$animateCss", function() {
115115
expect(cancelSpy).toHaveBeenCalled();
116116
expect(doneSpy).not.toHaveBeenCalled();
117117
}));
118+
119+
it("should not bother applying the provided [from] and [to] styles to the element if [cleanupStyles] is present",
120+
inject(function($animateCss, $rootScope) {
121+
122+
var animator = $animateCss(element, {
123+
cleanupStyles: true,
124+
from: { width: '100px' },
125+
to: { width: '900px', height: '1000px' }
126+
});
127+
128+
assertStyleIsEmpty(element, 'width');
129+
assertStyleIsEmpty(element, 'height');
130+
131+
var runner = animator.start();
132+
133+
assertStyleIsEmpty(element, 'width');
134+
assertStyleIsEmpty(element, 'height');
135+
136+
triggerRAF();
137+
138+
assertStyleIsEmpty(element, 'width');
139+
assertStyleIsEmpty(element, 'height');
140+
141+
runner.end();
142+
143+
assertStyleIsEmpty(element, 'width');
144+
assertStyleIsEmpty(element, 'height');
145+
146+
function assertStyleIsEmpty(element, prop) {
147+
expect(element[0].style.getPropertyValue(prop)).toBeFalsy();
148+
}
149+
}));
118150
});
119151

120152
});

test/ngAnimate/animateCssSpec.js

+65
Original file line numberDiff line numberDiff line change
@@ -2786,6 +2786,71 @@ describe("ngAnimate $animateCss", function() {
27862786
}));
27872787
});
27882788

2789+
describe("[cleanupStyles]", function() {
2790+
it("should cleanup [from] and [to] styles that have been applied for the animation when true",
2791+
inject(function($animateCss) {
2792+
2793+
var runner = $animateCss(element, {
2794+
duration: 1,
2795+
from: { background: 'gold' },
2796+
to: { color: 'brown' },
2797+
cleanupStyles: true
2798+
}).start();
2799+
2800+
assertStyleIsPresent(element, 'background', true);
2801+
assertStyleIsPresent(element, 'color', false);
2802+
2803+
triggerAnimationStartFrame();
2804+
2805+
assertStyleIsPresent(element, 'background', true);
2806+
assertStyleIsPresent(element, 'color', true);
2807+
2808+
runner.end();
2809+
2810+
assertStyleIsPresent(element, 'background', false);
2811+
assertStyleIsPresent(element, 'color', false);
2812+
2813+
function assertStyleIsPresent(element, style, bool) {
2814+
expect(element[0].style[style])[bool ? 'toBeTruthy' : 'toBeFalsy']();
2815+
}
2816+
}));
2817+
2818+
it('should restore existing overidden styles already present on the element when true',
2819+
inject(function($animateCss) {
2820+
2821+
element.css('height', '100px');
2822+
element.css('width', '111px');
2823+
2824+
var runner = $animateCss(element, {
2825+
duration: 1,
2826+
from: { height: '200px', 'font-size':'66px' },
2827+
to: { height: '300px', 'font-size': '99px', width: '222px' },
2828+
cleanupStyles: true
2829+
}).start();
2830+
2831+
assertStyle(element, 'height', '200px');
2832+
assertStyle(element, 'font-size', '66px');
2833+
assertStyle(element, 'width', '111px');
2834+
2835+
triggerAnimationStartFrame();
2836+
2837+
assertStyle(element, 'height', '300px');
2838+
assertStyle(element, 'width', '222px');
2839+
assertStyle(element, 'font-size', '99px');
2840+
2841+
runner.end();
2842+
2843+
assertStyle(element, 'width', '111px');
2844+
assertStyle(element, 'height', '100px');
2845+
2846+
expect(element[0].style.getPropertyValue('font-size')).not.toBe('66px');
2847+
2848+
function assertStyle(element, prop, value) {
2849+
expect(element[0].style.getPropertyValue(prop)).toBe(value);
2850+
}
2851+
}));
2852+
});
2853+
27892854
it('should round up long elapsedTime values to close off a CSS3 animation',
27902855
inject(function($animateCss) {
27912856

0 commit comments

Comments
 (0)