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

Commit

Permalink
fix(progressCircular): switch to using SVG transform for the indeterm…
Browse files Browse the repository at this point in the history
…inate

circle rotation and apply some performance optimizations
  • Loading branch information
crisbeto committed Mar 4, 2016
1 parent e3b0cd8 commit be8df78
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 65 deletions.
4 changes: 1 addition & 3 deletions src/components/progressCircular/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ <h4>Indeterminate</h4>
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
</div>

<h4>Theming </h4>
<h4>Theming</h4>

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


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

<div id="loaders" layout="row" layout-align="start center">

<p>Progress Circular Indicators: </p>

<h5>Off</h5>
Expand Down
124 changes: 81 additions & 43 deletions src/components/progressCircular/js/progressCircularDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,15 @@ angular
'$$rAF',
'$window',
'$mdProgressCircular',
'$interval',
'$mdUtil',
'$log',
MdProgressCircularDirective
]);

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

return {
restrict: 'E',
Expand Down Expand Up @@ -93,73 +91,66 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
};

function MdProgressCircularLink(scope, element) {
var svg = angular.element(element[0].querySelector('svg'));
var path = angular.element(element[0].querySelector('path'));
var lastAnimationId = 0;
var svg = element[0].querySelector('svg');
var path = angular.element(svg.querySelector('path'));
var startIndeterminate = $mdProgressCircular.startIndeterminate;
var endIndeterminate = $mdProgressCircular.endIndeterminate;
var rotationIndeterminate = 0;
var lastAnimationId = 0;
var interval;

scope.$watchGroup(['value', 'mdMode', 'mdDiameter'], function(newValues, oldValues) {
scope.$watchGroup(['value', 'mdMode'], function(newValues, oldValues) {
var mode = newValues[1];

if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
cleanupIndeterminate();
cleanupIndeterminateAnimation();
element.addClass('ng-hide');
} else {
element.removeClass('ng-hide');

if (mode === MODE_INDETERMINATE){
if (!interval) {
interval = $interval(
animateIndeterminate,
$mdProgressCircular.durationIndeterminate + 50,
0,
false
);

element.addClass(INDETERMINATE_CLASS);
animateIndeterminate();
}

if (mode === MODE_INDETERMINATE) {
startIndeterminateAnimation();
} else {
cleanupIndeterminate();
cleanupIndeterminateAnimation();
renderCircle(clamp(oldValues[0]), clamp(newValues[0]));
}
}
});

// This is in a separate watch in order to avoid layout, unless
// the value has actually changed.
scope.$watch('mdDiameter', function(newValue) {
var diameter = getSize(newValue);
var strokeWidth = getStroke(diameter);

// The viewBox has to be applied via setAttribute, because it is
// case-sensitive. If jQuery is included in the page, `.attr` lowercases
// all attribute names.
svg.setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
path.css('stroke-width', strokeWidth + 'px');
element.css({
width: diameter + 'px',
height: diameter + 'px'
});
});

function renderCircle(animateFrom, animateTo, easing, duration, rotation) {
var id = ++lastAnimationId;
var startTime = new $window.Date();
var startTime = $window.performance.now();
var changeInValue = animateTo - animateFrom;
var diameter = getSize(scope.mdDiameter);
var strokeWidth = $mdProgressCircular.strokeWidth / 100 * diameter;

var pathDiameter = diameter - strokeWidth;
var pathDiameter = diameter - getStroke(diameter);
var ease = easing || $mdProgressCircular.easeFn;
var animationDuration = duration || $mdProgressCircular.duration;

element.attr('aria-valuenow', animateTo);
path.attr('stroke-width', strokeWidth + 'px');

svg.css({
width: diameter + 'px',
height: diameter + 'px'
});

// The viewBox has to be applied via setAttribute, because it is
// case-sensitive. If jQuery is included in the page, `.attr` lowercases
// all attribute names.
svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);

// No need to animate it if the values are the same
if (animateTo === animateFrom) {
path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation));
} else {
(function animation() {
var currentTime = $window.Math.min(new $window.Date() - startTime, animationDuration);
$$rAF(function animation(now) {
var currentTime = now - startTime;

path.attr('d', getSvgArc(
ease(currentTime, animateFrom, changeInValue, animationDuration),
Expand All @@ -171,7 +162,7 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
if (id === lastAnimationId && currentTime < animationDuration) {
$$rAF(animation);
}
})();
});
}
}

Expand All @@ -193,11 +184,50 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter
endIndeterminate = -temp;
}

function cleanupIndeterminate() {
function startIndeterminateAnimation() {
if (!interval) {
var startTime = $window.performance.now();
var animationDuration = $mdProgressCircular.rotationDurationIndeterminate;
var radius = getSize(scope.mdDiameter) / 2;

// Spares us at least a little bit of string concatenation.
radius = ' ' + radius + ', ' + radius;

// This animates the indeterminate rotation. This can be achieved much easier
// with CSS keyframes, however IE11 seems to have problems centering the rotation
// which causes a wobble in the indeterminate animation.
$$rAF(function animation(now) {
var currentTime = now - startTime;
var rotation = $mdProgressCircular.easingPresets.linearEase(currentTime, 0, 360, animationDuration);

path.attr('transform', 'rotate(' + rotation + radius + ')');

if (interval) {
$$rAF(animation);
} else {
path.removeAttr('transform');
}

// Reset the animation
if (currentTime >= animationDuration) {
startTime = now;
}
});

// This shouldn't trigger a digest which is why we don't use $interval.
interval = $window.setInterval(
animateIndeterminate,
$mdProgressCircular.durationIndeterminate + 50
);

animateIndeterminate();
}
}

function cleanupIndeterminateAnimation() {
if (interval) {
$interval.cancel(interval);
$window.clearInterval(interval);
interval = null;
element.removeClass(INDETERMINATE_CLASS);
}
}
}
Expand Down Expand Up @@ -280,4 +310,12 @@ function MdProgressCircularDirective($$rAF, $window, $mdProgressCircular, $inter

return defaultValue;
}

/**
* Determines the circle's stroke width, based on
* the provided diameter.
*/
function getStroke(diameter) {
return $mdProgressCircular.strokeWidth / 100 * diameter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* @property {number} durationIndeterminate Duration of the indeterminate animation.
* @property {number} startIndeterminate Indeterminate animation start point.
* @param {number} endIndeterminate Indeterminate animation end point.
* @param {number} rotationDurationIndeterminate Duration of the indeterminate rotating animation.
* @param {function} easeFnIndeterminate Easing function to be used when animating
* between the indeterminate values.
*
Expand Down Expand Up @@ -46,9 +47,10 @@ function MdProgressCircularProvider() {
duration: 100,
easeFn: linearEase,

durationIndeterminate: 600,
startIndeterminate: 2.5,
durationIndeterminate: 500,
startIndeterminate: 3,
endIndeterminate: 80,
rotationDurationIndeterminate: 2900,
easeFnIndeterminate: materialEase,

easingPresets: {
Expand Down
23 changes: 6 additions & 17 deletions src/components/progressCircular/progress-circular.scss
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
$progress-circular-indeterminate-duration: 2.9s;

//
// Keyframe animation for the Indeterminate Progress
//
@keyframes indeterminate-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

// Used to avoid unnecessary layout
md-progress-circular {
svg {
// Ensures that the parent has the same dimensions as the circle.
display: block;
}
position: relative;

&._md-mode-indeterminate {
svg {
animation: indeterminate-rotate $progress-circular-indeterminate-duration linear infinite;
position: absolute;
overflow: visible;
top: 0;
left: 0;
}
}
}

0 comments on commit be8df78

Please sign in to comment.