From c2c1a91103759b174372ec47a0cb185156ed13c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 22 May 2017 13:38:08 -0700 Subject: [PATCH] fix(animations): repair flicker issues with WA polyfill Fixes #16919 Fixes #16918 --- .../web_animations_style_normalizer.ts | 7 +-- .../src/render/transition_animation_engine.ts | 3 +- .../web_animations/web_animations_player.ts | 11 +++- packages/animations/browser/src/util.ts | 15 +++-- .../timeline_animation_engine_spec.ts | 0 .../transition_animation_engine_spec.ts | 0 .../web_animations_player_spec.ts | 62 +++++++++++++++++++ 7 files changed, 85 insertions(+), 13 deletions(-) rename packages/animations/browser/test/{engine => render}/timeline_animation_engine_spec.ts (100%) rename packages/animations/browser/test/{engine => render}/transition_animation_engine_spec.ts (100%) create mode 100644 packages/animations/browser/test/render/web_animations/web_animations_player_spec.ts diff --git a/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts b/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts index 89e59f7db273c..69d29c1871daf 100644 --- a/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts +++ b/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts @@ -5,6 +5,8 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {dashCaseToCamelCase} from '../../util'; + import {AnimationStyleNormalizer} from './animation_style_normalizer'; export class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer { @@ -41,8 +43,3 @@ function makeBooleanMap(keys: string[]): {[key: string]: boolean} { keys.forEach(key => map[key] = true); return map; } - -const DASH_CASE_REGEXP = /-+([a-z0-9])/g; -export function dashCaseToCamelCase(input: string): string { - return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase()); -} diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index 4162d30c5c3cf..f925d4b33741e 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -997,9 +997,8 @@ export class TransitionAnimationEngine { }); allConsumedElements.forEach(element => addClass(element, NG_ANIMATING_CLASSNAME)); - const player = optimizeGroupPlayer(allNewPlayers); - player.onDone(() => { + player.onDestroy(() => { allConsumedElements.forEach(element => removeClass(element, NG_ANIMATING_CLASSNAME)); setStyles(rootElement, instruction.toStyles); }); diff --git a/packages/animations/browser/src/render/web_animations/web_animations_player.ts b/packages/animations/browser/src/render/web_animations/web_animations_player.ts index c55487dd27794..3e0490340fdab 100644 --- a/packages/animations/browser/src/render/web_animations/web_animations_player.ts +++ b/packages/animations/browser/src/render/web_animations/web_animations_player.ts @@ -53,6 +53,11 @@ export class WebAnimationsPlayer implements AnimationPlayer { } init(): void { + this._buildPlayer(); + this._preparePlayerBeforeStart(); + } + + private _buildPlayer(): void { if (this._initialized) return; this._initialized = true; @@ -82,14 +87,16 @@ export class WebAnimationsPlayer implements AnimationPlayer { this._player = this._triggerWebAnimation(this.element, keyframes, this.options); this._finalKeyframe = keyframes.length ? keyframes[keyframes.length - 1] : {}; + this._player.addEventListener('finish', () => this._onFinish()); + } + private _preparePlayerBeforeStart() { // this is required so that the player doesn't start to animate right away if (this._delay) { this._resetDomPlayerState(); } else { this._player.pause(); } - this._player.addEventListener('finish', () => this._onFinish()); } /** @internal */ @@ -108,7 +115,7 @@ export class WebAnimationsPlayer implements AnimationPlayer { onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); } play(): void { - this.init(); + this._buildPlayer(); if (!this.hasStarted()) { this._onStartFns.forEach(fn => fn()); this._onStartFns = []; diff --git a/packages/animations/browser/src/util.ts b/packages/animations/browser/src/util.ts index 9963fc6de65f8..62e4c81f19abb 100644 --- a/packages/animations/browser/src/util.ts +++ b/packages/animations/browser/src/util.ts @@ -123,16 +123,18 @@ export function copyStyles( export function setStyles(element: any, styles: ɵStyleData) { if (element['style']) { - Object.keys(styles).forEach(prop => element.style[prop] = styles[prop]); + Object.keys(styles).forEach(prop => { + const camelProp = dashCaseToCamelCase(prop); + element.style[camelProp] = styles[prop]; + }); } } export function eraseStyles(element: any, styles: ɵStyleData) { if (element['style']) { Object.keys(styles).forEach(prop => { - // IE requires '' instead of null - // see https://github.com/angular/angular/issues/7916 - element.style[prop] = ''; + const camelProp = dashCaseToCamelCase(prop); + element.style[camelProp] = ''; }); } } @@ -206,3 +208,8 @@ export function mergeAnimationOptions( } return destination; } + +const DASH_CASE_REGEXP = /-+([a-z0-9])/g; +export function dashCaseToCamelCase(input: string): string { + return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase()); +} diff --git a/packages/animations/browser/test/engine/timeline_animation_engine_spec.ts b/packages/animations/browser/test/render/timeline_animation_engine_spec.ts similarity index 100% rename from packages/animations/browser/test/engine/timeline_animation_engine_spec.ts rename to packages/animations/browser/test/render/timeline_animation_engine_spec.ts diff --git a/packages/animations/browser/test/engine/transition_animation_engine_spec.ts b/packages/animations/browser/test/render/transition_animation_engine_spec.ts similarity index 100% rename from packages/animations/browser/test/engine/transition_animation_engine_spec.ts rename to packages/animations/browser/test/render/transition_animation_engine_spec.ts diff --git a/packages/animations/browser/test/render/web_animations/web_animations_player_spec.ts b/packages/animations/browser/test/render/web_animations/web_animations_player_spec.ts new file mode 100644 index 0000000000000..d7dc7aefe76ba --- /dev/null +++ b/packages/animations/browser/test/render/web_animations/web_animations_player_spec.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {DOMAnimation} from '../../../src/render/web_animations/dom_animation'; +import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animations_player'; + +export function main() { + let element: any; + let innerPlayer: MockDomAnimation|null = null; + beforeEach(() => { + element = {}; + element['animate'] = () => { return innerPlayer = new MockDomAnimation(); }; + }); + + describe('WebAnimationsPlayer tests', () => { + it('should automatically pause the player when created and initialized', () => { + const keyframes = [ + {opacity: 0, offset: 0}, + {opacity: 1, offset: 1}, + ]; + + const player = new WebAnimationsPlayer(element, keyframes, {duration: 1000}); + + player.init(); + const p = innerPlayer !; + expect(p.log).toEqual(['pause']); + + player.play(); + expect(p.log).toEqual(['pause', 'play']); + }); + + it('should not pause the player if created and started before initialized', () => { + const keyframes = [ + {opacity: 0, offset: 0}, + {opacity: 1, offset: 1}, + ]; + + const player = new WebAnimationsPlayer(element, keyframes, {duration: 1000}); + + player.play(); + const p = innerPlayer !; + expect(p.log).toEqual(['play']); + }); + }); +} + +class MockDomAnimation implements DOMAnimation { + log: string[] = []; + cancel(): void { this.log.push('cancel'); } + play(): void { this.log.push('play'); } + pause(): void { this.log.push('pause'); } + finish(): void { this.log.push('finish'); } + onfinish: Function = () => {}; + position: number = 0; + currentTime: number = 0; + addEventListener(eventName: string, handler: (event: any) => any): any {} + dispatchEvent(eventName: string): any {} +}