@@ -47,17 +47,16 @@ angular
47
47
'$$rAF' ,
48
48
'$window' ,
49
49
'$mdProgressCircular' ,
50
- '$interval' ,
51
50
'$mdUtil' ,
51
+ '$interval' ,
52
52
'$log' ,
53
53
MdProgressCircularDirective
54
54
] ) ;
55
55
56
- function MdProgressCircularDirective ( $$rAF , $window , $mdProgressCircular , $interval , $mdUtil , $log ) {
56
+ function MdProgressCircularDirective ( $$rAF , $window , $mdProgressCircular , $mdUtil , $interval , $log ) {
57
57
var DEGREE_IN_RADIANS = $window . Math . PI / 180 ;
58
58
var MODE_DETERMINATE = 'determinate' ;
59
59
var MODE_INDETERMINATE = 'indeterminate' ;
60
- var INDETERMINATE_CLASS = '_md-mode-indeterminate' ;
61
60
62
61
return {
63
62
restrict : 'E' ,
@@ -70,7 +69,7 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
70
69
'<svg xmlns="http://www.w3.org/2000/svg">' +
71
70
'<path fill="none"/>' +
72
71
'</svg>' ,
73
- compile : function ( element , attrs ) {
72
+ compile : function ( element , attrs ) {
74
73
element . attr ( {
75
74
'aria-valuemin' : 0 ,
76
75
'aria-valuemax' : 100 ,
@@ -93,73 +92,66 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
93
92
} ;
94
93
95
94
function MdProgressCircularLink ( scope , element ) {
96
- var svg = angular . element ( element [ 0 ] . querySelector ( 'svg' ) ) ;
97
- var path = angular . element ( element [ 0 ] . querySelector ( 'path' ) ) ;
98
- var lastAnimationId = 0 ;
95
+ var svg = element [ 0 ] . querySelector ( 'svg' ) ;
96
+ var path = angular . element ( svg . querySelector ( 'path' ) ) ;
99
97
var startIndeterminate = $mdProgressCircular . startIndeterminate ;
100
98
var endIndeterminate = $mdProgressCircular . endIndeterminate ;
101
99
var rotationIndeterminate = 0 ;
100
+ var lastAnimationId = 0 ;
102
101
var interval ;
103
102
104
- scope . $watchGroup ( [ 'value' , 'mdMode' , 'mdDiameter' ] , function ( newValues , oldValues ) {
103
+ scope . $watchGroup ( [ 'value' , 'mdMode' ] , function ( newValues , oldValues ) {
105
104
var mode = newValues [ 1 ] ;
106
105
107
106
if ( mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE ) {
108
- cleanupIndeterminate ( ) ;
107
+ cleanupIndeterminateAnimation ( ) ;
109
108
element . addClass ( 'ng-hide' ) ;
110
109
} else {
111
110
element . removeClass ( 'ng-hide' ) ;
112
111
113
- if ( mode === MODE_INDETERMINATE ) {
114
- if ( ! interval ) {
115
- interval = $interval (
116
- animateIndeterminate ,
117
- $mdProgressCircular . durationIndeterminate + 50 ,
118
- 0 ,
119
- false
120
- ) ;
121
-
122
- element . addClass ( INDETERMINATE_CLASS ) ;
123
- animateIndeterminate ( ) ;
124
- }
125
-
112
+ if ( mode === MODE_INDETERMINATE ) {
113
+ startIndeterminateAnimation ( ) ;
126
114
} else {
127
- cleanupIndeterminate ( ) ;
115
+ cleanupIndeterminateAnimation ( ) ;
128
116
renderCircle ( clamp ( oldValues [ 0 ] ) , clamp ( newValues [ 0 ] ) ) ;
129
117
}
130
118
}
131
119
} ) ;
132
120
121
+ // This is in a separate watch in order to avoid layout, unless
122
+ // the value has actually changed.
123
+ scope . $watch ( 'mdDiameter' , function ( newValue ) {
124
+ var diameter = getSize ( newValue ) ;
125
+ var strokeWidth = getStroke ( diameter ) ;
126
+
127
+ // The viewBox has to be applied via setAttribute, because it is
128
+ // case-sensitive. If jQuery is included in the page, `.attr` lowercases
129
+ // all attribute names.
130
+ svg . setAttribute ( 'viewBox' , '0 0 ' + diameter + ' ' + diameter ) ;
131
+ path . css ( 'stroke-width' , strokeWidth + 'px' ) ;
132
+ element . css ( {
133
+ width : diameter + 'px' ,
134
+ height : diameter + 'px'
135
+ } ) ;
136
+ } ) ;
137
+
133
138
function renderCircle ( animateFrom , animateTo , easing , duration , rotation ) {
134
139
var id = ++ lastAnimationId ;
135
- var startTime = new $window . Date ( ) ;
140
+ var startTime = $window . performance . now ( ) ;
136
141
var changeInValue = animateTo - animateFrom ;
137
142
var diameter = getSize ( scope . mdDiameter ) ;
138
- var strokeWidth = $mdProgressCircular . strokeWidth / 100 * diameter ;
139
-
140
- var pathDiameter = diameter - strokeWidth ;
143
+ var pathDiameter = diameter - getStroke ( diameter ) ;
141
144
var ease = easing || $mdProgressCircular . easeFn ;
142
145
var animationDuration = duration || $mdProgressCircular . duration ;
143
146
144
147
element . attr ( 'aria-valuenow' , animateTo ) ;
145
- path . attr ( 'stroke-width' , strokeWidth + 'px' ) ;
146
-
147
- svg . css ( {
148
- width : diameter + 'px' ,
149
- height : diameter + 'px'
150
- } ) ;
151
-
152
- // The viewBox has to be applied via setAttribute, because it is
153
- // case-sensitive. If jQuery is included in the page, `.attr` lowercases
154
- // all attribute names.
155
- svg [ 0 ] . setAttribute ( 'viewBox' , '0 0 ' + diameter + ' ' + diameter ) ;
156
148
157
149
// No need to animate it if the values are the same
158
150
if ( animateTo === animateFrom ) {
159
151
path . attr ( 'd' , getSvgArc ( animateTo , diameter , pathDiameter , rotation ) ) ;
160
152
} else {
161
- ( function animation ( ) {
162
- var currentTime = $window . Math . min ( new $window . Date ( ) - startTime , animationDuration ) ;
153
+ $$rAF ( function animation ( now ) {
154
+ var currentTime = now - startTime ;
163
155
164
156
path . attr ( 'd' , getSvgArc (
165
157
ease ( currentTime , animateFrom , changeInValue , animationDuration ) ,
@@ -171,7 +163,7 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
171
163
if ( id === lastAnimationId && currentTime < animationDuration ) {
172
164
$$rAF ( animation ) ;
173
165
}
174
- } ) ( ) ;
166
+ } ) ;
175
167
}
176
168
}
177
169
@@ -193,11 +185,52 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
193
185
endIndeterminate = - temp ;
194
186
}
195
187
196
- function cleanupIndeterminate ( ) {
188
+ function startIndeterminateAnimation ( ) {
189
+ if ( ! interval ) {
190
+ var startTime = $window . performance . now ( ) ;
191
+ var animationDuration = $mdProgressCircular . rotationDurationIndeterminate ;
192
+ var radius = getSize ( scope . mdDiameter ) / 2 ;
193
+
194
+ // Spares us at least a little bit of string concatenation.
195
+ radius = ' ' + radius + ', ' + radius ;
196
+
197
+ // This animates the indeterminate rotation. This can be achieved much easier
198
+ // with CSS keyframes, however IE11 seems to have problems centering the rotation
199
+ // which causes a wobble in the indeterminate animation.
200
+ $$rAF ( function animation ( now ) {
201
+ var currentTime = now - startTime ;
202
+ var rotation = $mdProgressCircular . easingPresets . linearEase ( currentTime , 0 , 360 , animationDuration ) ;
203
+
204
+ path . attr ( 'transform' , 'rotate(' + rotation + radius + ')' ) ;
205
+
206
+ if ( interval ) {
207
+ $$rAF ( animation ) ;
208
+ } else {
209
+ path . removeAttr ( 'transform' ) ;
210
+ }
211
+
212
+ // Reset the animation
213
+ if ( currentTime >= animationDuration ) {
214
+ startTime = now ;
215
+ }
216
+ } ) ;
217
+
218
+ // This shouldn't trigger a digest which is why we don't use $interval.
219
+ interval = $interval (
220
+ animateIndeterminate ,
221
+ $mdProgressCircular . durationIndeterminate + 50 ,
222
+ 0 ,
223
+ false
224
+ ) ;
225
+
226
+ animateIndeterminate ( ) ;
227
+ }
228
+ }
229
+
230
+ function cleanupIndeterminateAnimation ( ) {
197
231
if ( interval ) {
198
232
$interval . cancel ( interval ) ;
199
233
interval = null ;
200
- element . removeClass ( INDETERMINATE_CLASS ) ;
201
234
}
202
235
}
203
236
}
@@ -280,4 +313,12 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
280
313
281
314
return defaultValue ;
282
315
}
316
+
317
+ /**
318
+ * Determines the circle's stroke width, based on
319
+ * the provided diameter.
320
+ */
321
+ function getStroke ( diameter ) {
322
+ return $mdProgressCircular . strokeWidth / 100 * diameter ;
323
+ }
283
324
}
0 commit comments