@@ -12,14 +12,15 @@ import {
1212 Input ,
1313 ElementRef ,
1414 Renderer2 ,
15- Directive ,
16- ViewChild ,
1715 SimpleChanges ,
1816 OnChanges ,
1917 ViewEncapsulation ,
18+ Optional ,
19+ Inject ,
2020} from '@angular/core' ;
2121import { CanColor , mixinColor } from '@angular/material/core' ;
2222import { Platform } from '@angular/cdk/platform' ;
23+ import { DOCUMENT } from '@angular/common' ;
2324
2425/** Possible mode for a progress spinner. */
2526export type ProgressSpinnerMode = 'determinate' | 'indeterminate' ;
@@ -31,6 +32,30 @@ export class MdProgressSpinnerBase {
3132}
3233export const _MdProgressSpinnerMixinBase = mixinColor ( MdProgressSpinnerBase , 'primary' ) ;
3334
35+ const INDETERMINATE_ANIMATION_TEMPLATE = `
36+ @keyframes mat-progress-spinner-stroke-rotate-DIAMETER {
37+ 0% { stroke-dashoffset: START_VALUE; transform: rotate(0); }
38+ 12.5% { stroke-dashoffset: END_VALUE; transform: rotate(0); }
39+ 12.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(72.5deg); }
40+ 25% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(72.5deg); }
41+
42+ 25.1% { stroke-dashoffset: START_VALUE; transform: rotate(270deg); }
43+ 37.5% { stroke-dashoffset: END_VALUE; transform: rotate(270deg); }
44+ 37.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(161.5deg); }
45+ 50% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(161.5deg); }
46+
47+ 50.01% { stroke-dashoffset: START_VALUE; transform: rotate(180deg); }
48+ 62.5% { stroke-dashoffset: END_VALUE; transform: rotate(180deg); }
49+ 62.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(251.5deg); }
50+ 75% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(251.5deg); }
51+
52+ 75.01% { stroke-dashoffset: START_VALUE; transform: rotate(90deg); }
53+ 87.5% { stroke-dashoffset: END_VALUE; transform: rotate(90deg); }
54+ 87.51% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(341.5deg); }
55+ 100% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(341.5deg); }
56+ }
57+ ` ;
58+
3459/**
3560 * <md-progress-spinner> component.
3661 */
@@ -54,13 +79,30 @@ export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase, 'pr
5479 encapsulation : ViewEncapsulation . None ,
5580 preserveWhitespaces : false ,
5681} )
57- export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements CanColor , OnChanges {
82+ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements CanColor ,
83+ OnChanges {
84+
5885 private _value : number ;
5986 private readonly _baseSize = 100 ;
6087 private readonly _baseStrokeWidth = 10 ;
88+ private _fallbackAnimation = false ;
6189
90+ /** The width and height of the host element. Will grow with stroke width. **/
6291 _elementSize = this . _baseSize ;
63- _circleRadius = 45 ;
92+
93+ /** Tracks diameters of existing instances to de-dupe generated styles (default d = 100) */
94+ static diameters = new Set < number > ( [ 100 ] ) ;
95+
96+ /** The diameter of the progress spinner (will set width and height of svg). */
97+ @Input ( )
98+ get diameter ( ) : number {
99+ return this . _diameter ;
100+ }
101+
102+ set diameter ( size : number ) {
103+ this . _setDiameterAndInitStyles ( size ) ;
104+ }
105+ _diameter = this . _baseSize ;
64106
65107 /** Stroke width of the progress spinner. */
66108 @Input ( ) strokeWidth : number = 10 ;
@@ -79,31 +121,76 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements Ca
79121 }
80122 }
81123
82- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
83- super ( renderer , elementRef ) ;
124+ constructor ( public _renderer : Renderer2 , public _elementRef : ElementRef ,
125+ platform : Platform , @Optional ( ) @Inject ( DOCUMENT ) private _document : any ) {
126+ super ( _renderer , _elementRef ) ;
84127
85- // On IE and Edge we can't animate the `stroke-dashoffset`
128+ this . _fallbackAnimation = platform . EDGE || platform . TRIDENT ;
129+
130+ // On IE and Edge, we can't animate the `stroke-dashoffset`
86131 // reliably so we fall back to a non-spec animation.
87- const animationClass = ( platform . EDGE || platform . TRIDENT ) ?
132+ const animationClass = this . _fallbackAnimation ?
88133 'mat-progress-spinner-indeterminate-fallback-animation' :
89134 'mat-progress-spinner-indeterminate-animation' ;
90135
91- renderer . addClass ( elementRef . nativeElement , animationClass ) ;
136+ _renderer . addClass ( _elementRef . nativeElement , animationClass ) ;
92137 }
93138
94139 ngOnChanges ( changes : SimpleChanges ) {
95- if ( changes . strokeWidth ) {
96- this . _elementSize = this . _baseSize + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
140+ if ( changes . strokeWidth || changes . diameter ) {
141+ this . _elementSize =
142+ this . _diameter + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
97143 }
98144 }
99145
100- _getStrokeDashOffset ( ) {
146+ /** The radius of the spinner, adjusted for stroke width. */
147+ get _circleRadius ( ) {
148+ return ( this . diameter - this . _baseStrokeWidth ) / 2 ;
149+ }
150+
151+ /** The view box of the spinner's svg element. */
152+ get _viewBox ( ) {
153+ return `0 0 ${ this . _elementSize } ${ this . _elementSize } ` ;
154+ }
155+
156+ /** The stroke circumference of the svg circle. */
157+ get _strokeCircumference ( ) : number {
158+ return 2 * Math . PI * this . _circleRadius ;
159+ }
160+
161+ /** The dash offset of the svg circle. */
162+ get _strokeDashOffset ( ) {
101163 if ( this . mode === 'determinate' ) {
102- return 2 * Math . PI * this . _circleRadius * ( 100 - this . _value ) / 100 ;
164+ return this . _strokeCircumference * ( 100 - this . _value ) / 100 ;
103165 }
104166
105167 return null ;
106168 }
169+
170+ /** Sets the diameter and adds diameter-specific styles if necessary. */
171+ private _setDiameterAndInitStyles ( size : number ) : void {
172+ this . _diameter = size ;
173+ if ( ! MdProgressSpinner . diameters . has ( this . diameter ) && ! this . _fallbackAnimation ) {
174+ this . _attachStyleNode ( ) ;
175+ }
176+ }
177+
178+ /** Dynamically generates a style tag containing the correct animation for this diameter. */
179+ private _attachStyleNode ( ) : void {
180+ const styleTag = this . _renderer . createElement ( 'style' ) ;
181+ styleTag . textContent = this . _getAnimationText ( ) ;
182+ this . _renderer . appendChild ( this . _document . head , styleTag ) ;
183+ MdProgressSpinner . diameters . add ( this . diameter ) ;
184+ }
185+
186+ /** Generates animation styles adjusted for the spinner's diameter. */
187+ private _getAnimationText ( ) : string {
188+ return INDETERMINATE_ANIMATION_TEMPLATE
189+ // Animation should begin at 5% and end at 80%
190+ . replace ( / S T A R T _ V A L U E / g, `${ 0.95 * this . _strokeCircumference } ` )
191+ . replace ( / E N D _ V A L U E / g, `${ 0.2 * this . _strokeCircumference } ` )
192+ . replace ( / D I A M E T E R / g, `${ this . diameter } ` ) ;
193+ }
107194}
108195
109196
@@ -131,8 +218,9 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements Ca
131218 preserveWhitespaces : false ,
132219} )
133220export class MdSpinner extends MdProgressSpinner {
134- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
135- super ( renderer , elementRef , platform ) ;
221+ constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ,
222+ @Optional ( ) @Inject ( DOCUMENT ) document : any ) {
223+ super ( renderer , elementRef , platform , document ) ;
136224 this . mode = 'indeterminate' ;
137225 }
138226}
0 commit comments