feat(spinner): SVG spinners

This commit is contained in:
Adam Bradley
2016-02-25 17:23:48 -06:00
parent 4a9013e02e
commit 6c73446628
7 changed files with 479 additions and 1 deletions

View File

@ -20,7 +20,8 @@
"components/modal/modal",
"components/scroll/scroll",
"components/scroll/pull-to-refresh",
"components/slides/slides";
"components/slides/slides",
"components/spinner/spinner";
// Ionicons (to be replaced with SVGs)

View File

@ -0,0 +1,119 @@
// Spinners
// --------------------------------------------------
ion-spinner {
display: inline-block;
position: relative;
width: 28px;
height: 28px;
}
ion-spinner svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateZ(0);
}
ion-spinner.spinner-paused svg {
animation-play-state: paused;
}
// Spinner: ios / ios-small
// --------------------------------------------------
.spinner-ios line,
.spinner-ios-small line {
stroke: #69717d;;
stroke-width: 4px;
stroke-linecap: round;
}
.spinner-ios svg,
.spinner-ios-small svg {
animation: spinner-fade-out 1s linear infinite;
}
// Spinner: bubbles
// --------------------------------------------------
.spinner-bubbles circle {
fill: black;
}
.spinner-bubbles svg {
animation: spinner-scale-out 1s linear infinite;
}
// Spinner: circles
// --------------------------------------------------
.spinner-circles circle {
fill: #69717d;
}
.spinner-circles svg {
animation: spinner-fade-out 1s linear infinite;
}
// Spinner: crescent
// --------------------------------------------------
.spinner-crescent circle {
fill: transparent;
stroke: black;
stroke-width: 4px;
stroke-dasharray: 128px;
stroke-dashoffset: 82px;
}
.spinner-crescent svg {
animation: spinner-rotate 1s linear infinite;
}
// Spinner: dots
// --------------------------------------------------
.spinner-dots circle {
fill: #444;
stroke-width: 0;
}
.spinner-dots svg {
animation: spinner-dots 1s linear infinite;
transform-origin: center;
}
// Animation Keyframes
// --------------------------------------------------
@keyframes spinner-fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes spinner-scale-out {
0% { transform: scale(1, 1); }
100% { transform: scale(0, 0); }
}
@keyframes spinner-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes spinner-dots {
0% { opacity: 0.9; transform: scale(1, 1); }
50% { opacity: 0.3; transform: scale(0.4, 0.4); }
100% { opacity: 0.9; transform: scale(1, 1); }
}

View File

