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

Commit c2ceb89

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`.
1 parent b3a3c6a commit c2ceb89

File tree

4 files changed

+142
-2
lines changed

4 files changed

+142
-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

+38-2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ 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 the animation is closed. This is useful for when the styles are used purely for the sake of the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation). By default this value is set to `false`.
207208
*
208209
* @return {object} an object with start and end methods and details about the animation.
209210
*
@@ -324,6 +325,23 @@ function createLocalCacheLookup() {
324325
};
325326
}
326327

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

426444
return function init(element, options) {
445+
var restoreStyles = {};
427446
var node = getDomNode(element);
428447
if (!node
429448
|| !node.parentNode
@@ -625,7 +644,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
625644
stagger.animationDuration === 0;
626645
}
627646

628-
applyAnimationFromStyles(element, options);
647+
if (options.from) {
648+
if (options.cleanupStyles) {
649+
registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
650+
}
651+
applyAnimationFromStyles(element, options);
652+
}
629653

630654
if (flags.blockTransition || flags.blockKeyframeAnimation) {
631655
applyBlocking(maxDuration);
@@ -692,6 +716,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
692716
applyAnimationClasses(element, options);
693717
applyAnimationStyles(element, options);
694718

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

888919
element.on(events.join(' '), onAnimationProgress);
889-
applyAnimationToStyles(element, options);
920+
if (options.to) {
921+
if (options.cleanupStyles) {
922+
registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
923+
}
924+
applyAnimationToStyles(element, options);
925+
}
890926
}
891927

892928
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 on 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)