fix(core): animations should blend in all previously transitioned styles into next animation if interrupted (#13148)
This commit is contained in:

committed by
Igor Minar

parent
1bd04e95de
commit
889b48d85f
@ -20,10 +20,8 @@ export class WebAnimationsDriver implements AnimationDriver {
|
|||||||
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
|
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
|
||||||
let formattedSteps: {[key: string]: string | number}[] = [];
|
let formattedSteps: {[key: string]: string | number}[] = [];
|
||||||
let startingStyleLookup: {[key: string]: string | number} = {};
|
let startingStyleLookup: {[key: string]: string | number} = {};
|
||||||
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
|
if (isPresent(startingStyles)) {
|
||||||
startingStyleLookup = _populateStyles(startingStyles, {});
|
startingStyleLookup = _populateStyles(startingStyles, {});
|
||||||
startingStyleLookup['offset'] = 0;
|
|
||||||
formattedSteps.push(startingStyleLookup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyframes.forEach((keyframe: AnimationKeyframe) => {
|
keyframes.forEach((keyframe: AnimationKeyframe) => {
|
||||||
@ -32,14 +30,18 @@ export class WebAnimationsDriver implements AnimationDriver {
|
|||||||
formattedSteps.push(data);
|
formattedSteps.push(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// this is a special case when only styles are applied as an
|
// Styling passed into element.animate() must always be balanced.
|
||||||
// animation. When this occurs we want to animate from start to
|
// The special cases below can occur if only style() calls exist
|
||||||
// end with the same values. Removing the offset and having only
|
// within an animation or when a style() calls are used prior
|
||||||
// start/end values is suitable enough for the web-animations API
|
// to a group() animation being issued or if the renderer is
|
||||||
if (formattedSteps.length == 1) {
|
// invoked by the user directly.
|
||||||
const start = formattedSteps[0];
|
if (formattedSteps.length == 0) {
|
||||||
start['offset'] = null;
|
formattedSteps = [startingStyleLookup, startingStyleLookup];
|
||||||
formattedSteps = [start, start];
|
} else if (formattedSteps.length == 1) {
|
||||||
|
const start = startingStyleLookup;
|
||||||
|
const end = formattedSteps[0];
|
||||||
|
end['offset'] = null;
|
||||||
|
formattedSteps = [start, end];
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerOptions: {[key: string]: string | number} = {
|
const playerOptions: {[key: string]: string | number} = {
|
||||||
|
@ -69,12 +69,21 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
|||||||
|
|
||||||
const previousStyleProps = Object.keys(this.previousStyles);
|
const previousStyleProps = Object.keys(this.previousStyles);
|
||||||
if (previousStyleProps.length) {
|
if (previousStyleProps.length) {
|
||||||
let startingKeyframe = findStartingKeyframe(keyframes);
|
let startingKeyframe = keyframes[0];
|
||||||
|
let missingStyleProps: string[] = [];
|
||||||
previousStyleProps.forEach(prop => {
|
previousStyleProps.forEach(prop => {
|
||||||
if (isPresent(startingKeyframe[prop])) {
|
if (!isPresent(startingKeyframe[prop])) {
|
||||||
startingKeyframe[prop] = this.previousStyles[prop];
|
missingStyleProps.push(prop);
|
||||||
}
|
}
|
||||||
|
startingKeyframe[prop] = this.previousStyles[prop];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (missingStyleProps.length) {
|
||||||
|
for (let i = 1; i < keyframes.length; i++) {
|
||||||
|
let kf = keyframes[i];
|
||||||
|
missingStyleProps.forEach(prop => { kf[prop] = _computeStyle(this.element, prop); });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
|
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
|
||||||
@ -180,17 +189,3 @@ function _copyKeyframeStyles(styles: {[style: string]: string | number}):
|
|||||||
});
|
});
|
||||||
return newStyles;
|
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;
|
|
||||||
}
|
|
||||||
|
@ -9,23 +9,9 @@
|
|||||||
import {AnimationPlayer} from '@angular/core';
|
import {AnimationPlayer} from '@angular/core';
|
||||||
import {el} from '@angular/platform-browser/testing/browser_util';
|
import {el} from '@angular/platform-browser/testing/browser_util';
|
||||||
|
|
||||||
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
|
|
||||||
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
|
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
|
||||||
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
|
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
|
||||||
import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core';
|
import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core';
|
||||||
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
|
|
||||||
|
|
||||||
class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
|
|
||||||
public log: {[key: string]: any}[] = [];
|
|
||||||
|
|
||||||
constructor() { super(); }
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
|
|
||||||
this.log.push({'elm': elm, 'keyframes': keyframes, 'options': options});
|
|
||||||
return new MockDomAnimatePlayer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _makeStyles(styles: {[key: string]: string | number}): AnimationStyles {
|
function _makeStyles(styles: {[key: string]: string | number}): AnimationStyles {
|
||||||
return new AnimationStyles([styles]);
|
return new AnimationStyles([styles]);
|
||||||
@ -38,10 +24,10 @@ function _makeKeyframe(
|
|||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('WebAnimationsDriver', () => {
|
describe('WebAnimationsDriver', () => {
|
||||||
let driver: ExtendedWebAnimationsDriver;
|
let driver: WebAnimationsDriver;
|
||||||
let elm: HTMLElement;
|
let elm: HTMLElement;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
driver = new ExtendedWebAnimationsDriver();
|
driver = new WebAnimationsDriver();
|
||||||
elm = el('<div></div>');
|
elm = el('<div></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -101,6 +87,33 @@ export function main() {
|
|||||||
const allZero = player.keyframes.every(kf => kf['offset'] == 0);
|
const allZero = player.keyframes.every(kf => kf['offset'] == 0);
|
||||||
expect(allZero).toBe(true);
|
expect(allZero).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create an animation with only starting-styles as data', () => {
|
||||||
|
const startingStyles = _makeStyles({color: 'red', fontSize: '20px'});
|
||||||
|
|
||||||
|
const player = driver.animate(elm, startingStyles, [], 1000, 1000, null);
|
||||||
|
|
||||||
|
expect(player.keyframes).toEqual([
|
||||||
|
{color: 'red', fontSize: '20px'},
|
||||||
|
{color: 'red', fontSize: '20px'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a balanced animation when only one keyframe is passed in', () => {
|
||||||
|
const startingStyles = _makeStyles({color: 'red', fontSize: '20px'});
|
||||||
|
|
||||||
|
const keyframeStyles: any = [_makeKeyframe(1, {color: 'blue'})];
|
||||||
|
|
||||||
|
const player = driver.animate(elm, startingStyles, keyframeStyles, 1000, 1000, null);
|
||||||
|
|
||||||
|
const kf0 = player.keyframes[0];
|
||||||
|
const kf1 = player.keyframes[1];
|
||||||
|
|
||||||
|
expect(kf0['color']).toEqual('red');
|
||||||
|
expect(kf0['fontSize']).toEqual('20px');
|
||||||
|
expect(kf1['color']).toEqual('blue');
|
||||||
|
expect(kf1['fontSize']).toEqual('20px');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,6 +208,32 @@ export function main() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow previous styles to be merged into the starting keyframe of the animation that were not apart of the animation to begin with',
|
||||||
|
() => {
|
||||||
|
if (!getDOM().supportsWebAnimation()) return;
|
||||||
|
|
||||||
|
const elm = el('<div></div>');
|
||||||
|
document.body.appendChild(elm);
|
||||||
|
elm.style.color = 'rgb(0,0,0)';
|
||||||
|
|
||||||
|
const previousStyles = {color: 'red'};
|
||||||
|
const previousPlayer =
|
||||||
|
new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
|
||||||
|
previousPlayer.play();
|
||||||
|
previousPlayer.finish();
|
||||||
|
|
||||||
|
const player = new ExtendedWebAnimationsPlayer(
|
||||||
|
elm, [{opacity: '0'}, {opacity: '1'}], {duration: 1000}, [previousPlayer]);
|
||||||
|
|
||||||
|
player.init();
|
||||||
|
|
||||||
|
const data = player.domPlayer.captures['trigger'][0];
|
||||||
|
expect(data['keyframes']).toEqual([
|
||||||
|
{opacity: '0', color: 'red'},
|
||||||
|
{opacity: '1', color: 'rgb(0, 0, 0)'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should properly calculate the previous styles for the player even when its currently playing',
|
it('should properly calculate the previous styles for the player even when its currently playing',
|
||||||
() => {
|
() => {
|
||||||
if (!getDOM().supportsWebAnimation()) return;
|
if (!getDOM().supportsWebAnimation()) return;
|
||||||
|
Reference in New Issue
Block a user