diff --git a/packages/animations/browser/src/dsl/animation_timeline_builder.ts b/packages/animations/browser/src/dsl/animation_timeline_builder.ts index dbcc078895..85750cbc53 100644 --- a/packages/animations/browser/src/dsl/animation_timeline_builder.ts +++ b/packages/animations/browser/src/dsl/animation_timeline_builder.ts @@ -409,11 +409,12 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor { break; } + const timeline = context.currentTimeline; if (delay) { - context.currentTimeline.delayNextStep(delay); + timeline.delayNextStep(delay); } - const startingTime = context.currentTimeline.currentTime; + const startingTime = timeline.currentTime; ast.animation.visit(this, context); context.previousNode = ast; @@ -612,10 +613,19 @@ export class TimelineBuilder { get currentTime() { return this.startTime + this.duration; } delayNextStep(delay: number) { - if (this.duration == 0) { - this.startTime += delay; - } else { + // in the event that a style() step is placed right before a stagger() + // and that style() step is the very first style() value in the animation + // then we need to make a copy of the keyframe [0, copy, 1] so that the delay + // properly applies the style() values to work with the stagger... + const hasPreStyleStep = this._keyframes.size == 1 && Object.keys(this._pendingStyles).length; + + if (this.duration || hasPreStyleStep) { this.forwardTime(this.currentTime + delay); + if (hasPreStyleStep) { + this.snapshotCurrentStyles(); + } + } else { + this.startTime += delay; } } diff --git a/packages/core/test/animation/animation_query_integration_spec.ts b/packages/core/test/animation/animation_query_integration_spec.ts index 04d6c73d80..8d591df3fb 100644 --- a/packages/core/test/animation/animation_query_integration_spec.ts +++ b/packages/core/test/animation/animation_query_integration_spec.ts @@ -529,6 +529,72 @@ export function main() { expect(p3.duration).toEqual(4000); }); + it('should properly apply pre styling before a stagger is issued', () => { + @Component({ + selector: 'ani-cmp', + template: ` +
+
+ {{ item }} +
+
+ `, + animations: [ + trigger( + 'myAnimation', + [ + transition( + '* => go', + [ + query( + ':enter', + [ + style({opacity: 0}), + stagger( + 100, + [ + animate(1000, style({opacity: 1})), + ]), + ]), + ]), + ]), + ] + }) + class Cmp { + public exp: any; + public items: any[] = [0, 1, 2, 3, 4]; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + + cmp.exp = 'go'; + fixture.detectChanges(); + engine.flush(); + + const players = getLog(); + expect(players.length).toEqual(5); + + for (let i = 0; i < players.length; i++) { + const player = players[i]; + const kf = player.keyframes; + const limit = kf.length - 1; + const staggerDelay = 100 * i; + const duration = 1000 + staggerDelay; + + expect(kf[0]).toEqual({opacity: '0', offset: 0}); + if (limit > 1) { + const offsetAtStaggerDelay = staggerDelay / duration; + expect(kf[1]).toEqual({opacity: '0', offset: offsetAtStaggerDelay}); + } + expect(kf[limit]).toEqual({opacity: '1', offset: 1}); + expect(player.duration).toEqual(duration); + } + }); + it('should apply a full stagger step delay if the timing data is left undefined', () => { @Component({ selector: 'ani-cmp',