@ -1,203 +0,0 @@
|
||||
import {
|
||||
DateWrapper,
|
||||
StringWrapper,
|
||||
RegExpWrapper,
|
||||
NumberWrapper,
|
||||
isPresent
|
||||
} from '../../src/facade/lang';
|
||||
import {Math} from '../../src/facade/math';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {camelCaseToDashCase} from '../dom/util';
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
|
||||
import {BrowserDetails} from './browser_details';
|
||||
import {CssAnimationOptions} from './css_animation_options';
|
||||
|
||||
export class Animation {
|
||||
/** functions to be called upon completion */
|
||||
callbacks: Function[] = [];
|
||||
|
||||
/** the duration (ms) of the animation (whether from CSS or manually set) */
|
||||
computedDuration: number;
|
||||
|
||||
/** the animation delay (ms) (whether from CSS or manually set) */
|
||||
computedDelay: number;
|
||||
|
||||
/** timestamp of when the animation started */
|
||||
startTime: number;
|
||||
|
||||
/** functions for removing event listeners */
|
||||
eventClearFunctions: Function[] = [];
|
||||
|
||||
/** 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;
|
||||
let duration = this.computedDuration != null ? this.computedDuration : 0;
|
||||
return delay + duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the start time and starts the animation
|
||||
* @param element
|
||||
* @param data
|
||||
* @param browserDetails
|
||||
*/
|
||||
constructor(public element: HTMLElement, public data: CssAnimationOptions,
|
||||
public browserDetails: BrowserDetails) {
|
||||
this.startTime = DateWrapper.toMillis(DateWrapper.now());
|
||||
this._stringPrefix = getDOM().getAnimationPrefix();
|
||||
this.setup();
|
||||
this.wait((timestamp: any) => this.start());
|
||||
}
|
||||
|
||||
wait(callback: Function) {
|
||||
// Firefox requires 2 frames for some reason
|
||||
this.browserDetails.raf(callback, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the initial styles before the animation is started
|
||||
*/
|
||||
setup(): void {
|
||||
if (this.data.fromStyles != null) this.applyStyles(this.data.fromStyles);
|
||||
if (this.data.duration != null)
|
||||
this.applyStyles({'transitionDuration': this.data.duration.toString() + 'ms'});
|
||||
if (this.data.delay != null)
|
||||
this.applyStyles({'transitionDelay': this.data.delay.toString() + 'ms'});
|
||||
}
|
||||
|
||||
/**
|
||||
* After the initial setup has occurred, this method adds the animation styles
|
||||
*/
|
||||
start(): void {
|
||||
this.addClasses(this.data.classesToAdd);
|
||||
this.addClasses(this.data.animationClasses);
|
||||
this.removeClasses(this.data.classesToRemove);
|
||||
if (this.data.toStyles != null) this.applyStyles(this.data.toStyles);
|
||||
var computedStyles = getDOM().getComputedStyle(this.element);
|
||||
this.computedDelay =
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the provided styles to the element
|
||||
* @param styles
|
||||
*/
|
||||
applyStyles(styles: {[key: string]: any}): void {
|
||||
StringMapWrapper.forEach(styles, (value: any, key: string) => {
|
||||
var dashCaseKey = camelCaseToDashCase(key);
|
||||
if (isPresent(getDOM().getStyle(this.element, dashCaseKey))) {
|
||||
getDOM().setStyle(this.element, dashCaseKey, value.toString());
|
||||
} else {
|
||||
getDOM().setStyle(this.element, this._stringPrefix + dashCaseKey, value.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided classes to the element
|
||||
* @param classes
|
||||
*/
|
||||
addClasses(classes: string[]): void {
|
||||
for (let i = 0, len = classes.length; i < len; i++) getDOM().addClass(this.element, classes[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided classes from the element
|
||||
* @param classes
|
||||
*/
|
||||
removeClasses(classes: string[]): void {
|
||||
for (let i = 0, len = classes.length; i < len; i++)
|
||||
getDOM().removeClass(this.element, classes[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds events to track when animations have finished
|
||||
*/
|
||||
addEvents(): void {
|
||||
if (this.totalTime > 0) {
|
||||
this.eventClearFunctions.push(
|
||||
getDOM().onAndCancel(this.element, getDOM().getTransitionEnd(),
|
||||
(event: any) => this.handleAnimationEvent(event)));
|
||||
} else {
|
||||
this.handleAnimationCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
handleAnimationEvent(event: any): void {
|
||||
let elapsedTime = Math.round(event.elapsedTime * 1000);
|
||||
if (!this.browserDetails.elapsedTimeIncludesDelay) elapsedTime += this.computedDelay;
|
||||
event.stopPropagation();
|
||||
if (elapsedTime >= this.totalTime) this.handleAnimationCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all animation callbacks and removes temporary classes
|
||||
*/
|
||||
handleAnimationCompleted(): void {
|
||||
this.removeClasses(this.data.animationClasses);
|
||||
this.callbacks.forEach(callback => callback());
|
||||
this.callbacks = [];
|
||||
this.eventClearFunctions.forEach(fn => fn());
|
||||
this.eventClearFunctions = [];
|
||||
this.completed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds animation callbacks to be called upon completion
|
||||
* @param callback
|
||||
* @returns {Animation}
|
||||
*/
|
||||
onComplete(callback: Function): Animation {
|
||||
if (this.completed) {
|
||||
callback();
|
||||
} else {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the duration string to the number of milliseconds
|
||||
* @param duration
|
||||
* @returns {number}
|
||||
*/
|
||||
parseDurationString(duration: string): number {
|
||||
var maxValue = 0;
|
||||
// duration must have at least 2 characters to be valid. (number + type)
|
||||
if (duration == null || duration.length < 2) {
|
||||
return maxValue;
|
||||
} else if (duration.substring(duration.length - 2) == 'ms') {
|
||||
let value = NumberWrapper.parseInt(this.stripLetters(duration), 10);
|
||||
if (value > maxValue) maxValue = value;
|
||||
} else if (duration.substring(duration.length - 1) == 's') {
|
||||
duration = StringWrapper.replace(duration, ',', '.');
|
||||
let ms = NumberWrapper.parseFloat(this.stripLetters(duration)) * 1000;
|
||||
let value = Math.floor(ms);
|
||||
if (value > maxValue) maxValue = value;
|
||||
}
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the letters from the duration string
|
||||
* @param str
|
||||
* @returns {string}
|
||||
*/
|
||||
stripLetters(str: string): string {
|
||||
return StringWrapper.replaceAll(str, RegExpWrapper.create('[^0-9]+$', ''), '');
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CssAnimationBuilder} from './css_animation_builder';
|
||||
import {BrowserDetails} from './browser_details';
|
||||
|
||||
@Injectable()
|
||||
export class AnimationBuilder {
|
||||
/**
|
||||
* Used for DI
|
||||
* @param browserDetails
|
||||
*/
|
||||
constructor(public browserDetails: BrowserDetails) {}
|
||||
|
||||
/**
|
||||
* Creates a new CSS Animation
|
||||
* @returns {CssAnimationBuilder}
|
||||
*/
|
||||
css(): CssAnimationBuilder { return new CssAnimationBuilder(this.browserDetails); }
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Math} from '../../src/facade/math';
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
|
||||
@Injectable()
|
||||
export class BrowserDetails {
|
||||
elapsedTimeIncludesDelay = false;
|
||||
|
||||
constructor() { this.doesElapsedTimeIncludesDelay(); }
|
||||
|
||||
/**
|
||||
* Determines if `event.elapsedTime` includes transition delay in the current browser. At this
|
||||
* time, Chrome and Opera seem to be the only browsers that include this.
|
||||
*/
|
||||
doesElapsedTimeIncludesDelay(): void {
|
||||
var div = getDOM().createElement('div');
|
||||
getDOM().setAttribute(div, 'style',
|
||||
`position: absolute; top: -9999px; left: -9999px; width: 1px;
|
||||
height: 1px; transition: all 1ms linear 1ms;`);
|
||||
// Firefox requires that we wait for 2 frames for some reason
|
||||
this.raf((timestamp: any) => {
|
||||
getDOM().on(div, 'transitionend', (event: any) => {
|
||||
var elapsed = Math.round(event.elapsedTime * 1000);
|
||||
this.elapsedTimeIncludesDelay = elapsed == 2;
|
||||
getDOM().remove(div);
|
||||
});
|
||||
getDOM().setStyle(div, 'width', '2px');
|
||||
}, 2);
|
||||
}
|
||||
|
||||
raf(callback: Function, frames: number = 1): Function {
|
||||
var queue: RafQueue = new RafQueue(callback, frames);
|
||||
return () => queue.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class RafQueue {
|
||||
currentFrameId: number;
|
||||
constructor(public callback: Function, public frames: number) { this._raf(); }
|
||||
private _raf() {
|
||||
this.currentFrameId =
|
||||
getDOM().requestAnimationFrame((timestamp: number) => this._nextFrame(timestamp));
|
||||
}
|
||||
private _nextFrame(timestamp: number) {
|
||||
this.frames--;
|
||||
if (this.frames > 0) {
|
||||
this._raf();
|
||||
} else {
|
||||
this.callback(timestamp);
|
||||
}
|
||||
}
|
||||
cancel() {
|
||||
getDOM().cancelAnimationFrame(this.currentFrameId);
|
||||
this.currentFrameId = null;
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
import {CssAnimationOptions} from './css_animation_options';
|
||||
import {Animation} from './animation';
|
||||
import {BrowserDetails} from './browser_details';
|
||||
|
||||
export class CssAnimationBuilder {
|
||||
/** @type {CssAnimationOptions} */
|
||||
data: CssAnimationOptions = new CssAnimationOptions();
|
||||
|
||||
/**
|
||||
* Accepts public properties for CssAnimationBuilder
|
||||
*/
|
||||
constructor(public browserDetails: BrowserDetails) {}
|
||||
|
||||
/**
|
||||
* Adds a temporary class that will be removed at the end of the animation
|
||||
* @param className
|
||||
*/
|
||||
addAnimationClass(className: string): CssAnimationBuilder {
|
||||
this.data.animationClasses.push(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class that will remain on the element after the animation has finished
|
||||
* @param className
|
||||
*/
|
||||
addClass(className: string): CssAnimationBuilder {
|
||||
this.data.classesToAdd.push(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a class from the element
|
||||
* @param className
|
||||
*/
|
||||
removeClass(className: string): CssAnimationBuilder {
|
||||
this.data.classesToRemove.push(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the animation duration (and overrides any defined through CSS)
|
||||
* @param duration
|
||||
*/
|
||||
setDuration(duration: number): CssAnimationBuilder {
|
||||
this.data.duration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the animation delay (and overrides any defined through CSS)
|
||||
* @param delay
|
||||
*/
|
||||
setDelay(delay: number): CssAnimationBuilder {
|
||||
this.data.delay = delay;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets styles for both the initial state and the destination state
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
setStyles(from: {[key: string]: any}, to: {[key: string]: any}): CssAnimationBuilder {
|
||||
return this.setFromStyles(from).setToStyles(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial styles for the animation
|
||||
* @param from
|
||||
*/
|
||||
setFromStyles(from: {[key: string]: any}): CssAnimationBuilder {
|
||||
this.data.fromStyles = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination styles for the animation
|
||||
* @param to
|
||||
*/
|
||||
setToStyles(to: {[key: string]: any}): CssAnimationBuilder {
|
||||
this.data.toStyles = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the animation and returns a promise
|
||||
* @param element
|
||||
*/
|
||||
start(element: HTMLElement): Animation {
|
||||
return new Animation(element, this.data, this.browserDetails);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
export class CssAnimationOptions {
|
||||
/** initial styles for the element */
|
||||
fromStyles: {[key: string]: any};
|
||||
|
||||
/** destination styles for the element */
|
||||
toStyles: {[key: string]: any};
|
||||
|
||||
/** classes to be added to the element */
|
||||
classesToAdd: string[] = [];
|
||||
|
||||
/** classes to be removed from the element */
|
||||
classesToRemove: string[] = [];
|
||||
|
||||
/** classes to be added for the duration of the animation */
|
||||
animationClasses: string[] = [];
|
||||
|
||||
/** override the duration of the animation (in milliseconds) */
|
||||
duration: number;
|
||||
|
||||
/** override the transition delay (in milliseconds) */
|
||||
delay: number;
|
||||
}
|
@ -19,7 +19,8 @@ import {
|
||||
ComponentRef
|
||||
} from "@angular/core";
|
||||
import {isBlank, isPresent} from "./facade/lang";
|
||||
import {wtfInit, SanitizationService, ReflectionCapabilities} from "../core_private";
|
||||
import {wtfInit, SanitizationService, ReflectionCapabilities, AnimationDriver, NoOpAnimationDriver} from '../core_private';
|
||||
import {WebAnimationsDriver} from '../src/dom/web_animations_driver';
|
||||
import {COMMON_DIRECTIVES, COMMON_PIPES, FORM_PROVIDERS, PlatformLocation} from "@angular/common";
|
||||
import {DomSanitizationService, DomSanitizationServiceImpl} from "./security/dom_sanitization_service";
|
||||
import {BrowserDomAdapter} from "./browser/browser_adapter";
|
||||
@ -33,8 +34,6 @@ import {KeyEventsPlugin} from "./dom/events/key_events";
|
||||
import {ELEMENT_PROBE_PROVIDERS} from "./dom/debug/ng_probe";
|
||||
import {DomEventsPlugin} from "./dom/events/dom_events";
|
||||
import {HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerGesturesPlugin} from "./dom/events/hammer_gestures";
|
||||
import {AnimationBuilder} from "./animate/animation_builder";
|
||||
import {BrowserDetails} from "./animate/browser_details";
|
||||
import {BrowserPlatformLocation} from "./browser/location/browser_platform_location";
|
||||
import {COMPILER_PROVIDERS, XHR} from "@angular/compiler";
|
||||
import {CachedXHR} from "./xhr/xhr_cache";
|
||||
@ -83,10 +82,9 @@ export const BROWSER_APP_PROVIDERS: Array<any /*Type | Provider | any[]*/> =
|
||||
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer},
|
||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||
DomSharedStylesHost,
|
||||
Testability,
|
||||
BrowserDetails,
|
||||
AnimationBuilder,
|
||||
EventManager,
|
||||
ELEMENT_PROBE_PROVIDERS
|
||||
];
|
||||
@ -198,3 +196,10 @@ function _exceptionHandler(): ExceptionHandler {
|
||||
function _document(): any {
|
||||
return getDOM().defaultDoc();
|
||||
}
|
||||
|
||||
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
if (getDOM().supportsWebAnimation()) {
|
||||
return new WebAnimationsDriver();
|
||||
}
|
||||
return new NoOpAnimationDriver();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
import {isBlank, isPresent, global, setValueOnPath, DateWrapper} from '../../src/facade/lang';
|
||||
|
||||
import {isBlank, isPresent, isFunction, global, setValueOnPath, DateWrapper} from '../../src/facade/lang';
|
||||
import {GenericBrowserDomAdapter} from './generic_browser_adapter';
|
||||
import {setRootDomAdapter} from '../dom/dom_adapter';
|
||||
|
||||
@ -341,6 +340,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
|
||||
requestAnimationFrame(callback): number { return window.requestAnimationFrame(callback); }
|
||||
cancelAnimationFrame(id: number) { window.cancelAnimationFrame(id); }
|
||||
supportsWebAnimation(): boolean { return isFunction(document.body['animate']); }
|
||||
performanceNow(): number {
|
||||
// performance.now() is not available in all browsers, see
|
||||
// http://caniuse.com/#search=performance.now
|
||||
|
@ -147,6 +147,7 @@ export abstract class DomAdapter {
|
||||
abstract setGlobalVar(name: string, value: any);
|
||||
abstract requestAnimationFrame(callback): number;
|
||||
abstract cancelAnimationFrame(id);
|
||||
abstract supportsWebAnimation(): boolean;
|
||||
abstract performanceNow(): number;
|
||||
abstract getAnimationPrefix(): string;
|
||||
abstract getTransitionEnd(): string;
|
||||
|
@ -0,0 +1,9 @@
|
||||
export interface DomAnimatePlayer {
|
||||
cancel(): void;
|
||||
play(): void;
|
||||
pause(): void;
|
||||
finish(): void;
|
||||
onfinish: Function;
|
||||
position: number;
|
||||
currentTime: number;
|
||||
}
|
@ -8,7 +8,6 @@ import {
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {RenderDebugInfo} from '../../core_private';
|
||||
import {AnimationBuilder} from '../animate/animation_builder';
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
@ -24,6 +23,14 @@ import {StringMapWrapper} from '../../src/facade/collection';
|
||||
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
import {DomSharedStylesHost} from './shared_styles_host';
|
||||
|
||||
import {
|
||||
AnimationKeyframe,
|
||||
AnimationStyles,
|
||||
AnimationPlayer,
|
||||
AnimationDriver
|
||||
} from '../../core_private';
|
||||
|
||||
import {EventManager} from './events/event_manager';
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
import {getDOM} from './dom_adapter';
|
||||
@ -38,12 +45,13 @@ export abstract class DomRootRenderer implements RootRenderer {
|
||||
protected registeredComponents: Map<string, DomRenderer> = new Map<string, DomRenderer>();
|
||||
|
||||
constructor(public document: any, public eventManager: EventManager,
|
||||
public sharedStylesHost: DomSharedStylesHost, public animate: AnimationBuilder) {}
|
||||
public sharedStylesHost: DomSharedStylesHost,
|
||||
public animationDriver: AnimationDriver) {}
|
||||
|
||||
renderComponent(componentProto: RenderComponentType): Renderer {
|
||||
var renderer = this.registeredComponents.get(componentProto.id);
|
||||
if (isBlank(renderer)) {
|
||||
renderer = new DomRenderer(this, componentProto);
|
||||
renderer = new DomRenderer(this, componentProto, this.animationDriver);
|
||||
this.registeredComponents.set(componentProto.id, renderer);
|
||||
}
|
||||
return renderer;
|
||||
@ -53,8 +61,9 @@ export abstract class DomRootRenderer implements RootRenderer {
|
||||
@Injectable()
|
||||
export class DomRootRenderer_ extends DomRootRenderer {
|
||||
constructor(@Inject(DOCUMENT) _document: any, _eventManager: EventManager,
|
||||
sharedStylesHost: DomSharedStylesHost, animate: AnimationBuilder) {
|
||||
super(_document, _eventManager, sharedStylesHost, animate);
|
||||
sharedStylesHost: DomSharedStylesHost,
|
||||
animationDriver: AnimationDriver) {
|
||||
super(_document, _eventManager, sharedStylesHost, animationDriver);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +72,8 @@ export class DomRenderer implements Renderer {
|
||||
private _hostAttr: string;
|
||||
private _styles: string[];
|
||||
|
||||
constructor(private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType) {
|
||||
constructor(private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType,
|
||||
private _animationDriver: AnimationDriver) {
|
||||
this._styles = _flattenStyles(componentProto.id, componentProto.styles, []);
|
||||
if (componentProto.encapsulation !== ViewEncapsulation.Native) {
|
||||
this._rootRenderer.sharedStylesHost.addStyles(this._styles);
|
||||
@ -145,14 +155,11 @@ export class DomRenderer implements Renderer {
|
||||
|
||||
attachViewAfter(node: any, viewRootNodes: any[]) {
|
||||
moveNodesAfterSibling(node, viewRootNodes);
|
||||
for (let i = 0; i < viewRootNodes.length; i++) this.animateNodeEnter(viewRootNodes[i]);
|
||||
}
|
||||
|
||||
detachView(viewRootNodes: any[]) {
|
||||
for (var i = 0; i < viewRootNodes.length; i++) {
|
||||
var node = viewRootNodes[i];
|
||||
getDOM().remove(node);
|
||||
this.animateNodeLeave(node);
|
||||
getDOM().remove(viewRootNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,39 +247,11 @@ export class DomRenderer implements Renderer {
|
||||
|
||||
setText(renderNode: any, text: string): void { getDOM().setText(renderNode, text); }
|
||||
|
||||
/**
|
||||
* Performs animations if necessary
|
||||
* @param node
|
||||
*/
|
||||
animateNodeEnter(node: Node) {
|
||||
if (getDOM().isElementNode(node) && getDOM().hasClass(node, 'ng-animate')) {
|
||||
getDOM().addClass(node, 'ng-enter');
|
||||
this._rootRenderer.animate.css()
|
||||
.addAnimationClass('ng-enter-active')
|
||||
.start(<HTMLElement>node)
|
||||
.onComplete(() => { getDOM().removeClass(node, 'ng-enter'); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If animations are necessary, performs animations then removes the element; otherwise, it just
|
||||
* removes the element.
|
||||
* @param node
|
||||
*/
|
||||
animateNodeLeave(node: Node) {
|
||||
if (getDOM().isElementNode(node) && getDOM().hasClass(node, 'ng-animate')) {
|
||||
getDOM().addClass(node, 'ng-leave');
|
||||
this._rootRenderer.animate.css()
|
||||
.addAnimationClass('ng-leave-active')
|
||||
.start(<HTMLElement>node)
|
||||
.onComplete(() => {
|
||||
getDOM().removeClass(node, 'ng-leave');
|
||||
getDOM().remove(node);
|
||||
});
|
||||
} else {
|
||||
getDOM().remove(node);
|
||||
}
|
||||
animate(element: any,
|
||||
startingStyles: AnimationStyles,
|
||||
keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
return this._animationDriver.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,136 @@
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isPresent, isNumber, StringWrapper} from '../facade/lang';
|
||||
import {BaseException, AUTO_STYLE} from '@angular/core';
|
||||
|
||||
import {
|
||||
AnimationDriver,
|
||||
AnimationPlayer,
|
||||
NoOpAnimationPlayer,
|
||||
AnimationKeyframe,
|
||||
AnimationStyles
|
||||
} from '../../core_private';
|
||||
|
||||
import {WebAnimationsPlayer} from './web_animations_player';
|
||||
|
||||
import {getDOM} from './dom_adapter';
|
||||
|
||||
export class WebAnimationsDriver implements AnimationDriver {
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
|
||||
var anyElm = <any>element;
|
||||
|
||||
var formattedSteps = [];
|
||||
var startingStyleLookup: {[key: string]: string|number}= {};
|
||||
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
|
||||
startingStyleLookup = _populateStyles(anyElm, startingStyles, {});
|
||||
startingStyleLookup['offset'] = 0;
|
||||
formattedSteps.push(startingStyleLookup);
|
||||
}
|
||||
|
||||
keyframes.forEach((keyframe: AnimationKeyframe) => {
|
||||
let data = _populateStyles(anyElm, keyframe.styles, startingStyleLookup);
|
||||
data['offset'] = keyframe.offset;
|
||||
formattedSteps.push(data);
|
||||
});
|
||||
|
||||
// this is a special case when only styles are applied as an
|
||||
// animation. When this occurs we want to animate from start to
|
||||
// end with the same values. Removing the offset and having only
|
||||
// start/end values is suitable enough for the web-animations API
|
||||
if (formattedSteps.length == 1) {
|
||||
var start = formattedSteps[0];
|
||||
start.offset = null;
|
||||
formattedSteps = [start, start];
|
||||
}
|
||||
|
||||
var player = anyElm.animate(
|
||||
formattedSteps,
|
||||
{'duration': duration, 'delay': delay, 'easing': easing, 'fill': 'forwards'});
|
||||
|
||||
return new WebAnimationsPlayer(player, duration);
|
||||
}
|
||||
}
|
||||
|
||||
function _populateStyles(element: any, styles: AnimationStyles, defaultStyles: {[key: string]: string|number}) {
|
||||
var data = {};
|
||||
styles.styles.forEach((entry) => {
|
||||
StringMapWrapper.forEach(entry, (val, prop) => {
|
||||
data[prop] = val == AUTO_STYLE
|
||||
? _computeStyle(element, prop)
|
||||
: val.toString() + _resolveStyleUnit(val, prop);
|
||||
});
|
||||
});
|
||||
StringMapWrapper.forEach(defaultStyles, (value, prop) => {
|
||||
if (!isPresent(data[prop])) {
|
||||
data[prop] = value;
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function _resolveStyleUnit(val: string | number, prop: string): string {
|
||||
var unit = '';
|
||||
if (_isPixelDimensionStyle(prop) && val != 0 && val != '0') {
|
||||
if (isNumber(val)) {
|
||||
unit = 'px';
|
||||
} else if (_findDimensionalSuffix(val.toString()).length == 0) {
|
||||
throw new BaseException('Please provide a CSS unit value for ' + prop + ':' + val);
|
||||
}
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
const _$0 = 48;
|
||||
const _$9 = 57;
|
||||
const _$PERIOD = 46;
|
||||
|
||||
function _findDimensionalSuffix(value: string): string {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
var c = StringWrapper.charCodeAt(value, i);
|
||||
if ((c >= _$0 && c <= _$9) || c == _$PERIOD) continue;
|
||||
return value.substring(i, value.length);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function _isPixelDimensionStyle(prop: string): boolean {
|
||||
switch (prop) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
case 'min-width':
|
||||
case 'min-height':
|
||||
case 'max-width':
|
||||
case 'max-height':
|
||||
case 'left':
|
||||
case 'top':
|
||||
case 'bottom':
|
||||
case 'right':
|
||||
case 'font-size':
|
||||
case 'outline-width':
|
||||
case 'outline-offset':
|
||||
case 'padding-top':
|
||||
case 'padding-left':
|
||||
case 'padding-bottom':
|
||||
case 'padding-right':
|
||||
case 'margin-top':
|
||||
case 'margin-left':
|
||||
case 'margin-bottom':
|
||||
case 'margin-right':
|
||||
case 'border-radius':
|
||||
case 'border-width':
|
||||
case 'border-top-width':
|
||||
case 'border-left-width':
|
||||
case 'border-right-width':
|
||||
case 'border-bottom-width':
|
||||
case 'text-indent':
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function _computeStyle(element: any, prop: string): string {
|
||||
return getDOM().getComputedStyle(element)[prop];
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AnimationPlayer} from '../../core_private';
|
||||
import {DomAnimatePlayer} from './dom_animate_player';
|
||||
|
||||
export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
private _subscriptions: Function[] = [];
|
||||
private _finished = false;
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
constructor(private _player: DomAnimatePlayer, public totalTime: number) {
|
||||
// this is required to make the player startable at a later time
|
||||
this.reset();
|
||||
this._player.onfinish = () => this._onFinish();
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
this._subscriptions.forEach(fn => fn());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play(): void { this._player.play(); }
|
||||
|
||||
pause(): void { this._player.pause(); }
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this._player.finish();
|
||||
}
|
||||
|
||||
reset(): void { this._player.cancel(); }
|
||||
|
||||
restart(): void {
|
||||
this.reset();
|
||||
this.play();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.reset();
|
||||
this._onFinish();
|
||||
}
|
||||
|
||||
setPosition(p): void {
|
||||
this._player.currentTime = p * this.totalTime;
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
return this._player.currentTime / this.totalTime;
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ import {MessageBus} from '../shared/message_bus';
|
||||
import {ObservableWrapper} from '../../../src/facade/async';
|
||||
import {deserializeGenericEvent} from './event_deserializer';
|
||||
|
||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles} from '../../../core_private';
|
||||
|
||||
@Injectable()
|
||||
export class WebWorkerRootRenderer implements RootRenderer {
|
||||
private _messageBroker;
|
||||
@ -241,6 +243,12 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
|
||||
this._runOnService('listenDone', [new FnArg(unlistenCallbackId, null)]);
|
||||
};
|
||||
}
|
||||
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class NamedEventEmitter {
|
||||
|
@ -152,4 +152,5 @@ export class WorkerDomAdapter extends DomAdapter {
|
||||
getAnimationPrefix(): string { throw "not implemented"; }
|
||||
getTransitionEnd(): string { throw "not implemented"; }
|
||||
supportsAnimation(): boolean { throw "not implemented"; }
|
||||
}
|
||||
supportsWebAnimation(): boolean { throw "not implemented"; }
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
APP_INITIALIZER,
|
||||
ApplicationRef
|
||||
} from "@angular/core";
|
||||
import {wtfInit} from "../core_private";
|
||||
import {wtfInit, AnimationDriver, NoOpAnimationDriver} from '../core_private';
|
||||
import {getDOM} from "./dom/dom_adapter";
|
||||
import {DomEventsPlugin} from "./dom/events/dom_events";
|
||||
import {KeyEventsPlugin} from "./dom/events/key_events";
|
||||
@ -27,8 +27,6 @@ import {HammerGesturesPlugin, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from "
|
||||
import {DOCUMENT} from "./dom/dom_tokens";
|
||||
import {DomRootRenderer, DomRootRenderer_} from "./dom/dom_renderer";
|
||||
import {DomSharedStylesHost, SharedStylesHost} from "./dom/shared_styles_host";
|
||||
import {BrowserDetails} from "./animate/browser_details";
|
||||
import {AnimationBuilder} from "./animate/animation_builder";
|
||||
import {BrowserGetTestability} from "./browser/testability";
|
||||
import {BrowserDomAdapter} from "./browser/browser_adapter";
|
||||
import {MessageBasedRenderer} from "./web_workers/ui/renderer";
|
||||
@ -96,13 +94,12 @@ export const WORKER_RENDER_APPLICATION_PROVIDERS: Array<any /*Type | Provider |
|
||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
||||
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||
Serializer,
|
||||
{provide: ON_WEB_WORKER, useValue: false},
|
||||
RenderStore,
|
||||
DomSharedStylesHost,
|
||||
Testability,
|
||||
BrowserDetails,
|
||||
AnimationBuilder,
|
||||
EventManager,
|
||||
WebWorkerInstance,
|
||||
{
|
||||
@ -192,3 +189,9 @@ function spawnWebWorker(uri: string, instance: WebWorkerInstance): void {
|
||||
|
||||
instance.init(webWorker, bus);
|
||||
}
|
||||
|
||||
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
// web workers have not been tested or configured to
|
||||
// work with animations just yet...
|
||||
return new NoOpAnimationDriver();
|
||||
}
|
||||
|
Reference in New Issue
Block a user