diff --git a/modules/angular2/src/animate/animation.ts b/modules/angular2/src/animate/animation.ts index 621d3311c7..b328beaca6 100644 --- a/modules/angular2/src/animate/animation.ts +++ b/modules/angular2/src/animate/animation.ts @@ -2,7 +2,8 @@ import { DateWrapper, StringWrapper, RegExpWrapper, - NumberWrapper + NumberWrapper, + isPresent } from 'angular2/src/core/facade/lang'; import {Math} from 'angular2/src/core/facade/math'; import {camelCaseToDashCase} from 'angular2/src/core/render/dom/util'; @@ -31,6 +32,8 @@ export class Animation { /** flag used to track whether or not the animation has finished */ completed: boolean = false; + private _stringPrefix: string = ''; + /** total amount of time that the animation should take including delay */ get totalTime(): number { let delay = this.computedDelay != null ? this.computedDelay : 0; @@ -47,6 +50,7 @@ export class Animation { constructor(public element: HTMLElement, public data: CssAnimationOptions, public browserDetails: BrowserDetails) { this.startTime = DateWrapper.toMillis(DateWrapper.now()); + this._stringPrefix = DOM.getAnimationPrefix(); this.setup(); this.wait(timestamp => this.start()); } @@ -77,11 +81,14 @@ export class Animation { if (this.data.toStyles != null) this.applyStyles(this.data.toStyles); var computedStyles = DOM.getComputedStyle(this.element); this.computedDelay = - Math.max(this.parseDurationString(computedStyles.getPropertyValue('transition-delay')), - this.parseDurationString(this.element.style.getPropertyValue('transition-delay'))); - this.computedDuration = Math.max( - this.parseDurationString(computedStyles.getPropertyValue('transition-duration')), - this.parseDurationString(this.element.style.getPropertyValue('transition-duration'))); + Math.max(this.parseDurationString( + computedStyles.getPropertyValue(this._stringPrefix + 'transition-delay')), + this.parseDurationString( + this.element.style.getPropertyValue(this._stringPrefix + 'transition-delay'))); + this.computedDuration = Math.max(this.parseDurationString(computedStyles.getPropertyValue( + this._stringPrefix + 'transition-duration')), + this.parseDurationString(this.element.style.getPropertyValue( + this._stringPrefix + 'transition-duration'))); this.addEvents(); } @@ -91,7 +98,12 @@ export class Animation { */ applyStyles(styles: StringMap): void { StringMapWrapper.forEach(styles, (value, key) => { - DOM.setStyle(this.element, camelCaseToDashCase(key), value.toString()); + var dashCaseKey = camelCaseToDashCase(key); + if (isPresent(DOM.getStyle(this.element, dashCaseKey))) { + DOM.setStyle(this.element, dashCaseKey, value.toString()); + } else { + DOM.setStyle(this.element, this._stringPrefix + dashCaseKey, value.toString()); + } }); } @@ -117,7 +129,7 @@ export class Animation { addEvents(): void { if (this.totalTime > 0) { this.eventClearFunctions.push(DOM.onAndCancel( - this.element, 'transitionend', (event: any) => this.handleAnimationEvent(event))); + this.element, DOM.getTransitionEnd(), (event: any) => this.handleAnimationEvent(event))); } else { this.handleAnimationCompleted(); } diff --git a/modules/angular2/src/core/dom/dom_adapter.ts b/modules/angular2/src/core/dom/dom_adapter.ts index 4f9952d6e7..bc74aac301 100644 --- a/modules/angular2/src/core/dom/dom_adapter.ts +++ b/modules/angular2/src/core/dom/dom_adapter.ts @@ -139,4 +139,7 @@ export class DomAdapter { requestAnimationFrame(callback): number { throw _abstract(); } cancelAnimationFrame(id) { throw _abstract(); } performanceNow(): number { throw _abstract(); } + getAnimationPrefix(): string { throw _abstract(); } + getTransitionEnd(): string { throw _abstract(); } + supportsAnimation(): boolean { throw _abstract(); } } diff --git a/modules/angular2/src/core/dom/generic_browser_adapter.ts b/modules/angular2/src/core/dom/generic_browser_adapter.ts index 4fd74f4d76..51be733045 100644 --- a/modules/angular2/src/core/dom/generic_browser_adapter.ts +++ b/modules/angular2/src/core/dom/generic_browser_adapter.ts @@ -1,11 +1,44 @@ -import {ListWrapper} from 'angular2/src/core/facade/collection'; -import {isPresent, isFunction} from 'angular2/src/core/facade/lang'; +import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection'; +import {isPresent, isFunction, StringWrapper} from 'angular2/src/core/facade/lang'; import {DomAdapter} from './dom_adapter'; /** * Provides DOM operations in any browser environment. */ export class GenericBrowserDomAdapter extends DomAdapter { + private _animationPrefix: string = null; + private _transitionEnd: string = null; + constructor() { + super(); + try { + var element = this.createElement('div', this.defaultDoc()); + if (isPresent(this.getStyle(element, 'animationName'))) { + this._animationPrefix = ''; + } else { + var domPrefixes = ['Webkit', 'Moz', 'O', 'ms']; + for (var i = 0; i < domPrefixes.length; i++) { + if (isPresent(this.getStyle(element, domPrefixes[i] + 'AnimationName'))) { + this._animationPrefix = '-' + StringWrapper.toLowerCase(domPrefixes[i]) + '-'; + break; + } + } + } + var transEndEventNames = { + WebkitTransition: 'webkitTransitionEnd', + MozTransition: 'transitionend', + OTransition: 'oTransitionEnd otransitionend', + transition: 'transitionend' + }; + StringMapWrapper.forEach(transEndEventNames, (value, key) => { + if (isPresent(this.getStyle(element, key))) { + this._transitionEnd = value; + } + }); + } catch (e) { + this._animationPrefix = null; + this._transitionEnd = null; + } + } getDistributedNodes(el: HTMLElement): Node[] { return (el).getDistributedNodes(); } resolveAndSetHref(el: HTMLAnchorElement, baseUrl: string, href: string) { el.href = href == null ? baseUrl : baseUrl + '/../' + href; @@ -37,4 +70,11 @@ export class GenericBrowserDomAdapter extends DomAdapter { supportsNativeShadowDOM(): boolean { return isFunction((this.defaultDoc().body).createShadowRoot); } + getAnimationPrefix(): string { + return isPresent(this._animationPrefix) ? this._animationPrefix : ""; + } + getTransitionEnd(): string { return isPresent(this._transitionEnd) ? this._transitionEnd : ""; } + supportsAnimation(): boolean { + return isPresent(this._animationPrefix) && isPresent(this._transitionEnd); + } } diff --git a/modules/angular2/src/core/dom/html_adapter.dart b/modules/angular2/src/core/dom/html_adapter.dart index 44176f6e06..5a13e1d13c 100644 --- a/modules/angular2/src/core/dom/html_adapter.dart +++ b/modules/angular2/src/core/dom/html_adapter.dart @@ -435,4 +435,16 @@ class Html5LibDomAdapter implements DomAdapter { performanceNow() { throw 'not implemented'; } + + getAnimationPrefix() { + throw 'not implemented'; + } + + getTransitionEnd() { + throw 'not implemented'; + } + + supportsAnimation() { + throw 'not implemented'; + } } diff --git a/modules/angular2/src/core/dom/parse5_adapter.ts b/modules/angular2/src/core/dom/parse5_adapter.ts index 31f2617d36..a47eefdbc8 100644 --- a/modules/angular2/src/core/dom/parse5_adapter.ts +++ b/modules/angular2/src/core/dom/parse5_adapter.ts @@ -552,6 +552,9 @@ export class Parse5DomAdapter extends DomAdapter { requestAnimationFrame(callback): number { return setTimeout(callback, 0); } cancelAnimationFrame(id: number) { clearTimeout(id); } performanceNow(): number { return DateWrapper.toMillis(DateWrapper.now()); } + getAnimationPrefix(): string { return ''; } + getTransitionEnd(): string { return 'transitionend'; } + supportsAnimation(): boolean { return true; } } // TODO: build a proper list, this one is all the keys of a HTMLInputElement diff --git a/modules/angular2/test/animate/animation_builder_spec.ts b/modules/angular2/test/animate/animation_builder_spec.ts index 14f360c736..f103c5597b 100644 --- a/modules/angular2/test/animate/animation_builder_spec.ts +++ b/modules/angular2/test/animate/animation_builder_spec.ts @@ -1,5 +1,6 @@ -import {el, describe, it, expect, inject, SpyObject} from 'angular2/test_lib'; +import {el, describe, it, iit, expect, inject, SpyObject} from 'angular2/test_lib'; import {AnimationBuilder} from 'angular2/src/animate/animation_builder'; +import {DOM} from 'angular2/src/core/dom/dom_adapter'; export function main() { describe("AnimationBuilder", () => { @@ -54,8 +55,13 @@ export function main() { var runner = animateCss.start(element); runner.flush(); - expect(runner.computedDelay).toBe(100); - expect(runner.computedDuration).toBe(200); + if (DOM.supportsAnimation()) { + expect(runner.computedDelay).toBe(100); + expect(runner.computedDuration).toBe(200); + } else { + expect(runner.computedDelay).toBe(0); + expect(runner.computedDuration).toBe(0); + } })); it('should support from styles', inject([AnimationBuilder], animate => { @@ -71,12 +77,18 @@ export function main() { it('should support duration and delay defined in CSS', inject([AnimationBuilder], (animate) => { var animateCss = animate.css(); - var element = el('
'); + var element = + el(`
`); var runner = animateCss.start(element); runner.flush(); - expect(runner.computedDuration).toEqual(500); - expect(runner.computedDelay).toEqual(250); + if (DOM.supportsAnimation()) { + expect(runner.computedDelay).toBe(250); + expect(runner.computedDuration).toBe(500); + } else { + expect(runner.computedDelay).toEqual(0); + expect(runner.computedDuration).toEqual(0); + } })); it('should add classes', inject([AnimationBuilder], (animate) => { @@ -108,11 +120,15 @@ export function main() { runner.flush(); - expect(callback).not.toHaveBeenCalled(); + if (DOM.supportsAnimation()) { + expect(callback).not.toHaveBeenCalled(); - runner.handleAnimationCompleted(); + runner.handleAnimationCompleted(); - expect(callback).toHaveBeenCalled(); + expect(callback).toHaveBeenCalled(); + } else { + expect(callback).toHaveBeenCalled(); + } })); });