Zac Fukuda
058

Circular Progress with Value in Pure JavaScript Class

Svg-based circular progress indicator with value in pure JavaScript class.

Demo


0%

Source Code

Javascript

class CircularProgress {
  width = 0;
  height = 0;
  cx = 0;
  cy = 0;
  r = 0;
  namespace = "http://www.w3.org/2000/svg";
  options = {
    stroke: "DodgerBlue",
    strokeWidth: 4,
    strokeLineCap: "round",
    hasTrack: true,
    trackBackgroundColor: "WhiteSmoke",
  };

  constructor(selectorOrElement, options) {
    this.svg = this.getSvgElement(selectorOrElement);

    if (selectorOrElement === null) return;
    if (options !== undefined && typeof options === "object") {
      this.options = { ...this.options, ...options };
    }
    
    this.width = parseInt(this.svg.getAttribute("width"));
    this.height = parseInt(this.svg.getAttribute("height"));
    this.cx = this.width / 2;
    this.cy = this.height / 2;
    this.r = (this.width - this.options.strokeWidth) / 2;
    this.svg.setAttribute("xmlns", this.namespace);
    this.svg.setAttribute("x", "0");
    this.svg.setAttribute("y", "0");
    this.svg.setAttribute("viewBox", "0 0 " + this.width + " " + this.height);
    this.svg.style.transform = "rotate(270deg)";

    if (this.options.hasTrack) this.addTrack();

    this.addBar();
  }

  isSVG(element) {
    if (typeof element !== "object") return false;
    if (!(element instanceof HTMLElement)) return false;
    
    return element.tagName === "SVG";
  }

  getSvgElement(selectorOrElement) {
    if (typeof selectorOrElement === "string") {
      return document.querySelector(selectorOrElement);
    }

    if (this.isSVG(selectorOrElement)) {
      return selectorOrElement;
    }

    return null
  }

  createBaseCircle() {
    const circle = document.createElementNS(this.namespace, "circle");
    circle.setAttribute("cx", this.cx.toString());
    circle.setAttribute("cy", this.cy.toString());
    circle.setAttribute("r", this.r.toString());
    circle.setAttribute("fill", "none");
    circle.setAttribute("stroke-width", this.options.strokeWidth.toString());

    return circle;
  }

  addTrack() {
    this.track = this.createBaseCircle();
    this.track.setAttribute("stroke", this.options.trackBackgroundColor);
    this.svg.appendChild(this.track);
  }

  addBar() {
    this.bar = this.createBaseCircle();
    this.bar.style.transition = "stroke-dashoffset 500ms";
    this.bar.setAttribute("pathLength", "100");
    this.bar.setAttribute("stroke", this.options.stroke);
    this.bar.setAttribute("stroke-linecap", this.options.strokeLineCap);
    this.bar.setAttribute("stroke-dasharray", "100");
    this.bar.setAttribute("stroke-dashoffset", "100");
    this.svg.appendChild(this.bar);
  }

  reset() {
    this.bar.style.transition = "";
    this.bar.setAttribute("stroke-dashoffset", "100");
    setTimeout(() => {
      this.bar.style.transition = "stroke-dashoffset 500ms";
    })
  }

  set value(x) {
    this.bar.setAttribute("stroke-dashoffset", (100 - x).toString());
  }
}

HTML

This is how to use CircularProgress class along with HTML.

<svg width="64" height="64"></svg>
<div><span id="circular-progress-text">0</span>%</div>
<button id="load-button" type="button">Load</button> 
<button id="cancel-button" type="button">Cancel</button>
<script src="./circular-progress.js"></script>
<script>
	let timer;
	let loaded = 0;
	const circularProgress = new CircularProgress("svg");
	const circularProgressText = document.getElementById("circular-progress-text");
	const loadButton = document.getElementById("load-button");
	const cancelButton = document.getElementById("cancel-button");
	
	function fakeLoad() {
		loaded += Math.round(Math.random() * 20);
		loaded = Math.min(loaded, 100);
		circularProgress.value = loaded;
		circularProgressText.innerHTML = loaded.toString();

		if (loaded < 100) {
			timer = setTimeout(fakeLoad, Math.round(Math.random() * 500));
		}
	}

	loadButton.onclick = function() {
		loaded = 0;
		circularProgress.reset();
		setTimeout(fakeLoad);
	}

	cancelButton.onclick = function() {
		clearTimeout(timer);
	}
</script>

Download

You can download the sample code at:

Download circular-progress.zip (3Kb)