@@ -15,20 +15,47 @@ import {
1515 SimpleChanges ,
1616 OnChanges ,
1717 ViewEncapsulation ,
18+ Optional ,
19+ Inject ,
1820} from '@angular/core' ;
1921import { CanColor , mixinColor } from '@angular/material/core' ;
2022import { Platform } from '@angular/cdk/platform' ;
23+ import { DOCUMENT } from '@angular/common' ;
2124
2225/** Possible mode for a progress spinner. */
2326export type ProgressSpinnerMode = 'determinate' | 'indeterminate' ;
2427
25- // Boilerplate for applying mixins to MdProgressSpinner .
28+ // Boilerplate for applying mixins to MatProgressSpinner .
2629/** @docs -private */
2730export class MatProgressSpinnerBase {
2831 constructor ( public _renderer : Renderer2 , public _elementRef : ElementRef ) { }
2932}
3033export const _MatProgressSpinnerMixinBase = mixinColor ( MatProgressSpinnerBase , 'primary' ) ;
3134
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+
3259/**
3360 * <mat-progress-spinner> component.
3461 */
@@ -53,13 +80,30 @@ export const _MatProgressSpinnerMixinBase = mixinColor(MatProgressSpinnerBase, '
5380 encapsulation : ViewEncapsulation . None ,
5481 preserveWhitespaces : false ,
5582} )
56- export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements CanColor , OnChanges {
83+ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements CanColor ,
84+ OnChanges {
85+
5786 private _value : number ;
5887 private readonly _baseSize = 100 ;
5988 private readonly _baseStrokeWidth = 10 ;
89+ private _fallbackAnimation = false ;
6090
91+ /** The width and height of the host element. Will grow with stroke width. **/
6192 _elementSize = this . _baseSize ;
62- _circleRadius = 45 ;
93+
94+ /** Tracks diameters of existing instances to de-dupe generated styles (default d = 100) */
95+ static diameters = new Set < number > ( [ 100 ] ) ;
96+
97+ /** The diameter of the progress spinner (will set width and height of svg). */
98+ @Input ( )
99+ get diameter ( ) : number {
100+ return this . _diameter ;
101+ }
102+
103+ set diameter ( size : number ) {
104+ this . _setDiameterAndInitStyles ( size ) ;
105+ }
106+ _diameter = this . _baseSize ;
63107
64108 /** Stroke width of the progress spinner. */
65109 @Input ( ) strokeWidth : number = 10 ;
@@ -78,31 +122,76 @@ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements
78122 }
79123 }
80124
81- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
82- super ( renderer , elementRef ) ;
125+ constructor ( public _renderer : Renderer2 , public _elementRef : ElementRef ,
126+ platform : Platform , @Optional ( ) @Inject ( DOCUMENT ) private _document : any ) {
127+ super ( _renderer , _elementRef ) ;
83128
84- // On IE and Edge we can't animate the `stroke-dashoffset`
129+ this . _fallbackAnimation = platform . EDGE || platform . TRIDENT ;
130+
131+ // On IE and Edge, we can't animate the `stroke-dashoffset`
85132 // reliably so we fall back to a non-spec animation.
86- const animationClass = ( platform . EDGE || platform . TRIDENT ) ?
133+ const animationClass = this . _fallbackAnimation ?
87134 'mat-progress-spinner-indeterminate-fallback-animation' :
88135 'mat-progress-spinner-indeterminate-animation' ;
89136
90- renderer . addClass ( elementRef . nativeElement , animationClass ) ;
137+ _renderer . addClass ( _elementRef . nativeElement , animationClass ) ;
91138 }
92139
93140 ngOnChanges ( changes : SimpleChanges ) {
94- if ( changes . strokeWidth ) {
95- this . _elementSize = this . _baseSize + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
141+ if ( changes . strokeWidth || changes . diameter ) {
142+ this . _elementSize =
143+ this . _diameter + Math . max ( this . strokeWidth - this . _baseStrokeWidth , 0 ) ;
96144 }
97145 }
98146
99- _getStrokeDashOffset ( ) {
147+ /** The radius of the spinner, adjusted for stroke width. */
148+ get _circleRadius ( ) {
149+ return ( this . diameter - this . _baseStrokeWidth ) / 2 ;
150+ }
151+
152+ /** The view box of the spinner's svg element. */
153+ get _viewBox ( ) {
154+ return `0 0 ${ this . _elementSize } ${ this . _elementSize } ` ;
155+ }
156+
157+ /** The stroke circumference of the svg circle. */
158+ get _strokeCircumference ( ) : number {
159+ return 2 * Math . PI * this . _circleRadius ;
160+ }
161+
162+ /** The dash offset of the svg circle. */
163+ get _strokeDashOffset ( ) {
100164 if ( this . mode === 'determinate' ) {
101- return 2 * Math . PI * this . _circleRadius * ( 100 - this . _value ) / 100 ;
165+ return this . _strokeCircumference * ( 100 - this . _value ) / 100 ;
102166 }
103167
104168 return null ;
105169 }
170+
171+ /** Sets the diameter and adds diameter-specific styles if necessary. */
172+ private _setDiameterAndInitStyles ( size : number ) : void {
173+ this . _diameter = size ;
174+ if ( ! MatProgressSpinner . diameters . has ( this . diameter ) && ! this . _fallbackAnimation ) {
175+ this . _attachStyleNode ( ) ;
176+ }
177+ }
178+
179+ /** Dynamically generates a style tag containing the correct animation for this diameter. */
180+ private _attachStyleNode ( ) : void {
181+ const styleTag = this . _renderer . createElement ( 'style' ) ;
182+ styleTag . textContent = this . _getAnimationText ( ) ;
183+ this . _renderer . appendChild ( this . _document . head , styleTag ) ;
184+ MatProgressSpinner . diameters . add ( this . diameter ) ;
185+ }
186+
187+ /** Generates animation styles adjusted for the spinner's diameter. */
188+ private _getAnimationText ( ) : string {
189+ return INDETERMINATE_ANIMATION_TEMPLATE
190+ // Animation should begin at 5% and end at 80%
191+ . replace ( / S T A R T _ V A L U E / g, `${ 0.95 * this . _strokeCircumference } ` )
192+ . replace ( / E N D _ V A L U E / g, `${ 0.2 * this . _strokeCircumference } ` )
193+ . replace ( / D I A M E T E R / g, `${ this . diameter } ` ) ;
194+ }
106195}
107196
108197
@@ -130,8 +219,9 @@ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements
130219 preserveWhitespaces : false ,
131220} )
132221export class MatSpinner extends MatProgressSpinner {
133- constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ) {
134- super ( renderer , elementRef , platform ) ;
222+ constructor ( renderer : Renderer2 , elementRef : ElementRef , platform : Platform ,
223+ @Optional ( ) @Inject ( DOCUMENT ) document : any ) {
224+ super ( renderer , elementRef , platform , document ) ;
135225 this . mode = 'indeterminate' ;
136226 }
137227}
0 commit comments