diff --git a/modules/@angular/compiler/src/animation/animation_compiler.ts b/modules/@angular/compiler/src/animation/animation_compiler.ts
index c68ac228e8..0b91ac7004 100644
--- a/modules/@angular/compiler/src/animation/animation_compiler.ts
+++ b/modules/@angular/compiler/src/animation/animation_compiler.ts
@@ -41,7 +41,9 @@ const _ANIMATION_TIME_VAR = o.variable('totalTime');
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
-const EMPTY_MAP = o.literalMap([]);
+const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers');
+const _EMPTY_MAP = o.literalMap([]);
+const _EMPTY_ARRAY = o.literalArr([]);
class _AnimationBuilder implements AnimationAstVisitor {
private _fnVarName: string;
@@ -110,10 +112,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
_callAnimateMethod(
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
context: _AnimationBuilderContext) {
+ let previousStylesValue: o.Expression = _EMPTY_ARRAY;
+ if (context.isExpectingFirstAnimateStep) {
+ previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
+ context.isExpectingFirstAnimateStep = false;
+ }
context.totalTransitionTime += ast.duration + ast.delay;
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
- o.literal(ast.delay), o.literal(ast.easing)
+ o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
]);
}
@@ -150,6 +157,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.totalTransitionTime = 0;
context.isExpectingFirstStyleStep = true;
+ context.isExpectingFirstAnimateStep = true;
const stateChangePreconditions: o.Expression[] = [];
@@ -187,17 +195,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.stateMap.registerState(DEFAULT_STATE, {});
const statements: o.Statement[] = [];
- statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
- .callMethod(
- 'cancelActiveAnimation',
+ statements.push(_PREVIOUS_ANIMATION_PLAYERS
+ .set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
+ 'getAnimationPlayers',
[
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
- ])
- .toStmt());
+ ]))
+ .toDeclStmt());
-
- statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
+ statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
@@ -223,17 +230,6 @@ class _AnimationBuilder implements AnimationAstVisitor {
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
- // before we start any animation we want to clear out the starting
- // styles from the element's style property (since they were placed
- // there at the end of the last animation
- statements.push(RENDER_STYLES_FN
- .callFn([
- _ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
- o.importExpr(resolveIdentifier(Identifiers.clearStyles))
- .callFn([_ANIMATION_START_STATE_STYLES_VAR])
- ])
- .toStmt());
-
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
// this check ensures that the animation factory always returns a player
@@ -269,6 +265,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
])])
.toStmt());
+ statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
+ .instantiate([_PREVIOUS_ANIMATION_PLAYERS])
+ .callMethod('destroy', [])
+ .toStmt());
+
+ // before we start any animation we want to clear out the starting
+ // styles from the element's style property (since they were placed
+ // there at the end of the last animation
+ statements.push(RENDER_STYLES_FN
+ .callFn([
+ _ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
+ o.importExpr(resolveIdentifier(Identifiers.clearStyles))
+ .callFn([_ANIMATION_START_STATE_STYLES_VAR])
+ ])
+ .toStmt());
+
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
.callMethod(
'queueAnimation',
@@ -304,7 +316,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
const lookupMap: any[] = [];
Object.keys(context.stateMap.states).forEach(stateName => {
const value = context.stateMap.states[stateName];
- let variableValue = EMPTY_MAP;
+ let variableValue = _EMPTY_MAP;
if (isPresent(value)) {
const styleMap: any[] = [];
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
@@ -324,6 +336,7 @@ class _AnimationBuilderContext {
stateMap = new _AnimationBuilderStateMap();
endStateAnimateStep: AnimationStepAst = null;
isExpectingFirstStyleStep = false;
+ isExpectingFirstAnimateStep = false;
totalTransitionTime = 0;
}
diff --git a/modules/@angular/core/src/animation/animation_group_player.ts b/modules/@angular/core/src/animation/animation_group_player.ts
index a3a637ba60..97bef23b95 100644
--- a/modules/@angular/core/src/animation/animation_group_player.ts
+++ b/modules/@angular/core/src/animation/animation_group_player.ts
@@ -88,7 +88,7 @@ export class AnimationGroupPlayer implements AnimationPlayer {
this._started = false;
}
- setPosition(p: any /** TODO #9100 */): void {
+ setPosition(p: number): void {
this._players.forEach(player => { player.setPosition(p); });
}
@@ -100,4 +100,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
});
return min;
}
+
+ get players(): AnimationPlayer[] { return this._players; }
}
diff --git a/modules/@angular/core/src/animation/animation_player.ts b/modules/@angular/core/src/animation/animation_player.ts
index ed22477cd7..c086bf09fb 100644
--- a/modules/@angular/core/src/animation/animation_player.ts
+++ b/modules/@angular/core/src/animation/animation_player.ts
@@ -56,6 +56,6 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
finish(): void { this._onFinish(); }
destroy(): void {}
reset(): void {}
- setPosition(p: any /** TODO #9100 */): void {}
+ setPosition(p: number): void {}
getPosition(): number { return 0; }
}
diff --git a/modules/@angular/core/src/animation/animation_sequence_player.ts b/modules/@angular/core/src/animation/animation_sequence_player.ts
index 3c2e26e7af..c12e31e77b 100644
--- a/modules/@angular/core/src/animation/animation_sequence_player.ts
+++ b/modules/@angular/core/src/animation/animation_sequence_player.ts
@@ -104,7 +104,9 @@ export class AnimationSequencePlayer implements AnimationPlayer {
}
}
- setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); }
+ setPosition(p: number): void { this._players[0].setPosition(p); }
getPosition(): number { return this._players[0].getPosition(); }
+
+ get players(): AnimationPlayer[] { return this._players; }
}
diff --git a/modules/@angular/core/src/animation/animation_style_util.ts b/modules/@angular/core/src/animation/animation_style_util.ts
index ea97194bca..e426ce4547 100644
--- a/modules/@angular/core/src/animation/animation_style_util.ts
+++ b/modules/@angular/core/src/animation/animation_style_util.ts
@@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
}
+ collectAndResolveStyles(collectedStyles, [finalStateStyles]);
+
return keyframes;
}
diff --git a/modules/@angular/core/src/debug/debug_renderer.ts b/modules/@angular/core/src/debug/debug_renderer.ts
index 0fee0a25af..8350747696 100644
--- a/modules/@angular/core/src/debug/debug_renderer.ts
+++ b/modules/@angular/core/src/debug/debug_renderer.ts
@@ -22,7 +22,7 @@ export class DebugDomRootRenderer implements RootRenderer {
}
}
-export class DebugDomRenderer implements Renderer {
+export class DebugDomRenderer {
constructor(private _delegate: Renderer) {}
selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any {
@@ -150,7 +150,9 @@ export class DebugDomRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer {
- return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
+ return this._delegate.animate(
+ element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}
diff --git a/modules/@angular/core/src/linker/animation_view_context.ts b/modules/@angular/core/src/linker/animation_view_context.ts
index c690f31181..77a294e792 100644
--- a/modules/@angular/core/src/linker/animation_view_context.ts
+++ b/modules/@angular/core/src/linker/animation_view_context.ts
@@ -8,8 +8,9 @@
import {AnimationGroupPlayer} from '../animation/animation_group_player';
import {AnimationPlayer} from '../animation/animation_player';
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
-import {AnimationTransitionEvent} from '../animation/animation_transition_event';
+import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
import {ViewAnimationMap} from '../animation/view_animation_map';
+import {ListWrapper} from '../facade/collection';
export class AnimationViewContext {
private _players = new ViewAnimationMap();
@@ -30,15 +31,26 @@ export class AnimationViewContext {
this._players.set(element, animationName, player);
}
- cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false):
- void {
+ getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
+ AnimationPlayer[] {
+ const players: AnimationPlayer[] = [];
if (removeAllAnimations) {
- this._players.findAllPlayersByElement(element).forEach(player => player.destroy());
+ this._players.findAllPlayersByElement(element).forEach(
+ player => { _recursePlayers(player, players); });
} else {
- const player = this._players.find(element, animationName);
- if (player) {
- player.destroy();
+ const currentPlayer = this._players.find(element, animationName);
+ if (currentPlayer) {
+ _recursePlayers(currentPlayer, players);
}
}
+ return players;
+ }
+}
+
+function _recursePlayers(player: AnimationPlayer, collectedPlayers: AnimationPlayer[]) {
+ if ((player instanceof AnimationGroupPlayer) || (player instanceof AnimationSequencePlayer)) {
+ player.players.forEach(player => _recursePlayers(player, collectedPlayers));
+ } else {
+ collectedPlayers.push(player);
}
}
diff --git a/modules/@angular/core/src/render/api.ts b/modules/@angular/core/src/render/api.ts
index baffd7a372..cb70bdb3c9 100644
--- a/modules/@angular/core/src/render/api.ts
+++ b/modules/@angular/core/src/render/api.ts
@@ -88,7 +88,8 @@ export abstract class Renderer {
abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer;
+ duration: number, delay: number, easing: string,
+ previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}
/**
diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts
index 2908365702..bfd13c9964 100644
--- a/modules/@angular/core/test/animation/animation_integration_spec.ts
+++ b/modules/@angular/core/test/animation/animation_integration_spec.ts
@@ -1854,6 +1854,8 @@ function declareTests({useJit}: {useJit: boolean}) {
let animation = driver.log.pop();
let kf = animation['keyframeLookup'];
expect(kf[1]).toEqual([1, {'background': 'green'}]);
+ let player = animation['player'];
+ player.finish();
cmp.exp = 'blue';
fixture.detectChanges();
@@ -1863,6 +1865,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'green'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
+ player = animation['player'];
+ player.finish();
cmp.exp = 'red';
fixture.detectChanges();
@@ -1872,6 +1876,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'grey'}]);
expect(kf[1]).toEqual([1, {'background': 'red'}]);
+ player = animation['player'];
+ player.finish();
cmp.exp = 'orange';
fixture.detectChanges();
@@ -1881,6 +1887,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'red'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
+ player = animation['player'];
+ player.finish();
}));
it('should seed in the origin animation state styles into the first animation step',
@@ -1911,6 +1919,44 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(animation['startingStyles']).toEqual({'height': '100px'});
}));
+ it('should seed in the previous animation styles into the transition if the previous transition was interupted midway',
+ fakeAsync(() => {
+ TestBed.overrideComponent(DummyIfCmp, {
+ set: {
+ template: `
+
+ `,
+ animations: [trigger(
+ 'status',
+ [
+ state('*', style({ opacity: 0 })),
+ state('a', style({height: '100px', width: '200px'})),
+ state('b', style({height: '1000px' })),
+ transition('* => *', [
+ animate(1000, style({ fontSize: '20px' })),
+ animate(1000)
+ ])
+ ])]
+ }
+ });
+
+ const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
+ const fixture = TestBed.createComponent(DummyIfCmp);
+ const cmp = fixture.componentInstance;
+
+ cmp.exp = 'a';
+ fixture.detectChanges();
+ flushMicrotasks();
+ driver.log = [];
+
+ cmp.exp = 'b';
+ fixture.detectChanges();
+ flushMicrotasks();
+
+ const animation = driver.log[0];
+ expect(animation['previousStyles']).toEqual({opacity: '0', fontSize: '*'});
+ }));
+
it('should perform a state change even if there is no transition that is found',
fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, {
diff --git a/modules/@angular/core/testing/mock_animation_player.ts b/modules/@angular/core/testing/mock_animation_player.ts
index 7eb777ca61..c16a00e661 100644
--- a/modules/@angular/core/testing/mock_animation_player.ts
+++ b/modules/@angular/core/testing/mock_animation_player.ts
@@ -5,8 +5,7 @@
* 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 {AnimationPlayer} from '@angular/core';
+import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
export class MockAnimationPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = [];
@@ -16,8 +15,21 @@ export class MockAnimationPlayer implements AnimationPlayer {
private _started = false;
public parentPlayer: AnimationPlayer = null;
+ public previousStyles: {[styleName: string]: string | number} = {};
- public log: any[] /** TODO #9100 */ = [];
+ public log: any[] = [];
+
+ constructor(
+ public startingStyles: {[key: string]: string | number} = {},
+ public keyframes: Array<[number, {[style: string]: string | number}]> = [],
+ previousPlayers: AnimationPlayer[] = []) {
+ previousPlayers.forEach(player => {
+ if (player instanceof MockAnimationPlayer) {
+ const styles = player._captureStyles();
+ Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
+ }
+ });
+ }
private _onFinish(): void {
if (!this._finished) {
@@ -67,6 +79,32 @@ export class MockAnimationPlayer implements AnimationPlayer {
}
}
- setPosition(p: any /** TODO #9100 */): void {}
+ setPosition(p: number): void {}
getPosition(): number { return 0; }
+
+ private _captureStyles(): {[styleName: string]: string | number} {
+ const captures: {[prop: string]: string | number} = {};
+
+ if (this.hasStarted()) {
+ // when assembling the captured styles, it's important that
+ // we build the keyframe styles in the following order:
+ // {startingStyles, ... other styles within keyframes, ... previousStyles }
+ Object.keys(this.startingStyles).forEach(prop => {
+ captures[prop] = this.startingStyles[prop];
+ });
+
+ this.keyframes.forEach(kf => {
+ const [offset, styles] = kf;
+ const newStyles: {[prop: string]: string | number} = {};
+ Object.keys(styles).forEach(
+ prop => { captures[prop] = this._finished ? styles[prop] : AUTO_STYLE; });
+ });
+ }
+
+ Object.keys(this.previousStyles).forEach(prop => {
+ captures[prop] = this.previousStyles[prop];
+ });
+
+ return captures;
+ }
}
diff --git a/modules/@angular/platform-browser/src/dom/animation_driver.ts b/modules/@angular/platform-browser/src/dom/animation_driver.ts
index 915c9a884d..7967a08eb9 100644
--- a/modules/@angular/platform-browser/src/dom/animation_driver.ts
+++ b/modules/@angular/platform-browser/src/dom/animation_driver.ts
@@ -13,7 +13,8 @@ import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../privat
class _NoOpAnimationDriver implements AnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer {
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return new NoOpAnimationPlayer();
}
}
@@ -25,5 +26,6 @@ export abstract class AnimationDriver {
static NOOP: AnimationDriver = new _NoOpAnimationDriver();
abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer;
+ duration: number, delay: number, easing: string,
+ previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}
diff --git a/modules/@angular/platform-browser/src/dom/dom_renderer.ts b/modules/@angular/platform-browser/src/dom/dom_renderer.ts
index bb96fbb7e1..bf64552c87 100644
--- a/modules/@angular/platform-browser/src/dom/dom_renderer.ts
+++ b/modules/@angular/platform-browser/src/dom/dom_renderer.ts
@@ -260,9 +260,10 @@ export class DomRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer {
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._animationDriver.animate(
- element, startingStyles, keyframes, duration, delay, easing);
+ element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}
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 57f6e004e7..7486333667 100644
--- a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts
+++ b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {AnimationPlayer} from '@angular/core';
import {isPresent} from '../facade/lang';
import {AnimationKeyframe, AnimationStyles} from '../private_import_core';
@@ -15,17 +16,18 @@ import {WebAnimationsPlayer} from './web_animations_player';
export class WebAnimationsDriver implements AnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): WebAnimationsPlayer {
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
let formattedSteps: {[key: string]: string | number}[] = [];
let startingStyleLookup: {[key: string]: string | number} = {};
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
- startingStyleLookup = _populateStyles(element, startingStyles, {});
+ startingStyleLookup = _populateStyles(startingStyles, {});
startingStyleLookup['offset'] = 0;
formattedSteps.push(startingStyleLookup);
}
keyframes.forEach((keyframe: AnimationKeyframe) => {
- const data = _populateStyles(element, keyframe.styles, startingStyleLookup);
+ const data = _populateStyles(keyframe.styles, startingStyleLookup);
data['offset'] = keyframe.offset;
formattedSteps.push(data);
});
@@ -52,13 +54,13 @@ export class WebAnimationsDriver implements AnimationDriver {
playerOptions['easing'] = easing;
}
- return new WebAnimationsPlayer(element, formattedSteps, playerOptions);
+ return new WebAnimationsPlayer(
+ element, formattedSteps, playerOptions, previousPlayers);
}
}
-function _populateStyles(
- element: any, styles: AnimationStyles,
- defaultStyles: {[key: string]: string | number}): {[key: string]: string | number} {
+function _populateStyles(styles: AnimationStyles, defaultStyles: {[key: string]: string | number}):
+ {[key: string]: string | number} {
const data: {[key: string]: string | number} = {};
styles.styles.forEach(
(entry) => { Object.keys(entry).forEach(prop => { data[prop] = entry[prop]; }); });
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 e57c82d145..627a8055c6 100644
--- a/modules/@angular/platform-browser/src/dom/web_animations_player.ts
+++ b/modules/@angular/platform-browser/src/dom/web_animations_player.ts
@@ -7,6 +7,8 @@
*/
import {AUTO_STYLE} from '@angular/core';
+
+import {isPresent} from '../facade/lang';
import {AnimationPlayer} from '../private_import_core';
import {getDOM} from './dom_adapter';
@@ -21,13 +23,22 @@ export class WebAnimationsPlayer implements AnimationPlayer {
private _finished = false;
private _started = false;
private _destroyed = false;
+ private _finalKeyframe: {[key: string]: string | number};
public parentPlayer: AnimationPlayer = null;
+ public previousStyles: {[styleName: string]: string | number};
constructor(
public element: any, public keyframes: {[key: string]: string | number}[],
- public options: {[key: string]: string | number}) {
+ public options: {[key: string]: string | number},
+ previousPlayers: WebAnimationsPlayer[] = []) {
this._duration = options['duration'];
+
+ this.previousStyles = {};
+ previousPlayers.forEach(player => {
+ let styles = player._captureStyles();
+ Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
+ });
}
private _onFinish() {
@@ -44,14 +55,30 @@ export class WebAnimationsPlayer implements AnimationPlayer {
const keyframes = this.keyframes.map(styles => {
const formattedKeyframe: {[key: string]: string | number} = {};
- Object.keys(styles).forEach(prop => {
- const value = styles[prop];
- formattedKeyframe[prop] = value == AUTO_STYLE ? _computeStyle(this.element, prop) : value;
+ Object.keys(styles).forEach((prop, index) => {
+ let value = styles[prop];
+ if (value == AUTO_STYLE) {
+ value = _computeStyle(this.element, prop);
+ }
+ if (value != undefined) {
+ formattedKeyframe[prop] = value;
+ }
});
return formattedKeyframe;
});
+ const previousStyleProps = Object.keys(this.previousStyles);
+ if (previousStyleProps.length) {
+ let startingKeyframe = findStartingKeyframe(keyframes);
+ previousStyleProps.forEach(prop => {
+ if (isPresent(startingKeyframe[prop])) {
+ startingKeyframe[prop] = this.previousStyles[prop];
+ }
+ });
+ }
+
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
+ this._finalKeyframe = _copyKeyframeStyles(keyframes[keyframes.length - 1]);
// this is required so that the player doesn't start to animate right away
this._resetDomPlayerState();
@@ -119,8 +146,47 @@ export class WebAnimationsPlayer implements AnimationPlayer {
setPosition(p: number): void { this._player.currentTime = p * this.totalTime; }
getPosition(): number { return this._player.currentTime / this.totalTime; }
+
+ private _captureStyles(): {[prop: string]: string | number} {
+ const styles: {[key: string]: string | number} = {};
+ if (this.hasStarted()) {
+ Object.keys(this._finalKeyframe).forEach(prop => {
+ if (prop != 'offset') {
+ styles[prop] =
+ this._finished ? this._finalKeyframe[prop] : _computeStyle(this.element, prop);
+ }
+ });
+ }
+
+ return styles;
+ }
}
function _computeStyle(element: any, prop: string): string {
return getDOM().getComputedStyle(element)[prop];
}
+
+function _copyKeyframeStyles(styles: {[style: string]: string | number}):
+ {[style: string]: string | number} {
+ const newStyles: {[style: string]: string | number} = {};
+ Object.keys(styles).forEach(prop => {
+ if (prop != 'offset') {
+ newStyles[prop] = styles[prop];
+ }
+ });
+ 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 e0505c631f..a608fe54fb 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
@@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
@@ -48,8 +47,7 @@ export function main() {
it('should use a fill mode of `both`', () => {
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
-
- const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear');
+ const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear', []);
const details = _formatOptions(player);
const options = details['options'];
expect(options['fill']).toEqual('both');
@@ -58,8 +56,7 @@ export function main() {
it('should apply the provided easing', () => {
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
-
- const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out');
+ const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out', []);
const details = _formatOptions(player);
const options = details['options'];
expect(options['easing']).toEqual('ease-out');
@@ -68,8 +65,7 @@ export function main() {
it('should only apply the provided easing if present', () => {
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
-
- const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null);
+ const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null, []);
const details = _formatOptions(player);
const options = details['options'];
const keys = Object.keys(options);
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 48b427781d..8072279501 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
@@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {MockAnimationPlayer, beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
+import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
+import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
+import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
@@ -18,14 +20,16 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
constructor(
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
- public options: {[key: string]: string | number}) {
- super(element, keyframes, options);
+ public options: {[key: string]: string | number},
+ public previousPlayers: WebAnimationsPlayer[] = []) {
+ super(element, keyframes, options, previousPlayers);
}
get domPlayer() { return this._overriddenDomPlayer; }
/** @internal */
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
+ this._overriddenDomPlayer._capture('trigger', {elm, keyframes, options});
return this._overriddenDomPlayer;
}
}
@@ -33,7 +37,7 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
export function main() {
function makePlayer(): {[key: string]: any} {
const someElm = el('');
- const player = new ExtendedWebAnimationsPlayer(someElm, [], {});
+ const player = new ExtendedWebAnimationsPlayer(someElm, [{}, {}], {}, []);
player.init();
return {'captures': player.domPlayer.captures, 'player': player};
}
@@ -156,5 +160,72 @@ export function main() {
player.destroy();
expect(captures['cancel'].length).toBe(1);
});
+
+ it('should resolve auto styles based on what is computed from the provided element', () => {
+ const elm = el('');
+ document.body.appendChild(elm); // required for getComputedStyle() to work
+ elm.style.opacity = '0.5';
+
+ const player = new ExtendedWebAnimationsPlayer(
+ elm, [{opacity: AUTO_STYLE}, {opacity: '1'}], {duration: 1000}, []);
+
+ player.init();
+
+ const data = player.domPlayer.captures['trigger'][0];
+ expect(data['keyframes']).toEqual([{opacity: '0.5'}, {opacity: '1'}]);
+ });
+
+ describe('previousStyle', () => {
+ it('should merge keyframe styles based on the previous styles passed in when the player has finished its operation',
+ () => {
+ const elm = el('');
+ const previousStyles = {width: '100px', height: '666px'};
+ const previousPlayer =
+ new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
+ previousPlayer.play();
+ previousPlayer.finish();
+
+ const player = new ExtendedWebAnimationsPlayer(
+ elm,
+ [
+ {width: '0px', height: '0px', opacity: 0, offset: 0},
+ {width: '0px', height: '0px', opacity: 1, offset: 1}
+ ],
+ {duration: 1000}, [previousPlayer]);
+
+ player.init();
+
+ const data = player.domPlayer.captures['trigger'][0];
+ expect(data['keyframes']).toEqual([
+ {width: '100px', height: '666px', opacity: 0, offset: 0},
+ {width: '0px', height: '0px', opacity: 1, offset: 1}
+ ]);
+ });
+
+ it('should properly calculate the previous styles for the player even when its currently playing',
+ () => {
+ if (!getDOM().supportsWebAnimation()) return;
+
+ const elm = el('');
+ document.body.appendChild(elm);
+
+ const fromStyles = {width: '100px', height: '666px'};
+ const toStyles = {width: '50px', height: '333px'};
+ const previousPlayer =
+ new WebAnimationsPlayer(elm, [fromStyles, toStyles], {duration: 1000}, []);
+ previousPlayer.play();
+ previousPlayer.setPosition(0.5);
+ previousPlayer.pause();
+
+ const newStyles = {width: '0px', height: '0px'};
+ const player = new WebAnimationsPlayer(
+ elm, [newStyles, newStyles], {duration: 1000}, [previousPlayer]);
+
+ player.init();
+
+ const data = player.previousStyles;
+ expect(player.previousStyles).toEqual({width: '75px', height: '499.5px'});
+ });
+ });
});
}
diff --git a/modules/@angular/platform-browser/testing/mock_animation_driver.ts b/modules/@angular/platform-browser/testing/mock_animation_driver.ts
index 91c4129957..50f5e86a01 100644
--- a/modules/@angular/platform-browser/testing/mock_animation_driver.ts
+++ b/modules/@angular/platform-browser/testing/mock_animation_driver.ts
@@ -9,19 +9,29 @@
import {AnimationPlayer} from '@angular/core';
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
import {AnimationDriver} from '@angular/platform-browser';
+
+import {ListWrapper} from './facade/collection';
import {AnimationKeyframe, AnimationStyles} from './private_import_core';
export class MockAnimationDriver extends AnimationDriver {
public log: {[key: string]: any}[] = [];
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer {
- const player = new MockAnimationPlayer();
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
+ const mockPlayers = previousPlayers.filter(
+ player => player instanceof MockAnimationPlayer);
+ const normalizedStartingStyles = _serializeStyles(startingStyles);
+ const normalizedKeyframes = _serializeKeyframes(keyframes);
+ const player =
+ new MockAnimationPlayer(normalizedStartingStyles, normalizedKeyframes, previousPlayers);
+
this.log.push({
'element': element,
- 'startingStyles': _serializeStyles(startingStyles),
+ 'startingStyles': normalizedStartingStyles,
+ 'previousStyles': player.previousStyles,
'keyframes': keyframes,
- 'keyframeLookup': _serializeKeyframes(keyframes),
+ 'keyframeLookup': normalizedKeyframes,
'duration': duration,
'delay': delay,
'easing': easing,
diff --git a/modules/@angular/platform-server/src/server_renderer.ts b/modules/@angular/platform-server/src/server_renderer.ts
index 265c4c7b3d..5a09ff65c4 100644
--- a/modules/@angular/platform-server/src/server_renderer.ts
+++ b/modules/@angular/platform-server/src/server_renderer.ts
@@ -206,9 +206,10 @@ export class ServerRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer {
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._animationDriver.animate(
- element, startingStyles, keyframes, duration, delay, easing);
+ element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}
diff --git a/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts b/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts
index 5561062dd1..31f76473fd 100644
--- a/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts
+++ b/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts
@@ -89,7 +89,7 @@ export class MessageBasedRenderer {
'animate',
[
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
- PRIMITIVE, PRIMITIVE
+ PRIMITIVE, PRIMITIVE, PRIMITIVE
],
this._animate.bind(this));
@@ -248,8 +248,14 @@ export class MessageBasedRenderer {
private _animate(
renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number,
- delay: number, easing: string, playerId: any) {
- const player = renderer.animate(element, startingStyles, keyframes, duration, delay, easing);
+ delay: number, easing: string, previousPlayers: number[], playerId: any) {
+ let normalizedPreviousPlayers: AnimationPlayer[];
+ if (previousPlayers && previousPlayers.length) {
+ normalizedPreviousPlayers =
+ previousPlayers.map(playerId => this._renderStore.deserialize(playerId));
+ }
+ const player = renderer.animate(
+ element, startingStyles, keyframes, duration, delay, easing, normalizedPreviousPlayers);
this._renderStore.store(player, playerId);
}
diff --git a/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts b/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts
index 63ecca3ea6..314f196f2f 100644
--- a/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts
+++ b/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts
@@ -16,6 +16,7 @@ import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
+
import {deserializeGenericEvent} from './event_deserializer';
@Injectable()
@@ -239,13 +240,16 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
animate(
renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
- duration: number, delay: number, easing: string): AnimationPlayer {
+ duration: number, delay: number, easing: string,
+ previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
const playerId = this._rootRenderer.allocateId();
+ const previousPlayerIds: number[] =
+ previousPlayers.map(player => this._rootRenderer.renderStore.serialize(player));
this._runOnService('animate', [
new FnArg(renderElement, RenderStoreObject), new FnArg(startingStyles, null),
new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null),
- new FnArg(easing, null), new FnArg(playerId, null)
+ new FnArg(easing, null), new FnArg(previousPlayerIds, null), new FnArg(playerId, null)
]);
const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement);
@@ -325,7 +329,7 @@ export class WebWorkerRenderNode {
animationPlayerEvents = new AnimationPlayerEmitter();
}
-class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject {
+class _AnimationWorkerRendererPlayer implements RenderStoreObject {
public parentPlayer: AnimationPlayer = null;
private _destroyed: boolean = false;
diff --git a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts
index 52c2d916be..418c497713 100644
--- a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts
+++ b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_animation_integration_spec.ts
@@ -289,6 +289,30 @@ export function main() {
expect(player.log.indexOf('destroy') >= 0).toBe(true);
}));
+
+ it('should properly transition to the next animation if the current one is cancelled',
+ fakeAsync(() => {
+ const fixture = TestBed.createComponent(AnimationCmp);
+ const cmp = fixture.componentInstance;
+
+ cmp.state = 'on';
+ fixture.detectChanges();
+ flushMicrotasks();
+
+ let player = uiDriver.log.shift()['player'];
+ player.finish();
+ player = uiDriver.log.shift()['player'];
+ player.setPosition(0.5);
+
+ uiDriver.log = [];
+
+ cmp.state = 'off';
+ fixture.detectChanges();
+ flushMicrotasks();
+
+ const step = uiDriver.log.shift();
+ expect(step['previousStyles']).toEqual({opacity: AUTO_STYLE, fontSize: AUTO_STYLE});
+ }));
});
}
diff --git a/modules/playground/e2e_test/web_workers/animations/animations_spec.ts b/modules/playground/e2e_test/web_workers/animations/animations_spec.ts
index 681cec60ce..f0fc711344 100644
--- a/modules/playground/e2e_test/web_workers/animations/animations_spec.ts
+++ b/modules/playground/e2e_test/web_workers/animations/animations_spec.ts
@@ -40,6 +40,29 @@ describe('WebWorkers Animations', function() {
browser.wait(() => boxElm.getSize().then(sizes => sizes['width'] > 750), 1000);
});
+ it('should cancel the animation midway and continue from where it left off', () => {
+ browser.ignoreSynchronization = true;
+ browser.get(URL);
+
+ waitForBootstrap();
+
+ const elem = element(by.css(selector + ' .box'));
+ const btn = element(by.css(selector + ' button'));
+ const getWidth = () => elem.getSize().then((sizes: any) => sizes['width']);
+
+ btn.click();
+
+ browser.sleep(250);
+
+ btn.click();
+
+ expect(getWidth()).toBeLessThan(600);
+
+ browser.sleep(500);
+
+ expect(getWidth()).toBeLessThan(50);
+ });
+
function waitForBootstrap() {
browser.wait(protractor.until.elementLocated(by.css(selector + ' .box')), 5000)
.then(() => {}, () => {
diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts
index 82641226ca..3c3f95b1b2 100644
--- a/tools/public_api_guard/core/index.d.ts
+++ b/tools/public_api_guard/core/index.d.ts
@@ -756,7 +756,7 @@ export declare class RenderComponentType {
/** @experimental */
export declare abstract class Renderer {
- abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer;
+ abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string, previousPlayers?: AnimationPlayer[]): AnimationPlayer;
abstract attachViewAfter(node: any, viewRootNodes: any[]): void;
abstract createElement(parentElement: any, name: string, debugInfo?: RenderDebugInfo): any;
abstract createTemplateAnchor(parentElement: any, debugInfo?: RenderDebugInfo): any;
diff --git a/tools/public_api_guard/platform-browser/index.d.ts b/tools/public_api_guard/platform-browser/index.d.ts
index 919937f148..e2557aa4a5 100644
--- a/tools/public_api_guard/platform-browser/index.d.ts
+++ b/tools/public_api_guard/platform-browser/index.d.ts
@@ -1,6 +1,6 @@
/** @experimental */
export declare abstract class AnimationDriver {
- abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer;
+ abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string, previousPlayers?: AnimationPlayer[]): AnimationPlayer;
static NOOP: AnimationDriver;
}