mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
feat(spinner): SVG spinners
This commit is contained in:
@ -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)
|
||||
|
119
ionic/components/spinner/spinner.scss
Normal file
119
ionic/components/spinner/spinner.scss
Normal 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); }
|
||||
}
|
283
ionic/components/spinner/spinner.ts
Normal file
283
ionic/components/spinner/spinner.ts
Normal 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 you’d 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 it’s 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
13
ionic/components/spinner/test/basic/index.ts
Normal file
13
ionic/components/spinner/test/basic/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {App} from 'ionic-angular';
|
||||
|
||||
|
||||
@App({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
class E2EApp {
|
||||
paused: boolean = false;
|
||||
|
||||
toggleState() {
|
||||
this.paused = !this.paused;
|
||||
}
|
||||
}
|
55
ionic/components/spinner/test/basic/main.html
Normal file
55
ionic/components/spinner/test/basic/main.html
Normal 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>
|
@ -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,
|
||||
|
@ -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',
|
||||
|
||||
|
Reference in New Issue
Block a user