@syncfusion/ej2-progressbar
Version:
Essential JS 2 ProgressBar Component
444 lines (431 loc) • 24.3 kB
text/typescript
import { Animation, AnimationOptions, isNullOrUndefined, animationMode } from '@syncfusion/ej2-base';
import { degreeToLocation, effect, getPathArc } from '../utils/helper';
import { ProgressBar } from '../progressbar';
import { lineCapRadius, completeAngle } from '../model/constant';
/**
* Animation for progress bar
*/
export class ProgressAnimation {
/**
* Performs linear animation on the specified element.
*
* @param {Element} element - The HTML element to animate.
* @param {ProgressBar} progress - The progress bar control.
* @param {number} delay - The delay before starting the animation, in milliseconds.
* @param {number} previousWidth - The previous width of the progress.
* @param {Element} active - The active element to control the animation.
* @returns {void}
*/
public doLinearAnimation(element: Element, progress: ProgressBar, delay: number, previousWidth?: number, active?: Element): void {
const animation: Animation = new Animation({});
const linearPath: HTMLElement = <HTMLElement>element;
const duration: number = (progress.isActive) ? 3000 : progress.animation.duration;
const width: string = linearPath.getAttribute('width');
const x: string = linearPath.getAttribute('x');
let opacityValue: number = 0;
let value: number = 0;
const start: number = (!progress.enableRtl || (progress.cornerRadius === 'Round4px')) ? previousWidth : parseInt(x, 10);
const end: number = (!progress.enableRtl || (progress.cornerRadius === 'Round4px')) ? parseInt(width, 10) - start :
parseInt(width, 10) - previousWidth;
const rtlX: number = parseInt(x, 10) - end;
linearPath.style.visibility = 'hidden';
animation.animate(linearPath, {
duration: (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : duration,
delay: delay,
progress: (args: AnimationOptions): void => {
progress.cancelResize = true;
if (progress.enableRtl && !(progress.cornerRadius === 'Round4px')) {
if (args.timeStamp >= args.delay) {
linearPath.style.visibility = 'visible';
if (progress.isActive) {
value = this.activeAnimate((args.timeStamp / args.duration), parseInt(x, 10), parseInt(width, 10), true);
opacityValue = effect(args.timeStamp, 0.5, 0.5, args.duration, true);
active.setAttribute('opacity', opacityValue.toString());
linearPath.setAttribute('x', value.toString());
} else {
value = effect(args.timeStamp, start, end, args.duration, true);
linearPath.setAttribute('x', value.toString());
}
}
} else {
if (args.timeStamp >= args.delay) {
linearPath.style.visibility = 'visible';
if (progress.isActive) {
value = this.activeAnimate((args.timeStamp / args.duration), 0, parseInt(width, 10), false);
opacityValue = effect(args.timeStamp, 0.5, 0.5, args.duration, true);
active.setAttribute('opacity', opacityValue.toString());
linearPath.setAttribute('width', value.toString());
} else {
value = effect(args.timeStamp, start, end, args.duration, false);
linearPath.setAttribute('width', value.toString());
}
}
}
},
end: () => {
progress.cancelResize = false;
linearPath.style.visibility = '';
if (progress.enableRtl && !(progress.cornerRadius === 'Round4px')) {
if (progress.isActive) {
linearPath.setAttribute('x', x.toString());
this.doLinearAnimation(element, progress, delay, previousWidth, active);
} else {
linearPath.setAttribute('x', rtlX.toString());
}
} else {
linearPath.setAttribute('width', width);
if (progress.isActive) {
this.doLinearAnimation(element, progress, delay, previousWidth, active);
}
}
progress.trigger('animationComplete', {
value: progress.value, trackColor: progress.trackColor,
progressColor: progress.progressColor
});
}
});
}
/**
* Initiates linear animation for an indeterminate progress bar.
*
* @param {Element} element - The HTML element representing the progress bar.
* @param {number} progressWidth - The width of the progress bar.
* @param {number} thickness - The thickness of the progress bar.
* @param {ProgressBar} progress - The progress bar control.
* @param {Element} clipPath - The SVG clip path element to control the animation.
* @returns {void}
*/
public doLinearIndeterminate(
element: Element, progressWidth: number, thickness: number, progress: ProgressBar, clipPath: Element
): void {
const animation: Animation = new Animation({});
const linearPath: HTMLElement = <HTMLElement>element;
const x: string = linearPath.getAttribute('x');
const width: string = linearPath.getAttribute('width');
let value: number = 0;
const start: number = (width) ? -(parseInt(width, 10)) : -progressWidth;
const end: number = (progress.progressRect.x + progress.progressRect.width) + ((width) ? (parseInt(width, 10)) : progressWidth);
const duration: number = (!progress.enableProgressSegments) ? progress.animation.duration : progress.animation.duration + 1000;
animation.animate(<HTMLElement>clipPath, {
duration: (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : duration,
delay: 0,
progress: (args: AnimationOptions): void => {
if (progress.enableRtl && !(progress.cornerRadius === 'Round4px')) {
value = effect(
args.timeStamp, parseInt(x, 10) || progress.progressRect.x + progressWidth,
end, args.duration, true
);
if (!progress.enableProgressSegments) {
linearPath.setAttribute('x', value.toString());
} else {
linearPath.setAttribute('d', progress.getPathLine(value, progressWidth, thickness));
}
} else {
value = effect(args.timeStamp, start, end, args.duration, false);
if (!progress.enableProgressSegments) {
linearPath.setAttribute('x', value.toString());
} else {
linearPath.setAttribute('d', progress.getPathLine(value, progressWidth, thickness));
}
}
},
end: () => {
if (progress.enableRtl && !progress.enableProgressSegments && !(progress.cornerRadius === 'Round4px')) {
linearPath.setAttribute('x', x.toString());
} else if (!progress.enableProgressSegments) {
linearPath.setAttribute('x', start.toString());
}
if (!progress.destroyIndeterminate) {
this.doLinearIndeterminate(element, progressWidth, thickness, progress, clipPath);
}
}
});
}
/**
* Performs striped animation on the specified element.
*
* @param {Element} element - The HTML element to animate.
* @param {ProgressBar} progress - The progress bar object.
* @param {number} value - The value indicating the progress of the animation.
* @returns {void}
*/
public doStripedAnimation(element: Element, progress: ProgressBar, value: number): void {
const animation: Animation = new Animation({});
const point: number = 1000 / progress.animation.duration;
animation.animate(<HTMLElement>element, {
duration: (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : progress.animation.duration,
delay: progress.animation.delay,
progress: (): void => {
value += (progress.enableRtl) ? -point : point;
element.setAttribute('gradientTransform', 'translate(' + value + ') rotate(-45)');
},
end: () => {
if (!progress.destroyIndeterminate) {
this.doStripedAnimation(element, progress, value);
}
}
});
}
/**
* Initiates circular animation on the specified element.
*
* @param {number} x - The x-coordinate of the center of the circle.
* @param {number} y - The y-coordinate of the center of the circle.
* @param {number} radius - The radius of the circle.
* @param {number} progressEnd - The end value of the progress.
* @param {number} totalEnd - The total end value of the progress.
* @param {Element} element - The HTML element representing the circular progress.
* @param {ProgressBar} progress - The progress bar control.
* @param {number} thickness - The thickness of the circular progress.
* @param {number} delay - The delay before starting the animation, in milliseconds.
* @param {number} startValue - The starting value of the progress.
* @param {number} previousTotal - The previous total value of the progress.
* @param {Element} active - The active element to control the animation.
* @returns {void}
*/
public doCircularAnimation(
x: number, y: number, radius: number, progressEnd: number, totalEnd: number,
element: Element, progress: ProgressBar, thickness: number, delay: number, startValue?: number,
previousTotal?: number, active?: Element
): void {
const animation: Animation = new Animation({});
const circularPath: HTMLElement = <HTMLElement>element;
let start: number = progress.startAngle;
const pathRadius: number = radius + (thickness / 2);
let end: number = 0;
let opacityValue: number = 0;
const duration: number = (progress.isActive) ? 3000 : progress.animation.duration;
start += (progress.cornerRadius === 'Round' && totalEnd !== completeAngle && totalEnd !== 0) ?
((progress.enableRtl) ? (lineCapRadius / 2) * thickness : -(lineCapRadius / 2) * thickness) : 0;
totalEnd += (progress.cornerRadius === 'Round' && totalEnd !== completeAngle && totalEnd !== 0) ?
(lineCapRadius / 2) * thickness : 0;
progressEnd += (progress.cornerRadius === 'Round' && totalEnd !== completeAngle && totalEnd !== 0) ?
((progress.enableRtl) ? -(lineCapRadius / 2) * thickness : (lineCapRadius / 2) * thickness) : 0;
if (progress.cornerRadius === 'Round' && totalEnd !== completeAngle && totalEnd !== 0 && progress.startAngle === progress.endAngle) {
const startPosition: number = degreeToLocation(x, y, pathRadius, start).x;
let endPosition: number = degreeToLocation(x, y, pathRadius, progressEnd).x;
while (((progress.enableRtl !== progress.startAngle >= 180) ? endPosition <= startPosition : endPosition >= startPosition)) {
progressEnd += (progress.enableRtl ? 0.1 : -0.1);
endPosition = degreeToLocation(x, y, pathRadius, progressEnd).x;
}
}
const startPos: number = (!isNullOrUndefined(startValue)) ? startValue : start;
const endPos: number = (!isNullOrUndefined(startValue)) ? totalEnd - previousTotal : totalEnd;
circularPath.setAttribute('visibility', 'Hidden');
animation.animate(circularPath, {
duration: (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : duration,
delay: delay,
progress: (args: AnimationOptions): void => {
progress.cancelResize = true;
if (args.timeStamp >= args.delay) {
circularPath.setAttribute('visibility', 'visible');
if (progress.isActive) {
end = this.activeAnimate((args.timeStamp / args.duration), startPos, endPos, progress.enableRtl);
opacityValue = effect(args.timeStamp, 0.5, 0.5, args.duration, true);
active.setAttribute('opacity', opacityValue.toString());
circularPath.setAttribute('d', getPathArc(x, y, pathRadius, start, end % 360, progress.enableRtl, true));
} else {
end = effect(args.timeStamp, startPos, endPos, args.duration, progress.enableRtl);
circularPath.setAttribute('d', getPathArc(x, y, pathRadius, start, end % 360, progress.enableRtl, true));
}
}
},
end: () => {
progress.cancelResize = false;
circularPath.setAttribute('visibility', '');
circularPath.setAttribute('d', getPathArc(x, y, pathRadius, start, progressEnd, progress.enableRtl, true));
if (progress.isActive) {
this.doCircularAnimation(
x, y, radius, progressEnd, totalEnd, element, progress, thickness,
delay, startValue, previousTotal, active
);
}
progress.trigger('animationComplete', {
value: progress.value, trackColor: progress.trackColor,
progressColor: progress.progressColor
});
}
});
}
/**
* Initiates circular animation for an indeterminate progress bar.
*
* @param {Element} circularProgress - The HTML element representing the circular progress bar.
* @param {ProgressBar} progress - The progress bar object.
* @param {number} start - The starting value of the progress.
* @param {number} end - The ending value of the progress.
* @param {number} x - The x-coordinate of the center of the circle.
* @param {number} y - The y-coordinate of the center of the circle.
* @param {number} radius - The radius of the circle.
* @param {number} thickness - The thickness of the circular progress bar.
* @param {Element} clipPath - The SVG clip path element to control the animation.
* @returns {void}
*/
public doCircularIndeterminate(
circularProgress: Element, progress: ProgressBar,
start: number, end: number, x: number, y: number, radius: number, thickness: number, clipPath: Element
): void {
const animation: Animation = new Animation({});
const pathRadius: number = radius + ((!progress.enableProgressSegments) ? (thickness / 2) : 0);
const duration: number = (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : progress.animation.duration;
const value: number = (!progress.enableProgressSegments) ? 6000 / duration : 4000 / duration;
animation.animate((<HTMLElement>clipPath), {
progress: (): void => {
(<HTMLElement>circularProgress).style.visibility = 'visible';
start += (progress.enableRtl) ? -value : value;
end += (progress.enableRtl) ? -value : value;
circularProgress.setAttribute(
'd', getPathArc(x, y, pathRadius, start % 360, end % 360, progress.enableRtl, !progress.enableProgressSegments)
);
},
end: () => {
if (!progress.destroyIndeterminate) {
this.doCircularIndeterminate(circularProgress, progress, start, end, x, y, radius, thickness, clipPath);
}
}
});
}
/**
* Initiates label animation for a progress bar.
*
* @param {Element} labelPath - The SVG path element representing the label.
* @param {number} start - The starting value of the progress.
* @param {number} end - The ending value of the progress.
* @param {ProgressBar} progress - The progress bar control.
* @param {number} delay - The delay before starting the animation, in milliseconds.
* @param {number} textSize - The size of the text.
* @returns {void}
*/
public doLabelAnimation(labelPath: Element, start: number, end: number, progress: ProgressBar, delay: number, textSize?: number): void {
const animation: Animation = new Animation({});
const label: Animation = new Animation({});
let startPos: number;
let endPos: number;
const text: string = labelPath.innerHTML;
let value: number = 0;
let xPos: number = 0;
let valueChanged: number = 0;
const percentage: number = 100;
const labelText: string = progress.labelStyle.text;
const labelPos: string = progress.labelStyle.textAlignment;
const posX: number = parseInt(labelPath.getAttribute('x'), 10);
labelPath.setAttribute('visibility', 'Hidden');
if (progress.type === 'Linear') {
startPos = (progress.enableRtl) ? (progress.progressRect.x + progress.progressRect.width) + (textSize / 2) :
start - (textSize / 2);
startPos = (startPos <= 0) ? 0 : startPos;
endPos = (progress.enableRtl) ? startPos - posX : posX - startPos;
}
animation.animate(<HTMLElement>labelPath, {
duration: (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : progress.animation.duration,
delay: delay,
progress: (args: AnimationOptions): void => {
progress.cancelResize = true;
args.name = 'SlideRight';
if (progress.type === 'Linear') {
if (args.timeStamp >= args.delay) {
if (labelText === '') {
labelPath.setAttribute('visibility', 'visible');
value = effect(args.timeStamp, start, end, args.duration, false);
valueChanged = parseInt((((Math.round(value)) / progress.progressRect.width) * percentage).toString(), 10);
labelPath.innerHTML = valueChanged.toString() + '%';
if (labelPos === 'Far' || labelPos === 'Center') {
xPos = effect(args.timeStamp, startPos, endPos, args.duration, progress.enableRtl);
labelPath.setAttribute('x', xPos.toString());
}
}
}
} else if (progress.type === 'Circular') {
if (labelText === '') {
labelPath.setAttribute('visibility', 'visible');
value = effect(args.timeStamp, start, end - start, args.duration, false);
valueChanged = parseInt((((Math.round(value)) / progress.totalAngle) * percentage).toString(), 10);
labelPath.innerHTML = valueChanged.toString() + '%';
}
}
},
end: () => {
progress.cancelResize = false;
if (labelText === '') {
labelPath.innerHTML = text;
labelPath.setAttribute('x', posX.toString());
} else {
label.animate(<HTMLElement>labelPath, {
progress: (args: AnimationOptions): void => {
labelPath.setAttribute('visibility', 'visible');
value = effect(args.timeStamp, 0, 1, args.duration, false);
labelPath.setAttribute('opacity', value.toString());
},
end: () => {
labelPath.setAttribute('opacity', '1');
}
});
}
}
});
}
/**
* Initiates annotation animation for a circular progress bar.
*
* @param {Element} circularPath - The SVG path element representing the circular progress bar.
* @param {ProgressBar} progress - The progress bar object.
* @param {number} previousEnd - The previous end value of the progress.
* @param {number} previousTotal - The previous total value of the progress.
* @returns {void}
*/
public doAnnotationAnimation(circularPath: Element, progress: ProgressBar, previousEnd?: number, previousTotal?: number): void {
const animation: Animation = new Animation({});
let value: number = 0;
const percentage: number = 100;
const isAnnotation: boolean = progress.annotations.length > 0;
let annotatElementChanged: Element;
let firstAnnotatElement: Element;
const start: number = progress.startAngle;
const totalAngle: number = progress.totalAngle;
let totalEnd: number;
let annotateValueChanged: number;
let annotateValue: number;
if (isAnnotation && progress.progressAnnotationModule) {
firstAnnotatElement = document.getElementById(progress.element.id + 'Annotation0').children[0];
if (firstAnnotatElement && firstAnnotatElement.children[0]) {
if (firstAnnotatElement.children[0].tagName === 'SPAN') {
annotatElementChanged = firstAnnotatElement.children[0];
}
}
}
totalEnd = ((progress.argsData.value - progress.minimum) / (progress.maximum - progress.minimum)) * progress.totalAngle;
progress.annotateTotal = totalEnd =
(progress.argsData.value < progress.minimum) ? 0 : totalEnd;
progress.annotateEnd = start + totalEnd;
annotateValue = ((progress.argsData.value - progress.minimum) / (progress.maximum - progress.minimum)) * percentage;
annotateValue = (progress.argsData.value < progress.minimum) ? 0 :
Math.round(annotateValue);
const startValue: number = (!isNullOrUndefined(previousEnd)) ? previousEnd : start;
const endValue: number = (!isNullOrUndefined(previousEnd)) ? totalEnd - previousTotal : totalEnd;
if (progress.argsData.value <= progress.minimum) {
annotatElementChanged.innerHTML = annotateValue + '%';
} else {
animation.animate((<HTMLElement>circularPath), {
duration: (progress.animation.duration === 0 && animationMode === 'Enable') ? 2000 : progress.animation.duration,
delay: progress.animation.delay,
progress: (args: AnimationOptions): void => {
progress.cancelResize = true;
if (isAnnotation && annotatElementChanged) {
value = effect(args.timeStamp, startValue, endValue, args.duration, false);
annotateValueChanged = parseInt((((Math.round(value) - start) / totalAngle) * percentage).toString(), 10);
annotatElementChanged.innerHTML = annotateValueChanged ? annotateValueChanged.toString() + '%' : '0%';
}
},
end: () => {
progress.cancelResize = false;
annotatElementChanged.innerHTML = annotateValue + '%';
}
});
}
}
private activeAnimate(t: number, start: number, end: number, enableRtl: boolean): number {
const time: number = 1 - Math.pow(1 - t, 3);
const attrValue: number = start + ((!enableRtl) ? (time * end) : -(time * end));
return attrValue;
}
}