X Tutup
Skip to content

Commit b8e69a2

Browse files
matskovsavkin
authored andcommitted
fix(animate): ensure transition properties are removed once the animation is over
1 parent 3fd898e commit b8e69a2

File tree

3 files changed

+180
-8
lines changed

3 files changed

+180
-8
lines changed

modules/angular2/src/animate/animation.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export class Animation {
3434

3535
private _stringPrefix: string = '';
3636

37+
private _temporaryStyles: {[key: string]: string} = {};
38+
3739
/** total amount of time that the animation should take including delay */
3840
get totalTime(): number {
3941
let delay = this.computedDelay != null ? this.computedDelay : 0;
@@ -65,10 +67,25 @@ export class Animation {
6567
*/
6668
setup(): void {
6769
if (this.data.fromStyles != null) this.applyStyles(this.data.fromStyles);
68-
if (this.data.duration != null)
70+
if (this.data.duration != null) {
71+
this._temporaryStyles['transitionDuration'] = this._readStyle('transitionDuration');
6972
this.applyStyles({'transitionDuration': this.data.duration.toString() + 'ms'});
70-
if (this.data.delay != null)
73+
}
74+
if (this.data.delay != null) {
75+
this._temporaryStyles['transitionDelay'] = this._readStyle('transitionDelay');
7176
this.applyStyles({'transitionDelay': this.data.delay.toString() + 'ms'});
77+
}
78+
79+
if (!StringMapWrapper.isEmpty(this.data.animationStyles)) {
80+
// it's important that we setup a list of the styles and their
81+
// initial inline style values prior to applying the animation
82+
// styles such that we can restore the values after the animation
83+
// has been completed.
84+
StringMapWrapper.keys(this.data.animationStyles)
85+
.forEach((prop) => { this._temporaryStyles[prop] = this._readStyle(prop); });
86+
87+
this.applyStyles(this.data.animationStyles);
88+
}
7289
}
7390

7491
/**
@@ -98,12 +115,8 @@ export class Animation {
98115
*/
99116
applyStyles(styles: {[key: string]: any}): void {
100117
StringMapWrapper.forEach(styles, (value, key) => {
101-
var dashCaseKey = camelCaseToDashCase(key);
102-
if (isPresent(DOM.getStyle(this.element, dashCaseKey))) {
103-
DOM.setStyle(this.element, dashCaseKey, value.toString());
104-
} else {
105-
DOM.setStyle(this.element, this._stringPrefix + dashCaseKey, value.toString());
106-
}
118+
var prop = this._formatStyleProp(key);
119+
DOM.setStyle(this.element, prop, value.toString());
107120
});
108121
}
109122

@@ -123,6 +136,26 @@ export class Animation {
123136
for (let i = 0, len = classes.length; i < len; i++) DOM.removeClass(this.element, classes[i]);
124137
}
125138

139+
private _readStyle(prop: string): string {
140+
return DOM.getStyle(this.element, this._formatStyleProp(prop));
141+
}
142+
143+
private _formatStyleProp(prop: string): string {
144+
prop = camelCaseToDashCase(prop);
145+
return prop.indexOf('animation') >= 0 ? this._stringPrefix + prop : prop;
146+
}
147+
148+
private _removeAndRestoreStyles(styles: {[key: string]: string}): void {
149+
StringMapWrapper.forEach(styles, (value, prop) => {
150+
prop = this._formatStyleProp(prop);
151+
if (value.length > 0) {
152+
DOM.setStyle(this.element, prop, value);
153+
} else {
154+
DOM.removeStyle(this.element, prop);
155+
}
156+
});
157+
}
158+
126159
/**
127160
* Adds events to track when animations have finished
128161
*/
@@ -147,6 +180,9 @@ export class Animation {
147180
*/
148181
handleAnimationCompleted(): void {
149182
this.removeClasses(this.data.animationClasses);
183+
this._removeAndRestoreStyles(this._temporaryStyles);
184+
this._temporaryStyles = {};
185+
150186
this.callbacks.forEach(callback => callback());
151187
this.callbacks = [];
152188
this.eventClearFunctions.forEach(fn => fn());

modules/angular2/src/animate/css_animation_options.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export class CssAnimationOptions {
1414
/** classes to be added for the duration of the animation */
1515
animationClasses: string[] = [];
1616

17+
/** styles to be applied for the duration of the animation */
18+
animationStyles: {[key: string]: string} = {};
19+
1720
/** override the duration of the animation (in milliseconds) */
1821
duration: number;
1922

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {
2+
el,
3+
describe,
4+
ddescribe,
5+
beforeEach,
6+
it,
7+
iit,
8+
expect,
9+
inject,
10+
SpyObject
11+
} from 'angular2/testing_internal';
12+
import {CssAnimationOptions} from 'angular2/src/animate/css_animation_options';
13+
import {Animation} from 'angular2/src/animate/animation';
14+
import {BrowserDetails} from 'angular2/src/animate/browser_details';
15+
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
16+
17+
export function main() {
18+
describe("Animation", () => {
19+
var element;
20+
21+
beforeEach(() => { element = el('<div></div>'); });
22+
23+
describe('transition-duration', () => {
24+
it('should only be applied for the duration of the animation', () => {
25+
var data = new CssAnimationOptions();
26+
data.duration = 1000;
27+
28+
expect(element).not.toHaveCssStyle('transition-duration');
29+
30+
new Animation(element, data, new BrowserDetails());
31+
expect(element).toHaveCssStyle({'transition-duration': '1000ms'});
32+
});
33+
34+
it('should be removed once the animation is over', () => {
35+
var data = new CssAnimationOptions();
36+
data.duration = 1000;
37+
38+
var animation = new Animation(element, data, new BrowserDetails());
39+
expect(element).toHaveCssStyle({'transition-duration': '1000ms'});
40+
41+
animation.handleAnimationCompleted();
42+
expect(element).not.toHaveCssStyle('transition-duration');
43+
});
44+
45+
it('should be restore the pre-existing transition-duration once the animation is over if present',
46+
() => {
47+
DOM.setStyle(element, 'transition-duration', '5s');
48+
expect(element).toHaveCssStyle({'transition-duration': '5s'});
49+
50+
var data = new CssAnimationOptions();
51+
data.duration = 1000;
52+
53+
var animation = new Animation(element, data, new BrowserDetails());
54+
expect(element).toHaveCssStyle({'transition-duration': '1000ms'});
55+
56+
animation.handleAnimationCompleted();
57+
58+
expect(element).toHaveCssStyle({'transition-duration': '5s'});
59+
});
60+
});
61+
62+
describe('transition-delay', () => {
63+
it('should only be applied for the delay of the animation', () => {
64+
var data = new CssAnimationOptions();
65+
data.delay = 1000;
66+
67+
expect(element).not.toHaveCssStyle('transition-delay');
68+
69+
new Animation(element, data, new BrowserDetails());
70+
expect(element).toHaveCssStyle({'transition-delay': '1000ms'});
71+
});
72+
73+
it('should be removed once the animation is over', () => {
74+
var data = new CssAnimationOptions();
75+
data.delay = 1000;
76+
77+
var animation = new Animation(element, data, new BrowserDetails());
78+
expect(element).toHaveCssStyle({'transition-delay': '1000ms'});
79+
80+
animation.handleAnimationCompleted();
81+
expect(element).not.toHaveCssStyle('transition-delay');
82+
});
83+
84+
it('should be restore the pre-existing transition-delay once the animation is over if present',
85+
() => {
86+
DOM.setStyle(element, 'transition-delay', '5s');
87+
expect(element).toHaveCssStyle({'transition-delay': '5s'});
88+
89+
var data = new CssAnimationOptions();
90+
data.delay = 1000;
91+
92+
var animation = new Animation(element, data, new BrowserDetails());
93+
expect(element).toHaveCssStyle({'transition-delay': '1000ms'});
94+
95+
animation.handleAnimationCompleted();
96+
97+
expect(element).toHaveCssStyle({'transition-delay': '5s'});
98+
});
99+
});
100+
101+
describe('temporary animation styles', () => {
102+
it('should be applied temporarily for the duration of the animation', () => {
103+
var data = new CssAnimationOptions();
104+
data.duration = 1000;
105+
data.animationStyles = {'width': '100px', 'opacity': '0.5'};
106+
107+
var animation = new Animation(element, data, new BrowserDetails());
108+
expect(element)
109+
.toHaveCssStyle({'opacity': '0.5', 'width': '100px', 'transition-duration': '1000ms'});
110+
111+
animation.handleAnimationCompleted();
112+
expect(element).not.toHaveCssStyle('width');
113+
expect(element).not.toHaveCssStyle('opacity');
114+
expect(element).not.toHaveCssStyle('transition-duration');
115+
});
116+
117+
it('should be restored back to the original styles on the element', () => {
118+
DOM.setStyle(element, 'height', '555px');
119+
120+
var data = new CssAnimationOptions();
121+
data.duration = 1000;
122+
data.animationStyles = {'width': '100px', 'height': '999px'};
123+
124+
var animation = new Animation(element, data, new BrowserDetails());
125+
expect(element).toHaveCssStyle({'width': '100px', 'height': '999px'});
126+
127+
animation.handleAnimationCompleted();
128+
expect(element).not.toHaveCssStyle('width');
129+
expect(element).toHaveCssStyle({'height': '555px'});
130+
});
131+
});
132+
});
133+
}

0 commit comments

Comments
 (0)
X Tutup