From 9ff5dd39e0c1616b8cde8bfd831082ee083b0836 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 11 Jul 2019 17:38:44 -0400 Subject: [PATCH] add base before and after methods, add tests --- core/src/utils/animation/animation.ts | 157 +++++++++++++++--- .../utils/animation/test/animation.spec.ts | 77 +++++++++ .../src/utils/animation/test/basic/index.html | 2 +- 3 files changed, 215 insertions(+), 21 deletions(-) diff --git a/core/src/utils/animation/animation.ts b/core/src/utils/animation/animation.ts index 95b1275ef8..7ab91dd817 100644 --- a/core/src/utils/animation/animation.ts +++ b/core/src/utils/animation/animation.ts @@ -56,6 +56,24 @@ const generateKeyframeString = (name: string | undefined, keyframes: any[] = []) return keyframeString.join(' '); }; +const createKeyframeStylesheet = (keyframeString: string): HTMLElement => { + const stylesheet = document.createElement('style'); + stylesheet.appendChild(document.createTextNode(keyframeString)); + document.querySelector('head')!.appendChild(stylesheet); + + return stylesheet; +}; + +const addClassToArray = (classes: string[] = [], className: string | string[] | undefined): string[] => { + if (className !== undefined) { + const classNameToAppend = (Array.isArray(className)) ? className : [className]; + + return [...classes, ...classNameToAppend]; + } + + return classes; +}; + export class Animation { private elements: Element[] = []; private childAnimations: Animation[] = []; @@ -74,32 +92,138 @@ export class Animation { parentAnimation: Animation | undefined; + private beforeAddClasses: string[] = []; + private beforeRemoveClasses: string[] = []; + private beforeStylesValue: { [property: string]: any } = {}; + + private afterAddClasses: string[] = []; + private afterRemoveClasses: string[] = []; + private afterStylesValue: { [property: string]: any } = {}; + constructor(public _name: string | undefined) {} + /** + * Destroy this animation and all child animations. + */ + destroy(): Animation { + this.childAnimations.forEach(childAnimation => { + childAnimation.destroy(); + }); + + this.elements = []; + this.childAnimations = []; + + return this; + } + + /** + * Add CSS class to this animation's elements + * before the animation begins. + */ + beforeAddClass(className: string | string[] | undefined): Animation { + this.beforeAddClasses = addClassToArray(this.beforeAddClasses, className); + + return this; + } + + /** + * Remove CSS class from this animation's elements + * before the animation begins. + */ + beforeRemoveClass(className: string | string[] | undefined): Animation { + this.beforeRemoveClasses = addClassToArray(this.beforeRemoveClasses, className); + + return this; + } + + /** + * Set CSS inline styles to this animation's elements + * before the animation begins. + */ + beforeStyles(styles: { [property: string]: any } = {}): Animation { + this.beforeStylesValue = styles; + + return this; + } + + /** + * Clear CSS inline styles from this animation's elements + * before the animation begins. + */ + beforeClearStyles(propertyNames: string[] = []): Animation { + for (const property of propertyNames) { + this.beforeStylesValue[property] = ''; + } + + return this; + } + + /** + * Add CSS class to this animation's elements + * after the animation ends. + */ + afterAddClass(className: string | string[] | undefined): Animation { + this.afterAddClasses = addClassToArray(this.afterAddClasses, className); + + return this; + } + + /** + * Remove CSS class from this animation's elements + * after the animation ends. + */ + afterRemoveClass(className: string | string[] | undefined): Animation { + this.afterRemoveClasses = addClassToArray(this.afterRemoveClasses, className); + + return this; + } + + /** + * Set CSS inline styles to this animation's elements + * after the animation ends. + */ + afterStyles(styles: { [property: string]: any } = {}): Animation { + this.afterStylesValue = styles; + + return this; + } + + /** + * Clear CSS inline styles from this animation's elements + * after the animation ends. + */ + afterClearStyles(propertyNames: string[] = []): Animation { + for (const property of propertyNames) { + this.afterStylesValue[property] = ''; + } + + return this; + } + getEasing(): string | undefined { if (this._easing !== undefined) { return this._easing; } - if (this.parentAnimation && this.parentAnimation.getEasing() !== undefined) { return this.parentAnimation.getEasing(); } + if (this.parentAnimation) { return this.parentAnimation.getEasing(); } return undefined; } getDuration(): number | undefined { if (this._duration !== undefined) { return this._duration; } - if (this.parentAnimation && this.parentAnimation.getDuration() !== undefined) { return this.parentAnimation.getDuration(); } + if (this.parentAnimation) { return this.parentAnimation.getDuration(); } return undefined; } getIterations(): number | undefined { if (this._iterations !== undefined) { return this._iterations; } - if (this.parentAnimation && this.parentAnimation.getIterations() !== undefined) { return this.parentAnimation.getIterations(); } + if (this.parentAnimation) { return this.parentAnimation.getIterations(); } return undefined; } getDelay(): number | undefined { if (this._delay !== undefined) { return this._delay; } - if (this.parentAnimation && this.parentAnimation.getDelay() !== undefined) { return this.parentAnimation.getDelay(); } + if (this.parentAnimation) { return this.parentAnimation.getDelay(); } return undefined; } @@ -165,12 +289,7 @@ export class Animation { private initializeAnimation(): void { if (!this.stylesheet) { - const stylesheet = document.createElement('style'); - stylesheet.appendChild(document.createTextNode(this._keyframeString)); - - document.querySelector('head')!.appendChild(stylesheet); - - this.stylesheet = stylesheet; + this.stylesheet = createKeyframeStylesheet(this._keyframeString); } const animationName = this._name; @@ -227,18 +346,16 @@ export class Animation { } pause(): Animation { - if (!this.initialized) { - this.initializeAnimation(); + if (this.initialized) { + this.childAnimations.forEach(animation => { + animation.pause(); + }); + + this.elements.forEach(element => { + (element as HTMLElement).style.animationPlayState = 'paused'; + }); } - this.childAnimations.forEach(animation => { - animation.pause(); - }); - - this.elements.forEach(element => { - (element as HTMLElement).style.animationPlayState = 'paused'; - }); - return this; } diff --git a/core/src/utils/animation/test/animation.spec.ts b/core/src/utils/animation/test/animation.spec.ts index 57e9c740d7..4f6e1929ad 100644 --- a/core/src/utils/animation/test/animation.spec.ts +++ b/core/src/utils/animation/test/animation.spec.ts @@ -123,6 +123,83 @@ describe('Animation Class', () => { }); }); + describe('Before and After Animation Methods', () => { + let animation; + beforeEach(() => { + animation = new Animation(); + }); + + it('should register all "before" styles', () => { + animation.beforeStyles({ 'background': 'red', 'opacity': 1 }); + expect(Object.keys(animation.beforeStylesValue).length).toEqual(2); + }); + + it('should register all "before" classes given arrays', () => { + const classesToAdd = ['my-class', 'hello-world']; + const classesToRemove = ['ionic-framework']; + + animation.beforeAddClass(classesToAdd); + animation.beforeRemoveClass(classesToRemove); + + expect(animation.beforeAddClasses.length).toEqual(classesToAdd.length); + expect(animation.beforeRemoveClasses.length).toEqual(classesToRemove.length); + }); + + it('should register all "before" classes given strings', () => { + const classesToAdd = 'my-class'; + const classesToRemove = 'ionic-framework'; + + animation.beforeAddClass(classesToAdd); + animation.beforeRemoveClass(classesToRemove); + + expect(animation.beforeAddClasses.length).toEqual(1); + expect(animation.beforeRemoveClasses.length).toEqual(1); + }); + + it('should not register "before" classes given undefined', () => { + animation.beforeAddClass(undefined); + animation.beforeRemoveClass(undefined); + + expect(animation.beforeAddClasses.length).toEqual(0); + expect(animation.beforeRemoveClasses.length).toEqual(0); + }); + + it('should register all "after" styles', () => { + animation.afterStyles({ 'background': 'red', 'opacity': 1 }); + expect(Object.keys(animation.afterStylesValue).length).toEqual(2); + }); + + it('should register all "after" classes given arrays', () => { + const classesToAdd = ['my-class', 'hello-world']; + const classesToRemove = ['ionic-framework']; + + animation.afterAddClass(classesToAdd); + animation.afterRemoveClass(classesToRemove); + + expect(animation.afterAddClasses.length).toEqual(classesToAdd.length); + expect(animation.afterRemoveClasses.length).toEqual(classesToRemove.length); + }); + + it('should register all "after" classes given strings', () => { + const classesToAdd = 'my-class'; + const classesToRemove = 'ionic-framework'; + + animation.afterAddClass(classesToAdd); + animation.afterRemoveClass(classesToRemove); + + expect(animation.afterAddClasses.length).toEqual(1); + expect(animation.afterRemoveClasses.length).toEqual(1); + }); + + it('should not register "after" classes given undefined', () => { + animation.afterAddClass(undefined); + animation.afterRemoveClass(undefined); + + expect(animation.afterAddClasses.length).toEqual(0); + expect(animation.afterRemoveClasses.length).toEqual(0); + }); + }); + describe('Animation Config Methods', () => { let animation; beforeEach(() => { diff --git a/core/src/utils/animation/test/basic/index.html b/core/src/utils/animation/test/basic/index.html index 6c3de30a57..032d5ae9b2 100644 --- a/core/src/utils/animation/test/basic/index.html +++ b/core/src/utils/animation/test/basic/index.html @@ -13,7 +13,7 @@