Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit d67389c

Browse files
crisbetoThomasBurleson
authored andcommitted
fix(progressCircular): switch to using SVG transform for the indeterminate circle rotation and apply some performance optimizations
Closes #7414
1 parent 1554eb1 commit d67389c

File tree

5 files changed

+96
-66
lines changed

5 files changed

+96
-66
lines changed

src/components/progressCircular/demoBasicUsage/index.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ <h4>Indeterminate</h4>
1818
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
1919
</div>
2020

21-
<h4>Theming </h4>
21+
<h4>Theming</h4>
2222

2323
<p>
2424
Your current theme colors can be used to easily colorize your progress indicator with `md-warn` or `md-accent`
@@ -34,11 +34,9 @@ <h4>Theming </h4>
3434
<md-progress-circular md-mode="{{vm.modes[4]}}" md-diameter="96"></md-progress-circular>
3535
</div>
3636

37-
3837
<hr ng-class="{'visible' : vm.activated}">
3938

4039
<div id="loaders" layout="row" layout-align="start center">
41-
4240
<p>Progress Circular Indicators: </p>
4341

4442
<h5>Off</h5>

src/components/progressCircular/js/progressCircularDirective.js

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,16 @@ angular
4747
'$$rAF',
4848
'$window',
4949
'$mdProgressCircular',
50-
'$interval',
5150
'$mdUtil',
51+
'$interval',
5252
'$log',
5353
MdProgressCircularDirective
5454
]);
5555

56-
function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $interval, $mdUtil, $log) {
56+
function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $mdUtil, $interval, $log) {
5757
var DEGREE_IN_RADIANS = $window.Math.PI / 180;
5858
var MODE_DETERMINATE = 'determinate';
5959
var MODE_INDETERMINATE = 'indeterminate';
60-
var INDETERMINATE_CLASS = '_md-mode-indeterminate';
6160

6261
return {
6362
restrict: 'E',
@@ -70,7 +69,7 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
7069
'<svg xmlns="http://www.w3.org/2000/svg">' +
7170
'<path fill="none"/>' +
7271
'</svg>',
73-
compile: function(element, attrs){
72+
compile: function(element, attrs) {
7473
element.attr({
7574
'aria-valuemin': 0,
7675
'aria-valuemax': 100,
@@ -93,73 +92,66 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
9392
};
9493

9594
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'));
9997
var startIndeterminate = $mdProgressCircular.startIndeterminate;
10098
var endIndeterminate = $mdProgressCircular.endIndeterminate;
10199
var rotationIndeterminate = 0;
100+
var lastAnimationId = 0;
102101
var interval;
103102

104-
scope.$watchGroup(['value', 'mdMode', 'mdDiameter'], function(newValues, oldValues) {
103+
scope.$watchGroup(['value', 'mdMode'], function(newValues, oldValues) {
105104
var mode = newValues[1];
106105

107106
if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
108-
cleanupIndeterminate();
107+
cleanupIndeterminateAnimation();
109108
element.addClass('ng-hide');
110109
} else {
111110
element.removeClass('ng-hide');
112111

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();
126114
} else {
127-
cleanupIndeterminate();
115+
cleanupIndeterminateAnimation();
128116
renderCircle(clamp(oldValues[0]), clamp(newValues[0]));
129117
}
130118
}
131119
});
132120

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+
133138
function renderCircle(animateFrom, animateTo, easing, duration, rotation) {
134139
var id = ++lastAnimationId;
135-
var startTime = new $window.Date();
140+
var startTime = $window.performance.now();
136141
var changeInValue = animateTo - animateFrom;
137142
var diameter = getSize(scope.mdDiameter);
138-
var strokeWidth = $mdProgressCircular.strokeWidth / 100 * diameter;
139-
140-
var pathDiameter = diameter - strokeWidth;
143+
var pathDiameter = diameter - getStroke(diameter);
141144
var ease = easing || $mdProgressCircular.easeFn;
142145
var animationDuration = duration || $mdProgressCircular.duration;
143146

144147
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);
156148

157149
// No need to animate it if the values are the same
158150
if (animateTo === animateFrom) {
159151
path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation));
160152
} 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;
163155

164156
path.attr('d', getSvgArc(
165157
ease(currentTime, animateFrom, changeInValue, animationDuration),
@@ -171,7 +163,7 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
171163
if (id === lastAnimationId && currentTime < animationDuration) {
172164
$$rAF(animation);
173165
}
174-
})();
166+
});
175167
}
176168
}
177169

@@ -193,11 +185,52 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
193185
endIndeterminate = -temp;
194186
}
195187

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() {
197231
if (interval) {
198232
$interval.cancel(interval);
199233
interval = null;
200-
element.removeClass(INDETERMINATE_CLASS);
201234
}
202235
}
203236
}
@@ -280,4 +313,12 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
280313

281314
return defaultValue;
282315
}
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+
}
283324
}

src/components/progressCircular/js/progressCircularProvider.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* @property {number} durationIndeterminate Duration of the indeterminate animation.
1616
* @property {number} startIndeterminate Indeterminate animation start point.
1717
* @param {number} endIndeterminate Indeterminate animation end point.
18+
* @param {number} rotationDurationIndeterminate Duration of the indeterminate rotating animation.
1819
* @param {function} easeFnIndeterminate Easing function to be used when animating
1920
* between the indeterminate values.
2021
*
@@ -46,9 +47,10 @@ function MdProgressCircularProvider() {
4647
duration: 100,
4748
easeFn: linearEase,
4849

49-
durationIndeterminate: 600,
50-
startIndeterminate: 2.5,
50+
durationIndeterminate: 500,
51+
startIndeterminate: 3,
5152
endIndeterminate: 80,
53+
rotationDurationIndeterminate: 2900,
5254
easeFnIndeterminate: materialEase,
5355

5456
easingPresets: {
Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
1-
$progress-circular-indeterminate-duration: 2.9s;
2-
3-
//
4-
// Keyframe animation for the Indeterminate Progress
5-
//
6-
@keyframes indeterminate-rotate {
7-
0% { transform: rotate(0deg); }
8-
100% { transform: rotate(360deg); }
9-
}
10-
1+
// Used to avoid unnecessary layout
112
md-progress-circular {
12-
svg {
13-
// Ensures that the parent has the same dimensions as the circle.
14-
display: block;
15-
}
3+
position: relative;
164

17-
&._md-mode-indeterminate {
185
svg {
19-
animation: indeterminate-rotate $progress-circular-indeterminate-duration linear infinite;
6+
position: absolute;
7+
overflow: visible;
8+
top: 0;
9+
left: 0;
2010
}
21-
}
2211
}

src/components/progressCircular/progress-circular.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('mdProgressCircular', function() {
9191
element = $compile('<div>' + template + '</div>')($rootScope);
9292
$rootScope.$digest();
9393

94-
return element.find('svg');
94+
return element.find('md-progress-circular');
9595
}
9696

9797
});

0 commit comments

Comments
 (0)