Skip to content

Commit

Permalink
fix(animate): ensure transition properties are removed once the anima…
Browse files Browse the repository at this point in the history
…tion is over
  • Loading branch information
matsko authored and vsavkin committed Dec 11, 2015
1 parent 3fd898e commit b8e69a2
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 8 deletions.
52 changes: 44 additions & 8 deletions modules/angular2/src/animate/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down Expand Up @@ -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());
});
}

Expand All @@ -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
*/
Expand All @@ -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());
Expand Down
3 changes: 3 additions & 0 deletions modules/angular2/src/animate/css_animation_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
133 changes: 133 additions & 0 deletions modules/angular2/test/animate/animation_spec.ts
Original file line number Diff line number Diff line change
@@ -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'});
});
});
});
}

0 comments on commit b8e69a2

Please sign in to comment.