From 889b48d85f13986e3b18389669b68295e8d6d361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Thu, 5 Jan 2017 11:32:52 -0800 Subject: [PATCH] fix(core): animations should blend in all previously transitioned styles into next animation if interrupted (#13148) --- .../src/dom/web_animations_driver.ts | 24 +++++----- .../src/dom/web_animations_player.ts | 29 +++++------- .../test/dom/web_animations_driver_spec.ts | 45 ++++++++++++------- .../test/dom/web_animations_player_spec.ts | 26 +++++++++++ 4 files changed, 80 insertions(+), 44 deletions(-) diff --git a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts index e0ebfb2fa7..df3760a905 100644 --- a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts +++ b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts @@ -20,10 +20,8 @@ export class WebAnimationsDriver implements AnimationDriver { previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer { let formattedSteps: {[key: string]: string | number}[] = []; let startingStyleLookup: {[key: string]: string | number} = {}; - if (isPresent(startingStyles) && startingStyles.styles.length > 0) { + if (isPresent(startingStyles)) { startingStyleLookup = _populateStyles(startingStyles, {}); - startingStyleLookup['offset'] = 0; - formattedSteps.push(startingStyleLookup); } keyframes.forEach((keyframe: AnimationKeyframe) => { @@ -32,14 +30,18 @@ export class WebAnimationsDriver implements AnimationDriver { formattedSteps.push(data); }); - // this is a special case when only styles are applied as an - // animation. When this occurs we want to animate from start to - // end with the same values. Removing the offset and having only - // start/end values is suitable enough for the web-animations API - if (formattedSteps.length == 1) { - const start = formattedSteps[0]; - start['offset'] = null; - formattedSteps = [start, start]; + // Styling passed into element.animate() must always be balanced. + // The special cases below can occur if only style() calls exist + // within an animation or when a style() calls are used prior + // to a group() animation being issued or if the renderer is + // invoked by the user directly. + if (formattedSteps.length == 0) { + formattedSteps = [startingStyleLookup, startingStyleLookup]; + } else if (formattedSteps.length == 1) { + const start = startingStyleLookup; + const end = formattedSteps[0]; + end['offset'] = null; + formattedSteps = [start, end]; } const playerOptions: {[key: string]: string | number} = { diff --git a/modules/@angular/platform-browser/src/dom/web_animations_player.ts b/modules/@angular/platform-browser/src/dom/web_animations_player.ts index ddd53e72a1..fbaf7e697a 100644 --- a/modules/@angular/platform-browser/src/dom/web_animations_player.ts +++ b/modules/@angular/platform-browser/src/dom/web_animations_player.ts @@ -69,12 +69,21 @@ export class WebAnimationsPlayer implements AnimationPlayer { const previousStyleProps = Object.keys(this.previousStyles); if (previousStyleProps.length) { - let startingKeyframe = findStartingKeyframe(keyframes); + let startingKeyframe = keyframes[0]; + let missingStyleProps: string[] = []; previousStyleProps.forEach(prop => { - if (isPresent(startingKeyframe[prop])) { - startingKeyframe[prop] = this.previousStyles[prop]; + if (!isPresent(startingKeyframe[prop])) { + missingStyleProps.push(prop); } + startingKeyframe[prop] = this.previousStyles[prop]; }); + + if (missingStyleProps.length) { + for (let i = 1; i < keyframes.length; i++) { + let kf = keyframes[i]; + missingStyleProps.forEach(prop => { kf[prop] = _computeStyle(this.element, prop); }); + } + } } this._player = this._triggerWebAnimation(this.element, keyframes, this.options); @@ -180,17 +189,3 @@ function _copyKeyframeStyles(styles: {[style: string]: string | number}): }); return newStyles; } - -function findStartingKeyframe(keyframes: {[prop: string]: string | number}[]): - {[prop: string]: string | number} { - let startingKeyframe = keyframes[0]; - // it's important that we find the LAST keyframe - // to ensure that style overidding is final. - for (let i = 1; i < keyframes.length; i++) { - const kf = keyframes[i]; - const offset = kf['offset']; - if (offset !== 0) break; - startingKeyframe = kf; - } - return startingKeyframe; -} diff --git a/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts b/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts index 9a8dbef5e8..529bc9b927 100644 --- a/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts +++ b/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts @@ -9,23 +9,9 @@ import {AnimationPlayer} from '@angular/core'; import {el} from '@angular/platform-browser/testing/browser_util'; -import {DomAnimatePlayer} from '../../src/dom/dom_animate_player'; import {WebAnimationsDriver} from '../../src/dom/web_animations_driver'; import {WebAnimationsPlayer} from '../../src/dom/web_animations_player'; import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core'; -import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player'; - -class ExtendedWebAnimationsDriver extends WebAnimationsDriver { - public log: {[key: string]: any}[] = []; - - constructor() { super(); } - - /** @internal */ - _triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer { - this.log.push({'elm': elm, 'keyframes': keyframes, 'options': options}); - return new MockDomAnimatePlayer(); - } -} function _makeStyles(styles: {[key: string]: string | number}): AnimationStyles { return new AnimationStyles([styles]); @@ -38,10 +24,10 @@ function _makeKeyframe( export function main() { describe('WebAnimationsDriver', () => { - let driver: ExtendedWebAnimationsDriver; + let driver: WebAnimationsDriver; let elm: HTMLElement; beforeEach(() => { - driver = new ExtendedWebAnimationsDriver(); + driver = new WebAnimationsDriver(); elm = el('
'); }); @@ -101,6 +87,33 @@ export function main() { const allZero = player.keyframes.every(kf => kf['offset'] == 0); expect(allZero).toBe(true); }); + + it('should create an animation with only starting-styles as data', () => { + const startingStyles = _makeStyles({color: 'red', fontSize: '20px'}); + + const player = driver.animate(elm, startingStyles, [], 1000, 1000, null); + + expect(player.keyframes).toEqual([ + {color: 'red', fontSize: '20px'}, + {color: 'red', fontSize: '20px'}, + ]); + }); + + it('should create a balanced animation when only one keyframe is passed in', () => { + const startingStyles = _makeStyles({color: 'red', fontSize: '20px'}); + + const keyframeStyles: any = [_makeKeyframe(1, {color: 'blue'})]; + + const player = driver.animate(elm, startingStyles, keyframeStyles, 1000, 1000, null); + + const kf0 = player.keyframes[0]; + const kf1 = player.keyframes[1]; + + expect(kf0['color']).toEqual('red'); + expect(kf0['fontSize']).toEqual('20px'); + expect(kf1['color']).toEqual('blue'); + expect(kf1['fontSize']).toEqual('20px'); + }); }); } diff --git a/modules/@angular/platform-browser/test/dom/web_animations_player_spec.ts b/modules/@angular/platform-browser/test/dom/web_animations_player_spec.ts index 7bba7ed96d..630e8e52f3 100644 --- a/modules/@angular/platform-browser/test/dom/web_animations_player_spec.ts +++ b/modules/@angular/platform-browser/test/dom/web_animations_player_spec.ts @@ -208,6 +208,32 @@ export function main() { ]); }); + it('should allow previous styles to be merged into the starting keyframe of the animation that were not apart of the animation to begin with', + () => { + if (!getDOM().supportsWebAnimation()) return; + + const elm = el('
'); + document.body.appendChild(elm); + elm.style.color = 'rgb(0,0,0)'; + + const previousStyles = {color: 'red'}; + const previousPlayer = + new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []); + previousPlayer.play(); + previousPlayer.finish(); + + const player = new ExtendedWebAnimationsPlayer( + elm, [{opacity: '0'}, {opacity: '1'}], {duration: 1000}, [previousPlayer]); + + player.init(); + + const data = player.domPlayer.captures['trigger'][0]; + expect(data['keyframes']).toEqual([ + {opacity: '0', color: 'red'}, + {opacity: '1', color: 'rgb(0, 0, 0)'}, + ]); + }); + it('should properly calculate the previous styles for the player even when its currently playing', () => { if (!getDOM().supportsWebAnimation()) return;