<!DOCTYPE html>
<html>
<head>
<base href="." />
<title>Imperative Animations Example</title>
<link rel="stylesheet" href="app.css">
<link href="https://fonts.googleapis.com/css?family=Lobster|Open+Sans:400,600" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
<script src="https://unpkg.com/zone.js/dist/zone.js"></script>
<script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import('app').catch(console.error.bind(console));
</script>
</head>
<body>
<dashboard>
Loading...
</dashboard>
</body>
</html>
.title {
color: white;
font-weight: bold;
margin-right: auto;
}
header {
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
background-color: #272727;
padding: 0.5em 1em;
}
.profile-button {
width: 40px;
cursor: pointer;
border-radius: 50%;
}
.image-container {
display: flex;
align-items: center;
}
.image-container:hover:before {
opacity: 1;
transform: translateX(-10px);
}
.image-container:before {
display: inline-block;
content: attr(data-tooltip);
color: white;
opacity: 0;
transform: translateX(-30px);
transition: opacity .2s ease, transform .2s ease;
}
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
paths: {
'npm:': 'https://unpkg.com/'
},
//map tells the System loader where to look for things
map: {
'app': './src',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'rxjs': 'npm:rxjs',
'typescript': 'npm:typescript@2.0.2/lib/typescript.js'
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {DashboardModule} from './dashboard';
platformBrowserDynamic().bootstrapModule(DashboardModule)
import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {ProfileDetailsComponent} from './profile-details'
export interface User {
name: string;
}
@Component({
selector: 'dashboard',
styleUrls: ['src/dashboard.css'],
template: `
<div>
<header>
<span class="title">Dashboard</span>
<div class="image-container" (click)="toggleProfileDetails()" data-tooltip="Profile" >
<img class="profile-button" src="https://api.adorable.io/avatars/60/me@you.com.png" />
</div>
</header>
<profile-details [user]="user" *ngIf="showProfileDetails"></profile-details>
</div>
`,
})
export class DashboardComponent {
showProfileDetails: boolean = false;
user: User;
constructor() {
this.user = {
name: 'Dominic Elm',
title: 'Frontend Developer',
location: 'Germany',
hearts: 235,
views: 23500
};
}
toggleProfileDetails() {
this.showProfileDetails = !this.showProfileDetails;
}
}
@NgModule({
imports: [ BrowserModule ],
declarations: [ DashboardComponent, ProfileDetailsComponent ],
bootstrap: [ DashboardComponent ]
})
export class DashboardModule {}
import {Component, Input, ElementRef, ViewChild, ViewChildren, QueryList} from '@angular/core'
import {User} from './dashboard.ts'
@Component({
selector: 'profile-details',
styleUrls: ['src/profile-details.css'],
template: `
<div #wrapper class="wrapper">
<header #header>
<div class="profile-image-wrapper">
<div class="profile-image-border" #profileImageBorder></div>
<img class="profile-image" #profileImage src="https://api.adorable.io/avatars/90/me@you.com.png" />
</div>
<div class="profile-header-content">
<span class="username" #username>{{ user.name }}</span>
<span class="username-title" #title>{{ user.title }}</span>
</div>
</header>
<main #main>
<ul class="stats">
<li class="stats-item">
<span class="stats-icon icon-eye" #statsIcon></span>
<span #statsText>{{ user.views }}</span>
</li>
<li class="stats-item">
<span class="stats-icon icon-location" #statsIcon></span>
<span #statsText>{{ user.location }}</span>
</li>
<li class="stats-item">
<span class="stats-icon icon-heart" #statsIcon></span>
<span #statsText>{{ user.hearts }}</span>
</li>
</ul>
</main>
</div>
`,
})
export class ProfileDetailsComponent {
@Input() user: User;
@ViewChild('wrapper') wrapper: ElementRef;
@ViewChild('main') main: ElementRef;
@ViewChild('profileImageBorder') profileImageBorder: ElementRef;
@ViewChild('profileImage') profileImage: ElementRef;
@ViewChild('username') username: ElementRef;
@ViewChild('title') title: ElementRef;
@ViewChildren('statsIcon') statsIconQueryList: QueryList<ElementRef>;
statsIcons: Element[] = [];
@ViewChildren('statsText') statsTextQueryList: QueryList<ElementRef>;
statsTexts: Element[] = [];
timeline;
constructor() {}
ngAfterViewInit() {
this.timeline = new TimelineLite();
this.statsIconQueryList.map((elementRef) => {
this.statsIcons.push(elementRef.nativeElement);
});
this.statsTextQueryList.map((elementRef) => {
this.statsTexts.push(elementRef.nativeElement);
});
this.timeline
.add('start')
.from(this.wrapper.nativeElement, .15, { opacity: 0 }, 'start')
.to(this.wrapper.nativeElement, .3, { rotationX: 0, y: 0, z: 0, ease: Power3.easeIn}, 'start')
.add('image', '-=0.1')
.add('main', '-=0.15')
.add('icons', '-=0.1')
.add('text', '-=0.05')
.from(this.profileImageBorder.nativeElement, .3, { scale: 0 }, 'image')
.from(this.profileImage.nativeElement, .3, { scale: 0, delay: .05 }, 'image')
.from(this.main.nativeElement, .4, { y: '100%' }, 'main')
.staggerFrom([this.username.nativeElement, this.title.nativeElement], .3, { opacity: 0, left: 50 }, 0.1, 'image')
.staggerFrom(this.statsIcons, .3, { opacity: 0, top: 10 }, 0.1, 'icons')
.staggerFrom(this.statsTexts, .3, { opacity: 0 }, 0.1, 'text')
.play();
}
ngOnDestory() {
this.timeline.kill();
this.timeline = null;
}
}
:host {
position: fixed;
perspective: 500px;
top: calc(50% + 30px);
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 400px;
}
.wrapper {
box-shadow: 0px 0 20px 0px rgba(0,0,0,0.2);
background: white;
transform-origin: top center;
transform: rotateX(-90deg) translateY(150px) translateZ(50px);
height: 320px;
display: flex;
flex-direction: column;
overflow: hidden;
}
header {
padding: 2em;
display: flex;
justify-content: center;
z-index: 0;
}
main {
background: #f8f8f8;
flex-grow: 1;
}
.profile-image {
border-radius: 50%;
}
.profile-image-wrapper {
position: relative;
display: inline-block;
height: 90px;
width: auto;
}
.profile-image-border {
background: #e3e3e3;
position: absolute;
top: -4px;
right: -4px;
bottom: -4px;
left: -4px;
border-radius: 50%;
z-index: -1;
}
.profile-header-content {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 1.5em;
color: #c8c8c8;
}
.profile-header-content > span {
position: relative;
}
.username {
font-weight: 600;
font-size: 1.3em;
}
.stats {
list-style: none;
display: flex;
justify-content: space-around;
padding: 2em;
}
.stats-item {
display: flex;
flex-direction: column;
align-items: center;
color: #ababab;
}
.stats-icon {
position: relative;
margin-bottom: 1em;
height: 32px;
width: 32px;
}
.icon-eye {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACyElEQVRYR+1WwXUaMRCdUQN2KohdgenAuANcgdGs78EVhFRg585KUEFwBbE7IBXEriCmAU3eJ9I+7bK74HDwhb3wQMvMn/9n/ojpgx/+4Px0BHBk4N0MeO9PVfWCmQdEdBqb+E1VV8z8y1r79p7G3gsAkhLRjaqOiGi4I8ETMy+JaLEPmF4ASBxCuGdmJN5Uq6prZl6p6lMOhJmHqjpg5pOMlaUx5q4PSCcA7/0XVZ1mNC9CCMvb21tU1/nMZrORMQaAbxIQIpqIyKLtT1sAvPcDVb3PqH5k5om19gUB2lghIvRArVrv/VksIAGBNDbFSWBqAGLyn6haVV+NMWNrbUV1fp7kwGdOOzNfWWtXKYH3fhhCmDPzZwBtnlcAyrIcM7OPf1yIyDinLHb/7yjJMzMDXMUKGCCiy5jkvKm7c24eZQFbd0VR4Ps/K86Tx8OHpl5ZgGcRaZ0E5xzYAojvIjJpxijLcsLMkBfNbAGCG8k3P7Y1S1mWoO+EmVHdpvLmE3UHSy8ict4Rp2IaINg59ydq3pkcgZxzihEsiiKZT+skJKAi0jlhWdFvYGBTWaKka74AAPqKyKe+MXwPADQ6x7n9ketyqAQIXBTF2S4JQgjXbU3YKkXWhE8ictUWfFcTRnPbNHjVhClQoxnnTQuNBvQSZ75mKrH5MMKw47Ux5iwfw8y8NqOdy71lRCEEBIefIxmcq2ZE2Tlipc1X7QljzLBpRKoKcHBGgKudt1kxXDCZCtCCjW8NK8aMY5zgbnjnlYjw3kOqHKyEEL7CsCLLMK9R06D6RgVJpslmAQRrVkQe+6bAOXepqgCX6F4jTlEUW+ZWOWFXwKgdgMDBqjVLRLh81NaxqsIdq0tKXNsAPf2vddzcAyEEVITKLvoYIKLnuBnnB19IOuwWDTcIIdSuZMYYbMDVPknzuHtdyXZUfNDxEcCRgQ9n4C9KZe8Lt5Mt/QAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
}
.icon-location {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADJElEQVRYR61XwVEcMRDUiACACAwRGCIAIuAcAUjizxEBEAH4j6QjAkMEPiLgHIFxBOAA2HbNlnSl25K0OvD+dksatXp6emZJrPF477cAHAOYENGOEGIvbF8AeCGiByJ6VEq9tYal1oXW2isiOhdCbIU9T4O9B+H9jYhulVLXLbFHAYRb/+TbAvgDYLqxsTEf3pLXvb+/H/LhRPRFCLEgoqMxNqoABodfG2OuWm4V2LpsAVEF4JybCyEOAChjzGx4uPf+EAC01sN0CGvtKRF5IcRca31UAl4EEAMAWLm5936n67pLIjpNgwKYSSmvlVIv8XtkonQBXlcD0AcyxrDa+8d7zzpgPbAQHwEs+iBEXA3HQggWIOe9/86PtZarg1nazbGQBXB3dzeRUv4AcGGMuQ2Hcwn+BkBSyolSitOzfDgdXddxGYKIdqP4rLVTIrrpuu7b2dnZwxBEFoBzjvN9EgL1TDjnGMh5KRCvSYAv08YpY+BCiO9a62krgDmAPWNMrPmeymFKcpTmKLfWcmoWWuvDZgC8MN3gnAPnXWs9qZVirByt9ZLd8G0lXoxRSgHnd1NrvR8XBgBPuVukgHIAAisv6zDAYjnO3OJrKrAhE8G4XodM1djLMhA9IBVc4gszY4zKpcE51wNP6z4RZtbMsgAS5d5rrZeGkxzwIKW8iKYTzOmGiFgfKzpJKmo71xeKRhRyuUJ5oJjL8SQwENturJZ7IprGg8J6LsFfJe2sbcXBlLgHMDPRJdntZkNzilZc845qMxqz0Vo5Rhse844xAL2N1ppJCUQi2qz4qj6QBv0oC63OOToRldpyjf6WNtzMQNJSN2smFANG5QP4m7byEuBRBkpdrpJ7Hl4va8pP9zYBCO24H8/SFp2x4th6R3vGWikItc/T0HNtxouNiIj206moppdmBgILxaEken5p8PiUBlKBdV3HrvcabtlbcRDeM4BtKeXO2L/AhzQQNyXj9nLEahnX/gsDyXASBckDCw+rPCk3C+9TDATK+d+Af7240wkAu1LKvfSfYKxPrF0Fw4Bx3A4AluN768GfBpB4Q3bYbAXyD+fiQj/I0Pj/AAAAAElFTkSuQmCC");
background-repeat: no-repeat;
}
.icon-heart {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACuUlEQVRYR8VX7XETMRDdtQuIqYC4AkwFmApIB7GkAogrIFQAKUCS04GpgKQCnAowHTgF+JZ5N9KN7nKf9kE8kx+Jtbtvn1bvbZhe+cN19Z1zn0TkipkXRISfnYjsmXmrtb5vw+ycuw6xl0nsLsT+qMaWAHjvFyLyjYiWLUWQTCmldumZEOtD0abwB2Zep7EFgJDgJxHNQvR9lmXb6XR6OB6Ps8lkchU6uyCiAzN/jInSWBF5RrfVWCK6DnlLsTkA7/1MRH6juIg8oZhSal9tw3t/icTM/C6CwBkRyYF3xC6yLNsksXOl1CEH4JxDgqWI/JlMJgt80cQhwGZZBipzEOFcLL7sEYsrfEtE91rrFaOr0D0FWh+6HkYFBBgAa63FY07vPRpFw6g3Z+fcdyL6TESPWuu24Svhwr2DCfwxFC8NZcdLQdwHIroDgPwXEVkbYwDmn3+stTfMjNf2CAAS6MBUd9I/BrrSNbw6AGst3uVF3wEck4FcM5IZ+GqMuR2jQFcOa+0tM3/JZyD5Za+1nncFj/G9cw6ih+e/LunA/3gJyQuADryJShi1oKTTY3RbY1jRb+601jeFGVlrIZGFxlfd7lwwFcN6MsbA5il1w5LGQybbdH0IoCD3vxLDKmT7xT4QjAaWC0YgTo3G1AdEcFrQjl3jOZhd4bQvNqKo8dCGc0HUFEfnJc+oXcnGANGneGkGapaPwjZFZGOMUX0oj2estZ6ZV8Fn3jcNdS0DSZIVM2PPg+f3BpEWFxFljNk0gW8FgCBr7SAQQ4q3XkGKuAKi0TMSWQdjrZ3H/J0MxIPOOdCYb7Z1yVOQ2HSgcn1mpjcAJGsCUSmeL5t9ive+gjRZFUSY8nxQ46bbt/hJAAITcaks1vKhS+3gGUi7Omctr7IzaAaqIERkG64A/0md5BknAxhyz21n/wLMOAqHi8kHLQAAAABJRU5ErkJggg==');
background-repeat: no-repeat;
transform: scale(0.8);
}
body {
font-family: 'Open Sans', sans-serif;
}