@ -0,0 +1,283 @@
import {Component, Input} from 'angular2/core';
import {NgStyle} from 'angular2/common';
import {Config} from '../../config/config';
/**
* @name Spinner
* @description
* The `ion-spinner` component provides a variety of animated SVG spinners.
* Spinners enables you to give users feedback that the app is actively
* processing/thinking/waiting/chillin out, or whatever youd like it to indicate.
* By default, the `ion-refresher` feature uses this spinner component while it's
* the refresher is in the `refreshing` state.
*
* Ionic offers a handful of spinners out of the box, and by default, it will use
* the appropriate spinner for the platform on which its running.
*
* <table class="table spinner-table">
* <tr>
* <th>
* <code>ios</code>
* </th>
* <td>
* <ion-spinner name="ios"></ion-spinner>
* </td>
* </tr>
* <tr>
* <th>
* <code>ios-small</code>
* </th>
* <td>
* <ion-spinner name="ios-small"></ion-spinner>
* </td>
* </tr>
* <tr>
* <th>
* <code>bubbles</code>
* </th>
* <td>
* <ion-spinner name="bubbles"></ion-spinner>
* </td>
* </tr>
* <tr>
* <th>
* <code>circles</code>
* </th>
* <td>
* <ion-spinner name="circles"></ion-spinner>
* </td>
* </tr>
* <tr>
* <th>
* <code>crescent</code>
* </th>
* <td>
* <ion-spinner name="crescent"></ion-spinner>
* </td>
* </tr>
* <tr>
* <th>
* <code>dots</code>
* </th>
* <td>
* <ion-spinner name="dots"></ion-spinner>
* </td>
* </tr>
* </table>
*
* @usage
* The following code would use the default spinner for the platform it's
* running from. If it's neither iOS or Android, it'll default to use `ios`.
*
* ```html
* <ion-spinner></ion-spinner>
* ```
*
* By setting the `name` property, you can specify which predefined spinner to
* use, no matter what the platform is.
*
* ```html
* <ion-spinner name="bubbles"></ion-spinner>
* ```
*
* ## Styling SVG with CSS
* One cool thing about SVG is its ability to be styled with CSS! One thing to note
* is that some of the CSS properties on an SVG element have different names. For
* example, SVG uses the term `stroke` instead of `border`, and `fill` instead
* of `background-color`.
*
* ```css
* ion-spinner svg {
* width: 28px;
* height: 28px;
* stroke: #444;
* fill: #222;
* }
* ```
*/
@Component({
selector: 'ion-spinner',
template:
'<svg viewBox="0 0 64 64" *ngFor="#i of _c" [ngStyle]="i.style">' +
'<circle [attr.r]="i.r" transform="translate(32,32)"></circle>' +
'</svg>' +
'<svg viewBox="0 0 64 64" *ngFor="#i of _l" [ngStyle]="i.style">' +
'<line [attr.y1]="i.y1" [attr.y2]="i.y2" transform="translate(32,32)"></line>' +
'</svg>',
directives: [NgStyle],
host: {
'[class]': '_applied',
'[class.spinner-paused]': 'paused'
}
})
export class Spinner {
private _c: any[];
private _l: any[];
private _name: string;
private _dur: number = null;
private _init: boolean;
private _applied: string;
/**
* @input {string} SVG spinner name.
*/
@Input()
get name(): string {
return this._name;
}
set name(val: string) {
this._name = val;
this.load();
}
/**
* @input {string} How long it takes it to do one loop.
*/
@Input()
get duration(): number {
return this._dur;
}
set duration(val: number) {
this._dur = val;
this.load();
}
/**
* @input {string} If the animation is paused or not. Defaults to `false`.
*/
@Input() paused: boolean = false;
constructor(private _config: Config) {}
ngOnInit() {
this._init = true;
this.load();
}
load() {
if (this._init) {
this._l = [];
this._c = [];
var name = this._name || this._config.get('spinner', 'ios');
const spinner = SPINNERS[name];
if (spinner) {
this._applied = 'spinner-' + name;
if (spinner.lines) {
for (var i = 0, l = spinner.lines; i < l; i++) {
this._l.push( this._loadEle(spinner, i, l) );
}
} else if (spinner.circles) {
for (var i = 0, l = spinner.circles; i < l; i++) {
this._c.push( this._loadEle(spinner, i, l) );
}
}
}
}
}
_loadEle(spinner: any, index: number, total: number) {
let duration = this._dur || spinner.dur
let data = spinner.fn(duration, index, total);
data.style.animationDuration = duration + 'ms';
return data;
}
}
const SPINNERS = {
ios: {
dur: 1000,
lines: 12,
fn: function(dur, index, total) {
return {
y1: 17,
y2: 29,
style: {
transform: 'rotate(' + (30 * index + (index < 6 ? 180 : -180)) + 'deg)',
animationDelay: -(dur - ((dur / total) * index)) + 'ms'
}
}
}
},
'ios-small': {
dur: 1000,
lines: 12,
fn: function(dur, index, total) {
return {
y1: 12,
y2: 20,
style: {
transform: 'rotate(' + (30 * index + (index < 6 ? 180 : -180)) + 'deg)',
animationDelay: -(dur - ((dur / total) * index)) + 'ms'
}
}
}
},
bubbles: {
dur: 1000,
circles: 9,
fn: function(dur, index, total) {
return {
r: 5,
style: {
top: 9 * Math.sin(2 * Math.PI * index / total),
left: 9 * Math.cos(2 * Math.PI * index / total),
animationDelay: -(dur - ((dur / total) * index)) + 'ms'
}
}
}
},
circles: {
dur: 1000,
circles: 8,
fn: function(dur, index, total) {
return {
r: 5,
style: {
top: 9 * Math.sin(2 * Math.PI * index / total),
left: 9 * Math.cos(2 * Math.PI * index / total),
animationDelay: -(dur - ((dur / total) * index)) + 'ms'
}
}
}
},
crescent: {
dur: 750,
circles: 1,
fn: function(dur) {
return {
r: 26,
style: {}
}
}
},
dots: {
dur: 750,
circles: 3,
fn: function(dur, index, total) {
return {
r: 6,
style: {
left: (9 - (9 * index)),
animationDelay: -(110 * index) + 'ms'
}
}
}
}
};

