fix(animations): retain styling when transition destinations are changed (#12208)

Closes #9661
Closes #12208
This commit is contained in:
Matias Niemelä
2016-11-14 16:59:06 -08:00
committed by Victor Berchet
parent 46023e4792
commit 9de76ebfa5
24 changed files with 403 additions and 79 deletions

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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, <WebAnimationsPlayer[]>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]; }); });

View File

@ -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 = <number>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;
}