diff --git a/modules/@angular/compiler/src/animation/animation_parser.ts b/modules/@angular/compiler/src/animation/animation_parser.ts index 18ce888ee2..b11c6f2b41 100644 --- a/modules/@angular/compiler/src/animation/animation_parser.ts +++ b/modules/@angular/compiler/src/animation/animation_parser.ts @@ -174,6 +174,11 @@ function _normalizeStyleMetadata( entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst}, schema: ElementSchemaRegistry, errors: AnimationParseError[], permitStateReferences: boolean): {[key: string]: string | number}[] { + const offset = entry.offset; + if (offset > 1 || offset < 0) { + errors.push(new AnimationParseError(`Offset values for animations must be between 0 and 1`)); + } + const normalizedStyles: {[key: string]: string | number}[] = []; entry.styles.forEach(styleEntry => { if (typeof styleEntry === 'string') { diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index 3354eaad78..77de31dd5b 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -156,6 +156,60 @@ function declareTests({useJit}: {useJit: boolean}) { expect(kf[1]).toEqual([1, {'backgroundColor': 'blue'}]); })); + it('should throw an error when a provided offset for an animation step if an offset value is greater than 1', + fakeAsync(() => { + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: ` +
+ `, + animations: [trigger( + 'tooBig', + [transition( + '* => *', [animate('444ms', style({'opacity': '1', offset: 1.1}))])])] + } + }); + + let message = ''; + try { + const fixture = TestBed.createComponent(DummyIfCmp); + } catch (e) { + message = e.message; + } + + const lines = message.split(/\n+/); + expect(lines[1]).toMatch( + /Unable to parse the animation sequence for "tooBig" on the DummyIfCmp component due to the following errors:/); + expect(lines[2]).toMatch(/Offset values for animations must be between 0 and 1/); + })); + + it('should throw an error when a provided offset for an animation step if an offset value is less than 0', + fakeAsync(() => { + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: ` +
+ `, + animations: [trigger( + 'tooSmall', + [transition( + '* => *', [animate('444ms', style({'opacity': '0', offset: -1}))])])] + } + }); + + let message = ''; + try { + const fixture = TestBed.createComponent(DummyIfCmp); + } catch (e) { + message = e.message; + } + + const lines = message.split(/\n+/); + expect(lines[1]).toMatch( + /Unable to parse the animation sequence for "tooSmall" on the DummyIfCmp component due to the following errors:/); + expect(lines[2]).toMatch(/Offset values for animations must be between 0 and 1/); + })); + describe('animation aliases', () => { it('should animate the ":enter" animation alias as "void => *"', fakeAsync(() => { TestBed.overrideComponent(DummyIfCmp, { diff --git a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts index 0ee68f936c..e0ebfb2fa7 100644 --- a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts +++ b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts @@ -28,7 +28,7 @@ export class WebAnimationsDriver implements AnimationDriver { keyframes.forEach((keyframe: AnimationKeyframe) => { const data = _populateStyles(keyframe.styles, startingStyleLookup); - data['offset'] = keyframe.offset; + data['offset'] = Math.max(0, Math.min(1, keyframe.offset)); formattedSteps.push(data); }); diff --git a/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts b/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts index 6fe554a6ca..9a8dbef5e8 100644 --- a/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts +++ b/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts @@ -85,6 +85,22 @@ export function main() { elm, startingStyles, styles, 1000, 1000, null, previousPlayers); expect(player.previousStyles).toEqual({}); }); + + it('should round down offset values that are bigger than 1', () => { + const startingStyles = _makeStyles({}); + const styles = [_makeKeyframe(0, {}), _makeKeyframe(2, {})]; + const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null); + expect(player.keyframes.pop()['offset']).toEqual(1); + }); + + it('should round down offset values that are bigger less than 0', () => { + const startingStyles = _makeStyles({}); + const styles = [_makeKeyframe(-99, {}), _makeKeyframe(-0.1, {}), _makeKeyframe(1, {})]; + const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null); + player.keyframes.pop(); // remove the final keyframe that is `1` + const allZero = player.keyframes.every(kf => kf['offset'] == 0); + expect(allZero).toBe(true); + }); }); }