fix(animate): ensure transition properties are removed once the animation is over
This commit is contained in:
parent
3fd898e91f
commit
b8e69a21a9
|
@ -34,6 +34,8 @@ export class Animation {
|
|||
|
||||
private _stringPrefix: string = '';
|
||||
|
||||
private _temporaryStyles: {[key: string]: string} = {};
|
||||
|
||||
/** total amount of time that the animation should take including delay */
|
||||
get totalTime(): number {
|
||||
let delay = this.computedDelay != null ? this.computedDelay : 0;
|
||||
|
@ -65,10 +67,25 @@ export class Animation {
|
|||
*/
|
||||
setup(): void {
|
||||
if (this.data.fromStyles != null) this.applyStyles(this.data.fromStyles);
|
||||
if (this.data.duration != null)
|
||||
if (this.data.duration != null) {
|
||||
this._temporaryStyles['transitionDuration'] = this._readStyle('transitionDuration');
|
||||
this.applyStyles({'transitionDuration': this.data.duration.toString() + 'ms'});
|
||||
if (this.data.delay != null)
|
||||
}
|
||||
if (this.data.delay != null) {
|
||||
this._temporaryStyles['transitionDelay'] = this._readStyle('transitionDelay');
|
||||
this.applyStyles({'transitionDelay': this.data.delay.toString() + 'ms'});
|
||||
}
|
||||
|
||||
if (!StringMapWrapper.isEmpty(this.data.animationStyles)) {
|
||||
// it's important that we setup a list of the styles and their
|
||||
// initial inline style values prior to applying the animation
|
||||
// styles such that we can restore the values after the animation
|
||||
// has been completed.
|
||||
StringMapWrapper.keys(this.data.animationStyles)
|
||||
.forEach((prop) => { this._temporaryStyles[prop] = this._readStyle(prop); });
|
||||
|
||||
this.applyStyles(this.data.animationStyles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,12 +115,8 @@ export class Animation {
|
|||
*/
|
||||
applyStyles(styles: {[key: string]: any}): void {
|
||||
StringMapWrapper.forEach(styles, (value, key) => {
|
||||
var dashCaseKey = camelCaseToDashCase(key);
|
||||
if (isPresent(DOM.getStyle(this.element, dashCaseKey))) {
|
||||
DOM.setStyle(this.element, dashCaseKey, value.toString());
|
||||
} else {
|
||||
DOM.setStyle(this.element, this._stringPrefix + dashCaseKey, value.toString());
|
||||
}
|
||||
var prop = this._formatStyleProp(key);
|
||||
DOM.setStyle(this.element, prop, value.toString());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -123,6 +136,26 @@ export class Animation {
|
|||
for (let i = 0, len = classes.length; i < len; i++) DOM.removeClass(this.element, classes[i]);
|
||||
}
|
||||
|
||||
private _readStyle(prop: string): string {
|
||||
return DOM.getStyle(this.element, this._formatStyleProp(prop));
|
||||
}
|
||||
|
||||
private _formatStyleProp(prop: string): string {
|
||||
prop = camelCaseToDashCase(prop);
|
||||
return prop.indexOf('animation') >= 0 ? this._stringPrefix + prop : prop;
|
||||
}
|
||||
|
||||
private _removeAndRestoreStyles(styles: {[key: string]: string}): void {
|
||||
StringMapWrapper.forEach(styles, (value, prop) => {
|
||||
prop = this._formatStyleProp(prop);
|
||||
if (value.length > 0) {
|
||||
DOM.setStyle(this.element, prop, value);
|
||||
} else {
|
||||
DOM.removeStyle(this.element, prop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds events to track when animations have finished
|
||||
*/
|
||||
|
@ -147,6 +180,9 @@ export class Animation {
|
|||
*/
|
||||
handleAnimationCompleted(): void {
|
||||
this.removeClasses(this.data.animationClasses);
|
||||
this._removeAndRestoreStyles(this._temporaryStyles);
|
||||
this._temporaryStyles = {};
|
||||
|
||||
this.callbacks.forEach(callback => callback());
|
||||
this.callbacks = [];
|
||||
this.eventClearFunctions.forEach(fn => fn());
|
||||
|
|
|
@ -14,6 +14,9 @@ export class CssAnimationOptions {
|
|||
/** classes to be added for the duration of the animation */
|
||||
animationClasses: string[] = [];
|
||||
|
||||
/** styles to be applied for the duration of the animation */
|
||||
animationStyles: {[key: string]: string} = {};
|
||||
|
||||
/** override the duration of the animation (in milliseconds) */
|
||||
duration: number;
|
||||
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import {
|
||||
el,
|
||||
describe,
|
||||
ddescribe,
|
||||
beforeEach,
|
||||
it,
|
||||
iit,
|
||||
expect,
|
||||
inject,
|
||||
SpyObject
|
||||
} from 'angular2/testing_internal';
|
||||
import {CssAnimationOptions} from 'angular2/src/animate/css_animation_options';
|
||||
import {Animation} from 'angular2/src/animate/animation';
|
||||
import {BrowserDetails} from 'angular2/src/animate/browser_details';
|
||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
||||
|
||||
export function main() {
|
||||
describe("Animation", () => {
|
||||
var element;
|
||||
|
||||
beforeEach(() => { element = el('<div></div>'); });
|
||||
|
||||
describe('transition-duration', () => {
|
||||
it('should only be applied for the duration of the animation', () => {
|
||||
var data = new CssAnimationOptions();
|
||||
data.duration = 1000;
|
||||
|
||||
expect(element).not.toHaveCssStyle('transition-duration');
|
||||
|
||||
new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'transition-duration': '1000ms'});
|
||||
});
|
||||
|
||||
it('should be removed once the animation is over', () => {
|
||||
var data = new CssAnimationOptions();
|
||||
data.duration = 1000;
|
||||
|
||||
var animation = new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'transition-duration': '1000ms'});
|
||||
|
||||
animation.handleAnimationCompleted();
|
||||
expect(element).not.toHaveCssStyle('transition-duration');
|
||||
});
|
||||
|
||||
it('should be restore the pre-existing transition-duration once the animation is over if present',
|
||||
() => {
|
||||
DOM.setStyle(element, 'transition-duration', '5s');
|
||||
expect(element).toHaveCssStyle({'transition-duration': '5s'});
|
||||
|
||||
var data = new CssAnimationOptions();
|
||||
data.duration = 1000;
|
||||
|
||||
var animation = new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'transition-duration': '1000ms'});
|
||||
|
||||
animation.handleAnimationCompleted();
|
||||
|
||||
expect(element).toHaveCssStyle({'transition-duration': '5s'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transition-delay', () => {
|
||||
it('should only be applied for the delay of the animation', () => {
|
||||
var data = new CssAnimationOptions();
|
||||
data.delay = 1000;
|
||||
|
||||
expect(element).not.toHaveCssStyle('transition-delay');
|
||||
|
||||
new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'transition-delay': '1000ms'});
|
||||
});
|
||||
|
||||
it('should be removed once the animation is over', () => {
|
||||
var data = new CssAnimationOptions();
|
||||
data.delay = 1000;
|
||||
|
||||
var animation = new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'transition-delay': '1000ms'});
|
||||
|
||||
animation.handleAnimationCompleted();
|
||||
expect(element).not.toHaveCssStyle('transition-delay');
|
||||
});
|
||||
|
||||
it('should be restore the pre-existing transition-delay once the animation is over if present',
|
||||
() => {
|
||||
DOM.setStyle(element, 'transition-delay', '5s');
|
||||
expect(element).toHaveCssStyle({'transition-delay': '5s'});
|
||||
|
||||
var data = new CssAnimationOptions();
|
||||
data.delay = 1000;
|
||||
|
||||
var animation = new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'transition-delay': '1000ms'});
|
||||
|
||||
animation.handleAnimationCompleted();
|
||||
|
||||
expect(element).toHaveCssStyle({'transition-delay': '5s'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('temporary animation styles', () => {
|
||||
it('should be applied temporarily for the duration of the animation', () => {
|
||||
var data = new CssAnimationOptions();
|
||||
data.duration = 1000;
|
||||
data.animationStyles = {'width': '100px', 'opacity': '0.5'};
|
||||
|
||||
var animation = new Animation(element, data, new BrowserDetails());
|
||||
expect(element)
|
||||
.toHaveCssStyle({'opacity': '0.5', 'width': '100px', 'transition-duration': '1000ms'});
|
||||
|
||||
animation.handleAnimationCompleted();
|
||||
expect(element).not.toHaveCssStyle('width');
|
||||
expect(element).not.toHaveCssStyle('opacity');
|
||||
expect(element).not.toHaveCssStyle('transition-duration');
|
||||
});
|
||||
|
||||
it('should be restored back to the original styles on the element', () => {
|
||||
DOM.setStyle(element, 'height', '555px');
|
||||
|
||||
var data = new CssAnimationOptions();
|
||||
data.duration = 1000;
|
||||
data.animationStyles = {'width': '100px', 'height': '999px'};
|
||||
|
||||
var animation = new Animation(element, data, new BrowserDetails());
|
||||
expect(element).toHaveCssStyle({'width': '100px', 'height': '999px'});
|
||||
|
||||
animation.handleAnimationCompleted();
|
||||
expect(element).not.toHaveCssStyle('width');
|
||||
expect(element).toHaveCssStyle({'height': '555px'});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue