refactor(animations): multiple group animations should tally-up delays correctly
This commit is contained in:
parent
88e3d7af9f
commit
ba17dcbf2b
|
@ -5,16 +5,13 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AnimationPlayer, AnimationStyles} from '@angular/core';
|
import {AnimationStyles} from '@angular/core';
|
||||||
|
|
||||||
import {StyleData} from '../common/style_data';
|
import {StyleData} from '../common/style_data';
|
||||||
import {copyStyles, normalizeStyles, parseTimeExpression} from '../common/util';
|
import {copyStyles, normalizeStyles, parseTimeExpression} from '../common/util';
|
||||||
|
|
||||||
import {AnimationDslVisitor, visitAnimationNode} from './animation_dsl_visitor';
|
import {AnimationDslVisitor, visitAnimationNode} from './animation_dsl_visitor';
|
||||||
import * as meta from './animation_metadata';
|
import * as meta from './animation_metadata';
|
||||||
import {AnimationTimelineInstruction, createTimelineInstruction} from './animation_timeline_instruction';
|
import {AnimationTimelineInstruction, createTimelineInstruction} from './animation_timeline_instruction';
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The code within this file aims to generate web-animations-compatible keyframes from Angular's
|
* The code within this file aims to generate web-animations-compatible keyframes from Angular's
|
||||||
* animation DSL code.
|
* animation DSL code.
|
||||||
|
@ -134,19 +131,13 @@ export class AnimationTimelineContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
transformIntoNewTimeline(newTime = 0) {
|
transformIntoNewTimeline(newTime = 0) {
|
||||||
const oldTimeline = this.currentTimeline;
|
this.currentTimeline = this.currentTimeline.fork(newTime);
|
||||||
const oldTime = oldTimeline.time;
|
|
||||||
if (newTime > 0) {
|
|
||||||
oldTimeline.time = newTime;
|
|
||||||
}
|
|
||||||
this.currentTimeline = oldTimeline.fork();
|
|
||||||
oldTimeline.time = oldTime;
|
|
||||||
this.timelines.push(this.currentTimeline);
|
this.timelines.push(this.currentTimeline);
|
||||||
return this.currentTimeline;
|
return this.currentTimeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementTime(time: number) {
|
incrementTime(time: number) {
|
||||||
this.currentTimeline.forwardTime(this.currentTimeline.time + time);
|
this.currentTimeline.forwardTime(this.currentTimeline.duration + time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +192,8 @@ export class AnimationTimelineVisitor implements AnimationDslVisitor {
|
||||||
context.currentTimeline.forwardFrame();
|
context.currentTimeline.forwardFrame();
|
||||||
context.currentTimeline.snapshotCurrentStyles();
|
context.currentTimeline.snapshotCurrentStyles();
|
||||||
}
|
}
|
||||||
ast.steps.map(s => visitAnimationNode(this, s, context));
|
|
||||||
|
ast.steps.forEach(s => visitAnimationNode(this, s, context));
|
||||||
|
|
||||||
// this means that some animation function within the sequence
|
// this means that some animation function within the sequence
|
||||||
// ended up creating a sub timeline (which means the current
|
// ended up creating a sub timeline (which means the current
|
||||||
|
@ -216,7 +208,7 @@ export class AnimationTimelineVisitor implements AnimationDslVisitor {
|
||||||
visitGroup(ast: meta.AnimationGroupMetadata, context: AnimationTimelineContext) {
|
visitGroup(ast: meta.AnimationGroupMetadata, context: AnimationTimelineContext) {
|
||||||
const innerTimelines: TimelineBuilder[] = [];
|
const innerTimelines: TimelineBuilder[] = [];
|
||||||
let furthestTime = context.currentTimeline.currentTime;
|
let furthestTime = context.currentTimeline.currentTime;
|
||||||
ast.steps.map(s => {
|
ast.steps.forEach(s => {
|
||||||
const innerContext = context.createSubContext();
|
const innerContext = context.createSubContext();
|
||||||
visitAnimationNode(this, s, innerContext);
|
visitAnimationNode(this, s, innerContext);
|
||||||
furthestTime = Math.max(furthestTime, innerContext.currentTimeline.currentTime);
|
furthestTime = Math.max(furthestTime, innerContext.currentTimeline.currentTime);
|
||||||
|
@ -289,16 +281,17 @@ export class AnimationTimelineVisitor implements AnimationDslVisitor {
|
||||||
offsetGap = MAX_KEYFRAME_OFFSET / limit;
|
offsetGap = MAX_KEYFRAME_OFFSET / limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyframeDuration = context.currentAnimateTimings.duration;
|
const startTime = context.currentTimeline.duration;
|
||||||
|
const duration = context.currentAnimateTimings.duration;
|
||||||
const innerContext = context.createSubContext();
|
const innerContext = context.createSubContext();
|
||||||
const innerTimeline = innerContext.currentTimeline;
|
const innerTimeline = innerContext.currentTimeline;
|
||||||
innerTimeline.easing = context.currentAnimateTimings.easing;
|
innerTimeline.easing = context.currentAnimateTimings.easing;
|
||||||
|
|
||||||
ast.steps.map((step: meta.AnimationStyleMetadata, i: number) => {
|
ast.steps.forEach((step: meta.AnimationStyleMetadata, i: number) => {
|
||||||
const normalizedStyles = normalizeStyles(new AnimationStyles(step.styles));
|
const normalizedStyles = normalizeStyles(new AnimationStyles(step.styles));
|
||||||
const offset = containsOffsets ? <number>normalizedStyles['offset'] :
|
const offset = containsOffsets ? <number>normalizedStyles['offset'] :
|
||||||
(i == limit ? MAX_KEYFRAME_OFFSET : i * offsetGap);
|
(i == limit ? MAX_KEYFRAME_OFFSET : i * offsetGap);
|
||||||
innerTimeline.forwardTime(offset * keyframeDuration);
|
innerTimeline.forwardTime(offset * duration);
|
||||||
innerTimeline.setStyles(normalizedStyles);
|
innerTimeline.setStyles(normalizedStyles);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -308,13 +301,13 @@ export class AnimationTimelineVisitor implements AnimationDslVisitor {
|
||||||
|
|
||||||
// we do this because the window between this timeline and the sub timeline
|
// we do this because the window between this timeline and the sub timeline
|
||||||
// should ensure that the styles within are exactly the same as they were before
|
// should ensure that the styles within are exactly the same as they were before
|
||||||
context.transformIntoNewTimeline(context.currentTimeline.time + keyframeDuration);
|
context.transformIntoNewTimeline(startTime + duration);
|
||||||
context.previousNode = ast;
|
context.previousNode = ast;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimelineBuilder {
|
export class TimelineBuilder {
|
||||||
public time: number = 0;
|
public duration: number = 0;
|
||||||
public easing: string = '';
|
public easing: string = '';
|
||||||
private _currentKeyframe: StyleData;
|
private _currentKeyframe: StyleData;
|
||||||
private _keyframes = new Map<number, StyleData>();
|
private _keyframes = new Map<number, StyleData>();
|
||||||
|
@ -332,27 +325,27 @@ export class TimelineBuilder {
|
||||||
|
|
||||||
hasStyling(): boolean { return this._keyframes.size > 1; }
|
hasStyling(): boolean { return this._keyframes.size > 1; }
|
||||||
|
|
||||||
get currentTime() { return this.startTime + this.time; }
|
get currentTime() { return this.startTime + this.duration; }
|
||||||
|
|
||||||
fork(): TimelineBuilder {
|
fork(currentTime = 0): TimelineBuilder {
|
||||||
return new TimelineBuilder(this.currentTime, this._globalTimelineStyles);
|
return new TimelineBuilder(currentTime || this.currentTime, this._globalTimelineStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _loadKeyframe() {
|
private _loadKeyframe() {
|
||||||
this._currentKeyframe = this._keyframes.get(this.time);
|
this._currentKeyframe = this._keyframes.get(this.duration);
|
||||||
if (!this._currentKeyframe) {
|
if (!this._currentKeyframe) {
|
||||||
this._currentKeyframe = Object.create(this._backFill, {});
|
this._currentKeyframe = Object.create(this._backFill, {});
|
||||||
this._keyframes.set(this.time, this._currentKeyframe);
|
this._keyframes.set(this.duration, this._currentKeyframe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardFrame() {
|
forwardFrame() {
|
||||||
this.time++;
|
this.duration++;
|
||||||
this._loadKeyframe();
|
this._loadKeyframe();
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardTime(time: number) {
|
forwardTime(time: number) {
|
||||||
this.time = time;
|
this.duration = time;
|
||||||
this._loadKeyframe();
|
this._loadKeyframe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +377,7 @@ export class TimelineBuilder {
|
||||||
|
|
||||||
snapshotCurrentStyles() { copyStyles(this._localTimelineStyles, false, this._currentKeyframe); }
|
snapshotCurrentStyles() { copyStyles(this._localTimelineStyles, false, this._currentKeyframe); }
|
||||||
|
|
||||||
getFinalKeyframe() { return this._keyframes.get(this.time); }
|
getFinalKeyframe() { return this._keyframes.get(this.duration); }
|
||||||
|
|
||||||
get properties() {
|
get properties() {
|
||||||
const properties: string[] = [];
|
const properties: string[] = [];
|
||||||
|
@ -408,7 +401,7 @@ export class TimelineBuilder {
|
||||||
const finalKeyframes: StyleData[] = [];
|
const finalKeyframes: StyleData[] = [];
|
||||||
// special case for when there are only start/destination
|
// special case for when there are only start/destination
|
||||||
// styles but no actual animation animate steps...
|
// styles but no actual animation animate steps...
|
||||||
if (this.time == 0) {
|
if (this.duration == 0) {
|
||||||
const targetKeyframe = this.getFinalKeyframe();
|
const targetKeyframe = this.getFinalKeyframe();
|
||||||
|
|
||||||
const firstKeyframe = copyStyles(targetKeyframe, true);
|
const firstKeyframe = copyStyles(targetKeyframe, true);
|
||||||
|
@ -421,11 +414,11 @@ export class TimelineBuilder {
|
||||||
} else {
|
} else {
|
||||||
this._keyframes.forEach((keyframe, time) => {
|
this._keyframes.forEach((keyframe, time) => {
|
||||||
const finalKeyframe = copyStyles(keyframe, true);
|
const finalKeyframe = copyStyles(keyframe, true);
|
||||||
finalKeyframe['offset'] = time / this.time;
|
finalKeyframe['offset'] = time / this.duration;
|
||||||
finalKeyframes.push(finalKeyframe);
|
finalKeyframes.push(finalKeyframe);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return createTimelineInstruction(finalKeyframes, this.time, this.startTime, this.easing);
|
return createTimelineInstruction(finalKeyframes, this.duration, this.startTime, this.easing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -465,6 +465,24 @@ export function main() {
|
||||||
{width: 200, height: 200, offset: 1},
|
{width: 200, height: 200, offset: 1},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect delays after multiple calls to group()', () => {
|
||||||
|
const steps = [
|
||||||
|
group([animate('2s', style({opacity: 1})), animate('2s', style({width: '100px'}))]),
|
||||||
|
animate(2000, style({width: 0, opacity: 0})),
|
||||||
|
group([animate('2s', style({opacity: 1})), animate('2s', style({width: '200px'}))]),
|
||||||
|
animate(2000, style({width: 0, opacity: 0}))
|
||||||
|
];
|
||||||
|
|
||||||
|
const players = invokeAnimationSequence(steps);
|
||||||
|
const middlePlayer = players[2];
|
||||||
|
expect(middlePlayer.delay).toEqual(2000);
|
||||||
|
expect(middlePlayer.duration).toEqual(2000);
|
||||||
|
|
||||||
|
const finalPlayer = players[players.length - 1];
|
||||||
|
expect(finalPlayer.delay).toEqual(6000);
|
||||||
|
expect(finalPlayer.duration).toEqual(2000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('timing values', () => {
|
describe('timing values', () => {
|
||||||
|
|
Loading…
Reference in New Issue