View File

@ -0,0 +1,13 @@
import {App} from 'ionic-angular';
@App({
templateUrl: 'main.html'
})
class E2EApp {
paused: boolean = false;
toggleState() {
this.paused = !this.paused;
}
}

View File

@ -0,0 +1,55 @@
<ion-toolbar>
<ion-title>Spinners</ion-title>
</ion-toolbar>
<ion-content padding>
<table style="border-collapse:initial; border-spacing: 20px">
<tr>
<td>Platform Default</td>
<td>
<ion-spinner [paused]="paused"></ion-spinner>
</td>
</tr>
<tr>
<td>ios</td>
<td>
<ion-spinner name="ios" duration="1000" [paused]="paused"></ion-spinner>
</td>
</tr>
<tr>
<td>ios-small</td>
<td>
<ion-spinner name="ios-small" duration="1000" [paused]="paused"></ion-spinner>
</td>
</tr>
<tr>
<td>bubbles</td>
<td>
<ion-spinner name="bubbles" [paused]="paused"></ion-spinner>
</td>
</tr>
<tr>
<td>circles</td>
<td>
<ion-spinner name="circles" [paused]="paused"></ion-spinner>
</td>
</tr>
<tr>
<td>crescent</td>
<td>
<ion-spinner name="crescent" [paused]="paused"></ion-spinner>
</td>
</tr>
<tr>
<td>dots</td>
<td>
<ion-spinner name="dots" [paused]="paused"></ion-spinner>
</td>
</tr>
</table>
<button (click)="toggleState()">Toggle Paused</button>
</ion-content>

View File

@ -19,6 +19,7 @@ import {Item} from '../components/item/item';
import {ItemSliding} from '../components/item/item-sliding';
import {Toolbar, ToolbarTitle, ToolbarItem} from '../components/toolbar/toolbar';
import {Icon} from '../components/icon/icon';
import {Spinner} from '../components/spinner/spinner';
import {Checkbox} from '../components/checkbox/checkbox';
import {Select} from '../components/select/select';
import {Option} from '../components/option/option';
@ -80,6 +81,7 @@ import {ShowWhen, HideWhen} from '../components/show-hide-when/show-hide-when';
*
* **Media**
* - Icon
* - Spinner
*
* **Forms**
* - Searchbar
@ -146,6 +148,7 @@ export const IONIC_DIRECTIVES = [
// Media
Icon,
Spinner,
// Forms
Searchbar,

View File

@ -25,6 +25,8 @@ Config.setModeConfig('ios', {
pageTransition: 'ios-transition',
pageTransitionDelay: 16,
spinner: 'ios',
tabbarPlacement: 'bottom',
});
@ -52,6 +54,8 @@ Config.setModeConfig('md', {
pageTransition: 'md-transition',
pageTransitionDelay: 96,
spinner: 'crescent',
tabbarHighlight: true,
tabbarPlacement: 'top',