diff --git a/src/lib/progress-circle/progress-circle.html b/src/lib/progress-circle/progress-circle.html
index 6c5b5be3fada..a6d4721c4a15 100644
--- a/src/lib/progress-circle/progress-circle.html
+++ b/src/lib/progress-circle/progress-circle.html
@@ -5,5 +5,5 @@
-->
diff --git a/src/lib/progress-circle/progress-circle.ts b/src/lib/progress-circle/progress-circle.ts
index a0290d2b42b7..76c03d8bfab4 100644
--- a/src/lib/progress-circle/progress-circle.ts
+++ b/src/lib/progress-circle/progress-circle.ts
@@ -6,7 +6,9 @@ import {
ChangeDetectorRef,
ChangeDetectionStrategy,
OnDestroy,
- Input
+ Input,
+ ElementRef,
+ NgZone
} from '@angular/core';
// TODO(josephperrott): Benchpress tests.
@@ -21,7 +23,8 @@ const DURATION_DETERMINATE = 225;
const startIndeterminate = 3;
/** End animation value of the indeterminate animation */
const endIndeterminate = 80;
-
+/* Maximum angle for the arc. The angle can't be exactly 360, because the arc becomes hidden. */
+const MAX_ANGLE = 359.99 / 100;
export type ProgressCircleMode = 'determinate' | 'indeterminate';
@@ -51,6 +54,9 @@ export class MdProgressCircle implements OnDestroy {
/** The id of the indeterminate interval. */
private _interdeterminateInterval: number;
+ /** The SVG node that is used to draw the circle. */
+ private _path: SVGPathElement;
+
/**
* Values for aria max and min are only defined as numbers when in a determinate mode. We do this
* because voiceover does not report the progress indicator as indeterminate if the aria min
@@ -74,20 +80,6 @@ export class MdProgressCircle implements OnDestroy {
this._interdeterminateInterval = interval;
}
- /** The current path value, representing the progress circle. */
- private _currentPath: string;
-
- /** TODO: internal */
- get currentPath() {
- return this._currentPath;
- }
- set currentPath(path: string) {
- this._currentPath = path;
- // Mark for check as our ChangeDetectionStrategy is OnPush, when changes come from within the
- // component, change detection must be called for.
- this._changeDetectorRef.markForCheck();
- }
-
/** Clean up any animations that were running. */
ngOnDestroy() {
this._cleanupIndeterminateAnimation();
@@ -136,8 +128,11 @@ export class MdProgressCircle implements OnDestroy {
}
private _mode: ProgressCircleMode = 'determinate';
- constructor(private _changeDetectorRef: ChangeDetectorRef) {
- }
+ constructor(
+ private _changeDetectorRef: ChangeDetectorRef,
+ private _ngZone: NgZone,
+ private _elementRef: ElementRef
+ ) {}
/**
@@ -152,29 +147,33 @@ export class MdProgressCircle implements OnDestroy {
*/
private _animateCircle(animateFrom: number, animateTo: number, ease: EasingFn,
duration: number, rotation: number) {
+
let id = ++this._lastAnimationId;
let startTime = Date.now();
let changeInValue = animateTo - animateFrom;
// No need to animate it if the values are the same
if (animateTo === animateFrom) {
- this.currentPath = getSvgArc(animateTo, rotation);
+ this._renderArc(animateTo, rotation);
} else {
let animation = () => {
let elapsedTime = Math.max(0, Math.min(Date.now() - startTime, duration));
- this.currentPath = getSvgArc(
+ this._renderArc(
ease(elapsedTime, animateFrom, changeInValue, duration),
rotation
);
// Prevent overlapping animations by checking if a new animation has been called for and
- // if the animation has lasted long than the animation duration.
+ // if the animation has lasted longer than the animation duration.
if (id === this._lastAnimationId && elapsedTime < duration) {
requestAnimationFrame(animation);
}
};
- requestAnimationFrame(animation);
+
+ // Run the animation outside of Angular's zone, in order to avoid
+ // hitting ZoneJS and change detection on each frame.
+ this._ngZone.runOutsideAngular(animation);
}
}
@@ -197,9 +196,10 @@ export class MdProgressCircle implements OnDestroy {
};
if (!this.interdeterminateInterval) {
- this.interdeterminateInterval = setInterval(
- animate, duration + 50, 0, false);
- animate();
+ this._ngZone.runOutsideAngular(() => {
+ this.interdeterminateInterval = setInterval(animate, duration + 50, 0, false);
+ animate();
+ });
}
}
@@ -210,6 +210,21 @@ export class MdProgressCircle implements OnDestroy {
private _cleanupIndeterminateAnimation() {
this.interdeterminateInterval = null;
}
+
+ /**
+ * Renders the arc onto the SVG element. Proxies `getArc` while setting the proper
+ * DOM attribute on the ``.
+ */
+ private _renderArc(currentValue: number, rotation: number) {
+ // Caches the path reference so it doesn't have to be looked up every time.
+ let path = this._path = this._path || this._elementRef.nativeElement.querySelector('path');
+
+ // Ensure that the path was found. This may not be the case if the
+ // animation function fires too early.
+ if (path) {
+ path.setAttribute('d', getSvgArc(currentValue, rotation));
+ }
+ }
}
@@ -230,8 +245,8 @@ export class MdProgressCircle implements OnDestroy {
styleUrls: ['progress-circle.css'],
})
export class MdSpinner extends MdProgressCircle {
- constructor(changeDetectorRef: ChangeDetectorRef) {
- super(changeDetectorRef);
+ constructor(changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef, ngZone: NgZone) {
+ super(changeDetectorRef, ngZone, elementRef);
this.mode = 'indeterminate';
}
}
@@ -277,7 +292,6 @@ function materialEase(currentTime: number, startValue: number,
let timeQuad = Math.pow(time, 4);
let timeQuint = Math.pow(time, 5);
return startValue + changeInValue * ((6 * timeQuint) + (-15 * timeQuad) + (10 * timeCubed));
-
}
@@ -292,14 +306,12 @@ function materialEase(currentTime: number, startValue: number,
* percentage value provided.
*/
function getSvgArc(currentValue: number, rotation: number) {
- // The angle can't be exactly 360, because the arc becomes hidden.
- let maximumAngle = 359.99 / 100;
let startPoint = rotation || 0;
let radius = 50;
let pathRadius = 40;
- let startAngle = startPoint * maximumAngle;
- let endAngle = currentValue * maximumAngle;
+ let startAngle = startPoint * MAX_ANGLE;
+ let endAngle = currentValue * MAX_ANGLE;
let start = polarToCartesian(radius, pathRadius, startAngle);
let end = polarToCartesian(radius, pathRadius, endAngle + startAngle);
let arcSweep = endAngle < 0 ? 0 : 1;