Skip to content

Commit bdec4ab

Browse files
committed
feat(library): Added animations
- Added animation service as well as animation presets - Defined animation models - Added web-animations dependency - Added notifier configuration, including animation options
1 parent 9ca8342 commit bdec4ab

12 files changed

+317
-46
lines changed

demo/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<script src="./../node_modules/reflect-metadata/Reflect.js"></script>
1313
<script src="./../node_modules/zone.js/dist/zone.js"></script>
1414
<script src="./../node_modules/systemjs/dist/system.src.js"></script>
15+
<script src="./../node_modules/web-animations-js/web-animations-next.min.js"></script>
1516

1617
<!-- Configure and run SystemJS module loader -->
1718
<script src="./systemjs.config.js"></script>

gulp-tasks/watch.task.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ const buildTask = require( './build.task' );
1010
/**
1111
* Gulp task: Browser-sync reload (helper only)
1212
*/
13-
gulp.task( 'watch--reload', ( done ) => {
13+
gulp.task( 'watch:reload', ( done ) => {
1414
browserSync.reload();
1515
done();
1616
} );
1717

1818
/**
1919
* Gulp task: Start watcher for build tasks (note: paths muts be written without dots in the beginning!!)
2020
*/
21-
gulp.task( 'watch--build',
21+
gulp.task( 'watch:build',
2222
gulp.series( [
2323
gulp.parallel( [
2424
'build--dev',
@@ -100,7 +100,7 @@ gulp.task( 'watch--build',
100100
/**
101101
* Gulp task: Start test watcher (best in combination with the build watcher)
102102
*/
103-
gulp.task( 'watch--test', ( done ) => {
103+
gulp.task( 'watch:test', ( done ) => {
104104
new karma.Server( {
105105
configFile: `${__dirname }/../karma.config.js`
106106
}, done ).start();

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"peerDependencies": {
4141
"@angular/core": "2.x",
4242
"@angular/common": "2.x",
43-
"rxjs": "5.0.0-beta.12"
43+
"rxjs": "5.0.0-beta.12",
44+
"web-animations-js": "2.2.x"
4445
},
4546
"devDependencies": {
4647
"@angular/core": "2.x",
@@ -80,6 +81,7 @@
8081
"tslint": "3.15.x",
8182
"typescript": "2.0.x",
8283
"typings": "1.4.x",
84+
"web-animations-js": "2.2.x",
8385
"zone.js": "0.6.x"
8486
}
8587
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NotifierNotification } from './../models/notifier-notification.model';
2+
import { NotifierAnimationKeyframes, NotifierAnimationPreset } from './../models/notifier-animation.model';
3+
4+
/**
5+
* Fade animation preset
6+
*/
7+
export const fade: NotifierAnimationPreset = {
8+
in: ( notification: NotifierNotification ): NotifierAnimationKeyframes => {
9+
return {
10+
from: {
11+
opacity: 0
12+
},
13+
to: {
14+
opacity: 1
15+
}
16+
};
17+
},
18+
out: ( notification: NotifierNotification ): NotifierAnimationKeyframes => {
19+
return {
20+
from: {
21+
opacity: 1
22+
},
23+
to: {
24+
opacity: 0
25+
}
26+
};
27+
}
28+
};

src/components/notifier-container.component.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class NotifierContainerComponent implements OnInit, OnDestroy {
9898
*/
9999
public onComponentInit( componentRef: NotifierNotificationComponent ): void {
100100
let currentNotification: NotifierNotification = this.notifications[ this.notifications.length - 1 ];
101-
currentNotification.componentRef = componentRef; // Save component Ref
101+
currentNotification.component = componentRef; // Save component Ref
102102
this.showNotification( currentNotification );
103103
};
104104

@@ -139,10 +139,10 @@ export class NotifierContainerComponent implements OnInit, OnDestroy {
139139
} else {
140140
this.hideNotification( action.payload );
141141
const notificationIndex: number = this.notifications.findIndex( ( notification: NotifierNotification ) => {
142-
return notification.componentRef === action.payload.componentRef;
142+
return notification.component === action.payload.component;
143143
} );
144144
const otherNotifications: Array<NotifierNotification> = this.notifications.slice( 0, notificationIndex );
145-
this.shiftNotifications( otherNotifications, action.payload.componentRef.currentElementHeight, false ).then( resolve );
145+
this.shiftNotifications( otherNotifications, action.payload.component.elementHeight, false ).then( resolve );
146146
}
147147

148148
} );
@@ -155,13 +155,13 @@ export class NotifierContainerComponent implements OnInit, OnDestroy {
155155

156156
// 1. Decision: First notification in the list?
157157
if ( this.notifications.length === 1 ) {
158-
notification.componentRef.show().then( this.tempPromiseResolver );
158+
notification.component.show().then( this.tempPromiseResolver );
159159
} else {
160160

161161
// 2. Decision: Stacking enabled? (same as stacking value below 2)
162162
if ( this.config.behaviour.stacking === false || this.config.behaviour.stacking < 2 ) {
163163
this.hideNotification( this.notifications[ 0 ] ).then( () => {
164-
notification.componentRef.show().then( this.tempPromiseResolver );
164+
notification.component.show().then( this.tempPromiseResolver );
165165
} );
166166
} else {
167167

@@ -170,14 +170,14 @@ export class NotifierContainerComponent implements OnInit, OnDestroy {
170170

171171
this.hideNotification( this.notifications[ 0 ] ); // Hide oldest notification
172172
const otherNotifications: Array<NotifierNotification> = this.notifications.slice( 1, this.notifications.length - 1 );
173-
this.shiftNotifications( otherNotifications, notification.componentRef.currentElementHeight, true );
174-
notification.componentRef.show().then( this.tempPromiseResolver ); // DONE
173+
this.shiftNotifications( otherNotifications, notification.component.elementHeight, true );
174+
notification.component.show().then( this.tempPromiseResolver ); // DONE
175175

176176
} else {
177177

178178
const otherNotifications: Array<NotifierNotification> = this.notifications.slice( 0, this.notifications.length - 1 );
179-
this.shiftNotifications( otherNotifications, notification.componentRef.currentElementHeight, true );
180-
notification.componentRef.show().then( this.tempPromiseResolver ); // DONE
179+
this.shiftNotifications( otherNotifications, notification.component.elementHeight, true );
180+
notification.component.show().then( this.tempPromiseResolver ); // DONE
181181

182182
}
183183

@@ -192,9 +192,9 @@ export class NotifierContainerComponent implements OnInit, OnDestroy {
192192
*/
193193
private hideNotification( notification: NotifierNotification ): Promise<null> {
194194
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
195-
notification.componentRef.hide().then( () => {
195+
notification.component.hide().then( () => {
196196
this.notifications = this.notifications.filter( ( currentNotification: NotifierNotification ) => {
197-
return currentNotification.componentRef !== notification.componentRef;
197+
return currentNotification.component !== notification.component;
198198
} );
199199
resolve(); // DONE
200200
} );
@@ -207,7 +207,7 @@ export class NotifierContainerComponent implements OnInit, OnDestroy {
207207
private shiftNotifications( notifications: Array<NotifierNotification>, distance: number, shiftToMakePlace: boolean ): Promise<null> {
208208
let shiftPromises: Array<Promise<null>> = [];
209209
for ( let notification of notifications ) {
210-
shiftPromises.push( notification.componentRef.shift( distance, shiftToMakePlace ) );
210+
shiftPromises.push( notification.component.shift( distance, shiftToMakePlace ) );
211211
}
212212
return Promise.all( shiftPromises ); // DONE
213213
}

src/components/notifier-notification.component.ts

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Optional, Output, Renderer } from '@angular/core';
22

3+
import { NotifierAnimationService } from './../services/notifier-animation.service';
4+
import { NoifierAnimationData } from './../models/notifier-animation.model';
35
import { NotifierConfigGlobal } from './../models/notifier-config-global.model';
46
import { NotifierNotification } from './../models/notifier-notification.model';
57

@@ -38,15 +40,30 @@ export class NotifierNotificationComponent implements AfterViewInit {
3840
@Output()
3941
public dismiss: EventEmitter<NotifierNotification>;
4042

43+
/**
44+
* Global configuration
45+
*/
46+
public config: NotifierConfigGlobal;
47+
4148
/**
4249
* Current notification height, calculated and cached here (#perfmatters)
4350
*/
44-
public currentElementHeight: number;
51+
public elementHeight: number;
4552

4653
/**
47-
* Global configuration
54+
* Current notification width, calculated and cached here (#perfmatters)
55+
*/
56+
public elementWidth: number;
57+
58+
/**
59+
* Current notification shift, calculated and cached here (#perfmatters)
4860
*/
49-
private config: NotifierConfigGlobal;
61+
public elementShift: number;
62+
63+
/**
64+
* Notifier animation service
65+
*/
66+
private animationService: NotifierAnimationService;
5067

5168
/**
5269
* Native element reference, used for manipulating DOM properties
@@ -58,23 +75,22 @@ export class NotifierNotificationComponent implements AfterViewInit {
5875
*/
5976
private renderer: Renderer;
6077

61-
/**
62-
* Current notification shift, calculated and cached here (#perfmatters)
63-
*/
64-
private currentElementShift: number;
65-
6678
/**
6779
* Constructor
6880
* @param {ElementRef} elementRef Component element reference
6981
* @param {Renderer} renderer Angular rendering service
7082
*/
71-
public constructor( elementRef: ElementRef, renderer: Renderer, @Optional() config: NotifierConfigGlobal ) {
83+
public constructor( animationService: NotifierAnimationService,
84+
elementRef: ElementRef,
85+
renderer: Renderer,
86+
@Optional() config: NotifierConfigGlobal ) {
7287
this.config = config === null ? new NotifierConfigGlobal() : config;
7388
this.init = new EventEmitter<NotifierNotificationComponent>();
7489
this.dismiss = new EventEmitter<NotifierNotification>();
7590
this.element = elementRef.nativeElement;
91+
this.animationService = animationService;
7692
this.renderer = renderer;
77-
this.currentElementShift = 0;
93+
this.elementShift = 0;
7894
}
7995

8096
/**
@@ -83,7 +99,8 @@ export class NotifierNotificationComponent implements AfterViewInit {
8399
*/
84100
public ngAfterViewInit(): void {
85101
this.setup();
86-
this.currentElementHeight = this.element.offsetHeight; // Calculate current notification height and cache it (#perfmatters)
102+
this.elementHeight = this.element.offsetHeight;
103+
this.elementWidth = this.element.offsetWidth;
87104
this.init.emit( this );
88105
}
89106

@@ -92,20 +109,40 @@ export class NotifierNotificationComponent implements AfterViewInit {
92109
* @returns {Promise<null>} Empty promise, resolved when finished
93110
*/
94111
public show(): Promise<null> {
95-
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
112+
113+
// Decision: Are animations enabled?
114+
if ( this.config.animations.enabled ) {
115+
const animationData: NoifierAnimationData = this.animationService.getAnimationData( 'in', this.notification );
116+
for ( let attribute in animationData.keyframes[ 0 ] ) { // Set pre-animation styles to prevent flickering
117+
this.renderer.setElementStyle( this.element, attribute, animationData.keyframes[ 0 ][ attribute ] );
118+
}
96119
this.renderer.setElementStyle( this.element, 'visibility', 'visible' );
97-
resolve();
98-
} );
120+
return this.element.animate( animationData.keyframes, animationData.options ).finished; // Returns a promise
121+
} else {
122+
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
123+
this.renderer.setElementStyle( this.element, 'visibility', 'visible' );
124+
resolve();
125+
} );
126+
}
127+
99128
}
100129

101130
/**
102131
* Hide (animate out) notification
103132
* @returns {Promise<null>} Empty promise, resolved when finished
104133
*/
105134
public hide(): Promise<null> {
106-
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
107-
resolve();
108-
} );
135+
136+
// Decision: Are animations enabled?
137+
if ( this.config.animations.enabled ) {
138+
const animationData: NoifierAnimationData = this.animationService.getAnimationData( 'out', this.notification );
139+
return this.element.animate( animationData.keyframes, animationData.options ).finished; // Returns a promise
140+
} else {
141+
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
142+
resolve();
143+
} );
144+
}
145+
109146
}
110147

111148
/**
@@ -119,23 +156,23 @@ export class NotifierNotificationComponent implements AfterViewInit {
119156
switch ( this.config.position.verticalPosition ) { // TODO: Maybe if-else ??
120157
case 'top':
121158
if ( shiftToMakePlace ) {
122-
newElementShift = this.currentElementShift + distance + this.config.position.verticalGap;
159+
newElementShift = this.elementShift + distance + this.config.position.verticalGap;
123160
} else {
124-
newElementShift = this.currentElementShift - distance - this.config.position.verticalGap;
161+
newElementShift = this.elementShift - distance - this.config.position.verticalGap;
125162
}
126163
break;
127164
case 'bottom':
128165
if ( shiftToMakePlace ) {
129-
newElementShift = this.currentElementShift - distance - this.config.position.verticalGap;
166+
newElementShift = this.elementShift - distance - this.config.position.verticalGap;
130167
} else {
131-
newElementShift = this.currentElementShift + distance + this.config.position.verticalGap;
168+
newElementShift = this.elementShift + distance + this.config.position.verticalGap;
132169
}
133170
}
134171
let horizontalPosition: string = this.config.position.horizontalPosition === 'middle' ? '-50%' : '0';
135172

136173
// Shift
137174
this.renderer.setElementStyle( this.element, 'transform', `translate3d( ${ horizontalPosition }, ${ newElementShift }px, 0 )` );
138-
this.currentElementShift = newElementShift;
175+
this.elementShift = newElementShift;
139176
resolve(); // DONE
140177

141178
} );
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { NotifierNotification } from './notifier-notification.model';
2+
3+
/**
4+
* Notifier animation, similar to the Web Animation API syntax
5+
*/
6+
export interface NoifierAnimationData {
7+
8+
/**
9+
* Animation keyframes, first entry for animate-in, second entry for animate-out
10+
*/
11+
keyframes: Array<{
12+
[ property: string ]: any;
13+
}>;
14+
15+
/**
16+
* Futher animation options
17+
*/
18+
options: {
19+
20+
/**
21+
* Animation delay, in ms
22+
*/
23+
delay: number;
24+
25+
/**
26+
* Animation duration, in ms
27+
*/
28+
duration: number;
29+
30+
/**
31+
* Animation easing function (cp. CSS easing attributes)
32+
*/
33+
easing: string;
34+
35+
/**
36+
* Animation fill mode
37+
*/
38+
fill: string;
39+
40+
};
41+
42+
}
43+
44+
/**
45+
* Notifier animation keyframes
46+
*/
47+
export interface NotifierAnimationKeyframes {
48+
49+
/**
50+
* CSS attributes before the animation starts
51+
*/
52+
from: {
53+
[ property: string ]: any
54+
};
55+
56+
/**
57+
* CSS attributes after the animation ends
58+
*/
59+
to: {
60+
[ property: string ]: any
61+
};
62+
63+
}
64+
65+
/**
66+
* Notifier animation preset
67+
*/
68+
export interface NotifierAnimationPreset {
69+
70+
/**
71+
* Function generating the keyframes for animating-in
72+
*/
73+
in: ( notification: NotifierNotification ) => NotifierAnimationKeyframes;
74+
75+
/**
76+
* Function generating the keyframes for animating-out
77+
*/
78+
out: ( notification: NotifierNotification ) => NotifierAnimationKeyframes;
79+
80+
}

0 commit comments

Comments
 (0)