6
6
ChangeDetectorRef ,
7
7
ChangeDetectionStrategy ,
8
8
OnDestroy ,
9
- Input
9
+ Input ,
10
+ ElementRef ,
11
+ NgZone
10
12
} from '@angular/core' ;
11
13
12
14
// TODO(josephperrott): Benchpress tests.
@@ -21,7 +23,8 @@ const DURATION_DETERMINATE = 225;
21
23
const startIndeterminate = 3 ;
22
24
/** End animation value of the indeterminate animation */
23
25
const endIndeterminate = 80 ;
24
-
26
+ /* Maximum angle for the arc. The angle can't be exactly 360, because the arc becomes hidden. */
27
+ const MAX_ANGLE = 359.99 / 100 ;
25
28
26
29
export type ProgressCircleMode = 'determinate' | 'indeterminate' ;
27
30
@@ -51,6 +54,9 @@ export class MdProgressCircle implements OnDestroy {
51
54
/** The id of the indeterminate interval. */
52
55
private _interdeterminateInterval : number ;
53
56
57
+ /** The SVG <path> node that is used to draw the circle. */
58
+ private _path : SVGPathElement ;
59
+
54
60
/**
55
61
* Values for aria max and min are only defined as numbers when in a determinate mode. We do this
56
62
* because voiceover does not report the progress indicator as indeterminate if the aria min
@@ -74,20 +80,6 @@ export class MdProgressCircle implements OnDestroy {
74
80
this . _interdeterminateInterval = interval ;
75
81
}
76
82
77
- /** The current path value, representing the progress circle. */
78
- private _currentPath : string ;
79
-
80
- /** TODO: internal */
81
- get currentPath ( ) {
82
- return this . _currentPath ;
83
- }
84
- set currentPath ( path : string ) {
85
- this . _currentPath = path ;
86
- // Mark for check as our ChangeDetectionStrategy is OnPush, when changes come from within the
87
- // component, change detection must be called for.
88
- this . _changeDetectorRef . markForCheck ( ) ;
89
- }
90
-
91
83
/** Clean up any animations that were running. */
92
84
ngOnDestroy ( ) {
93
85
this . _cleanupIndeterminateAnimation ( ) ;
@@ -136,8 +128,11 @@ export class MdProgressCircle implements OnDestroy {
136
128
}
137
129
private _mode : ProgressCircleMode = 'determinate' ;
138
130
139
- constructor ( private _changeDetectorRef : ChangeDetectorRef ) {
140
- }
131
+ constructor (
132
+ private _changeDetectorRef : ChangeDetectorRef ,
133
+ private _ngZone : NgZone ,
134
+ private _elementRef : ElementRef
135
+ ) { }
141
136
142
137
143
138
/**
@@ -152,29 +147,33 @@ export class MdProgressCircle implements OnDestroy {
152
147
*/
153
148
private _animateCircle ( animateFrom : number , animateTo : number , ease : EasingFn ,
154
149
duration : number , rotation : number ) {
150
+
155
151
let id = ++ this . _lastAnimationId ;
156
152
let startTime = Date . now ( ) ;
157
153
let changeInValue = animateTo - animateFrom ;
158
154
159
155
// No need to animate it if the values are the same
160
156
if ( animateTo === animateFrom ) {
161
- this . currentPath = getSvgArc ( animateTo , rotation ) ;
157
+ this . _renderArc ( animateTo , rotation ) ;
162
158
} else {
163
159
let animation = ( ) => {
164
160
let elapsedTime = Math . max ( 0 , Math . min ( Date . now ( ) - startTime , duration ) ) ;
165
161
166
- this . currentPath = getSvgArc (
162
+ this . _renderArc (
167
163
ease ( elapsedTime , animateFrom , changeInValue , duration ) ,
168
164
rotation
169
165
) ;
170
166
171
167
// Prevent overlapping animations by checking if a new animation has been called for and
172
- // if the animation has lasted long than the animation duration.
168
+ // if the animation has lasted longer than the animation duration.
173
169
if ( id === this . _lastAnimationId && elapsedTime < duration ) {
174
170
requestAnimationFrame ( animation ) ;
175
171
}
176
172
} ;
177
- requestAnimationFrame ( animation ) ;
173
+
174
+ // Run the animation outside of Angular's zone, in order to avoid
175
+ // hitting ZoneJS and change detection on each frame.
176
+ this . _ngZone . runOutsideAngular ( animation ) ;
178
177
}
179
178
}
180
179
@@ -197,9 +196,10 @@ export class MdProgressCircle implements OnDestroy {
197
196
} ;
198
197
199
198
if ( ! this . interdeterminateInterval ) {
200
- this . interdeterminateInterval = setInterval (
201
- animate , duration + 50 , 0 , false ) ;
202
- animate ( ) ;
199
+ this . _ngZone . runOutsideAngular ( ( ) => {
200
+ this . interdeterminateInterval = setInterval ( animate , duration + 50 , 0 , false ) ;
201
+ animate ( ) ;
202
+ } ) ;
203
203
}
204
204
}
205
205
@@ -210,6 +210,21 @@ export class MdProgressCircle implements OnDestroy {
210
210
private _cleanupIndeterminateAnimation ( ) {
211
211
this . interdeterminateInterval = null ;
212
212
}
213
+
214
+ /**
215
+ * Renders the arc onto the SVG element. Proxies `getArc` while setting the proper
216
+ * DOM attribute on the `<path>`.
217
+ */
218
+ private _renderArc ( currentValue : number , rotation : number ) {
219
+ // Caches the path reference so it doesn't have to be looked up every time.
220
+ let path = this . _path = this . _path || this . _elementRef . nativeElement . querySelector ( 'path' ) ;
221
+
222
+ // Ensure that the path was found. This may not be the case if the
223
+ // animation function fires too early.
224
+ if ( path ) {
225
+ path . setAttribute ( 'd' , getSvgArc ( currentValue , rotation ) ) ;
226
+ }
227
+ }
213
228
}
214
229
215
230
@@ -230,8 +245,8 @@ export class MdProgressCircle implements OnDestroy {
230
245
styleUrls : [ 'progress-circle.css' ] ,
231
246
} )
232
247
export class MdSpinner extends MdProgressCircle {
233
- constructor ( changeDetectorRef : ChangeDetectorRef ) {
234
- super ( changeDetectorRef ) ;
248
+ constructor ( changeDetectorRef : ChangeDetectorRef , elementRef : ElementRef , ngZone : NgZone ) {
249
+ super ( changeDetectorRef , ngZone , elementRef ) ;
235
250
this . mode = 'indeterminate' ;
236
251
}
237
252
}
@@ -277,7 +292,6 @@ function materialEase(currentTime: number, startValue: number,
277
292
let timeQuad = Math . pow ( time , 4 ) ;
278
293
let timeQuint = Math . pow ( time , 5 ) ;
279
294
return startValue + changeInValue * ( ( 6 * timeQuint ) + ( - 15 * timeQuad ) + ( 10 * timeCubed ) ) ;
280
-
281
295
}
282
296
283
297
@@ -292,14 +306,12 @@ function materialEase(currentTime: number, startValue: number,
292
306
* percentage value provided.
293
307
*/
294
308
function getSvgArc ( currentValue : number , rotation : number ) {
295
- // The angle can't be exactly 360, because the arc becomes hidden.
296
- let maximumAngle = 359.99 / 100 ;
297
309
let startPoint = rotation || 0 ;
298
310
let radius = 50 ;
299
311
let pathRadius = 40 ;
300
312
301
- let startAngle = startPoint * maximumAngle ;
302
- let endAngle = currentValue * maximumAngle ;
313
+ let startAngle = startPoint * MAX_ANGLE ;
314
+ let endAngle = currentValue * MAX_ANGLE ;
303
315
let start = polarToCartesian ( radius , pathRadius , startAngle ) ;
304
316
let end = polarToCartesian ( radius , pathRadius , endAngle + startAngle ) ;
305
317
let arcSweep = endAngle < 0 ? 0 : 1 ;
0 commit comments