fix(animations): make sure style calculations are not computed too early (#15540)
Closes #15507
This commit is contained in:
parent
f368381d12
commit
a580f8c61f
|
@ -13,3 +13,4 @@ export {NoopAnimationDriver as ɵNoopAnimationDriver} from './render/animation_d
|
||||||
export {DomAnimationEngine as ɵDomAnimationEngine} from './render/dom_animation_engine';
|
export {DomAnimationEngine as ɵDomAnimationEngine} from './render/dom_animation_engine';
|
||||||
export {NoopAnimationEngine as ɵNoopAnimationEngine} from './render/noop_animation_engine';
|
export {NoopAnimationEngine as ɵNoopAnimationEngine} from './render/noop_animation_engine';
|
||||||
export {WebAnimationsDriver as ɵWebAnimationsDriver, supportsWebAnimations as ɵsupportsWebAnimations} from './render/web_animations/web_animations_driver';
|
export {WebAnimationsDriver as ɵWebAnimationsDriver, supportsWebAnimations as ɵsupportsWebAnimations} from './render/web_animations/web_animations_driver';
|
||||||
|
export {WebAnimationsPlayer as ɵWebAnimationsPlayer} from './render/web_animations/web_animations_player';
|
||||||
|
|
|
@ -259,8 +259,6 @@ export class DomAnimationEngine {
|
||||||
const player = this._buildPlayer(element, instruction, previousPlayers, i);
|
const player = this._buildPlayer(element, instruction, previousPlayers, i);
|
||||||
player.onDestroy(
|
player.onDestroy(
|
||||||
() => { deleteFromArrayMap(this._activeElementAnimations, element, player); });
|
() => { deleteFromArrayMap(this._activeElementAnimations, element, player); });
|
||||||
player.init();
|
|
||||||
|
|
||||||
this._markPlayerAsActive(element, player);
|
this._markPlayerAsActive(element, player);
|
||||||
return player;
|
return player;
|
||||||
});
|
});
|
||||||
|
@ -354,6 +352,7 @@ export class DomAnimationEngine {
|
||||||
// in the event that an animation throws an error then we do
|
// in the event that an animation throws an error then we do
|
||||||
// not want to re-run animations on any previous animations
|
// not want to re-run animations on any previous animations
|
||||||
// if they have already been kicked off beforehand
|
// if they have already been kicked off beforehand
|
||||||
|
player.init();
|
||||||
if (!player.hasStarted()) {
|
if (!player.hasStarted()) {
|
||||||
player.play();
|
player.play();
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,22 @@ export function main() {
|
||||||
expect(engine.queuedPlayers.pop() instanceof NoopAnimationPlayer).toBe(true);
|
expect(engine.queuedPlayers.pop() instanceof NoopAnimationPlayer).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not initialize the animation until the engine has been flushed', () => {
|
||||||
|
const engine = makeEngine();
|
||||||
|
engine.registerTrigger(trigger(
|
||||||
|
'trig', [transition('* => something', [animate(1000, style({color: 'gold'}))])]));
|
||||||
|
|
||||||
|
engine.setProperty(element, 'trig', 'something');
|
||||||
|
const player = engine.queuedPlayers.pop() as MockAnimationPlayer;
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
player.onInit(() => initialized = true);
|
||||||
|
|
||||||
|
expect(initialized).toBe(false);
|
||||||
|
engine.flush();
|
||||||
|
expect(initialized).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not queue an animation if the property value has not changed at all', () => {
|
it('should not queue an animation if the property value has not changed at all', () => {
|
||||||
const engine = makeEngine();
|
const engine = makeEngine();
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ export class MockAnimationDriver implements AnimationDriver {
|
||||||
export class MockAnimationPlayer extends NoopAnimationPlayer {
|
export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||||
private __finished = false;
|
private __finished = false;
|
||||||
public previousStyles: {[key: string]: string | number} = {};
|
public previousStyles: {[key: string]: string | number} = {};
|
||||||
|
private _onInitFns: (() => any)[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public element: any, public keyframes: {[key: string]: string | number}[],
|
public element: any, public keyframes: {[key: string]: string | number}[],
|
||||||
|
@ -45,6 +46,16 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @internal */
|
||||||
|
onInit(fn: () => any) { this._onInitFns.push(fn); }
|
||||||
|
|
||||||
|
/* @internal */
|
||||||
|
init() {
|
||||||
|
super.init();
|
||||||
|
this._onInitFns.forEach(fn => fn());
|
||||||
|
this._onInitFns = [];
|
||||||
|
}
|
||||||
|
|
||||||
finish(): void {
|
finish(): void {
|
||||||
super.finish();
|
super.finish();
|
||||||
this.__finished = true;
|
this.__finished = true;
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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 {animate, style, transition, trigger} from '@angular/animations';
|
||||||
|
import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser';
|
||||||
|
import {ɵDomAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser'
|
||||||
|
import {Component, ViewChild} from '@angular/core';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
import {TestBed} from '../../testing';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
// these tests are only mean't to be run within the DOM (for now)
|
||||||
|
if (typeof Element == 'undefined' || !ɵsupportsWebAnimations()) return;
|
||||||
|
|
||||||
|
describe('animation integration tests using web animations', function() {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [{provide: AnimationDriver, useClass: ɵWebAnimationsDriver}],
|
||||||
|
imports: [BrowserAnimationsModule]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should animate a component that captures height during an animation', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'if-cmp',
|
||||||
|
template: `
|
||||||
|
<div *ngIf="exp" #element [@myAnimation]="exp">
|
||||||
|
hello {{ text }}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
animations: [trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[
|
||||||
|
transition('* => *', [style({height: '0px'}), animate(1000, style({height: '*'}))]),
|
||||||
|
])]
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
exp: any = false;
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
@ViewChild('element') public element: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
|
||||||
|
const engine = TestBed.get(ɵAnimationEngine);
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
cmp.exp = 1;
|
||||||
|
cmp.text = '';
|
||||||
|
fixture.detectChanges();
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
const element = cmp.element.nativeElement;
|
||||||
|
element.style.lineHeight = '20px';
|
||||||
|
element.style.width = '50px';
|
||||||
|
|
||||||
|
cmp.exp = 2;
|
||||||
|
cmp.text = '12345';
|
||||||
|
fixture.detectChanges();
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
let player = engine.activePlayers.pop() as ɵWebAnimationsPlayer;
|
||||||
|
player.setPosition(1);
|
||||||
|
|
||||||
|
assertStyleBetween(element, 'height', 15, 25);
|
||||||
|
|
||||||
|
cmp.exp = 3;
|
||||||
|
cmp.text = '12345-12345-12345-12345';
|
||||||
|
fixture.detectChanges();
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
player = engine.activePlayers.pop() as ɵWebAnimationsPlayer;
|
||||||
|
player.setPosition(1);
|
||||||
|
assertStyleBetween(element, 'height', 35, 45);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertStyleBetween(
|
||||||
|
element: any, prop: string, start: string | number, end: string | number) {
|
||||||
|
const style = (window.getComputedStyle(element) as any)[prop] as string;
|
||||||
|
if (typeof start == 'number' && typeof end == 'number') {
|
||||||
|
const value = parseFloat(style);
|
||||||
|
expect(value).toBeGreaterThan(start);
|
||||||
|
expect(value).toBeLessThan(end);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue