Progress Bar en forma de Arco

Las progress bar habitualmente consisten en una barra de color que va aumentando o disminuyendo de longitud mientras ocurre algo: cargar un fichero, recorrer un form multistep... En esta aplicación te presento lo que podriamos llamar una progress arc en lugar de una barra creciente se usa un arco de circunferencia, o un sector circular, para ilustrar el progreso de una tarea.

Solución

<script>

/*Progress bar con forma de arco o sector circular*/

/*Convertir polres (radio, ángulo) a cartesianas (x, y)*/

function polarToCartesian(centerX, centerY, radius, angleInDegrees) {

  let angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

  return {

  x: centerX + (radius * Math.cos(angleInRadians)),

  y: centerY + (radius * Math.sin(angleInRadians))

  };

}

/*Construir el path del arco*/

function describeArc(x, y, radius, startAngle, endAngle){

  let start = polarToCartesian(x, y, radius, endAngle);

  let end = polarToCartesian(x, y, radius, startAngle);

  let largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

  let d = [

   "M", start.x, start.y,

   "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y

  ].join(" ");

  return d;

}

/*Clase para un arco, los datos se obtienen del elemento del DOM con id = ident */

class ProgressArc{

   constructor(ident){

    let container = document.getElementById(ident);

    this.end = parseInt(container.dataset.value);

    this.end = (this.end > 100)?100:this.end;

    this.radio = parseInt(window.getComputedStyle(container).height)/2;

    this.text = container.getElementsByTagName('div')[0];

    this.arc = container.getElementsByTagName('svg')[0];

    this.actual = 0

    this.arc.style.height = this.radio*2+'px';

    }

/*Animación */

play(){

    this.timer = setInterval(this.draw.bind(this), 25)

}

/*Dibuja el arco*/

draw(){

    let path = this.arc.children[0]

    let angle = 0;

    this.actual++;

    angle = this.actual*3.599;

    if (this.actual >= this.end){

    clearInterval(this.timer)

    path.setAttribute("d", describeArc(this.radio, this.radio, this.radio, 0, angle));

    }

else

    path.setAttribute("d", describeArc(this.radio, this.radio, this.radio, 0, angle));

    this.text.innerText = this.actual+"%";

    }

}

/*Crear los objetos para cada progressArc de la página*/

    window.onload = function() {

    var arco = new ProgressArc('arco');

    arco.play();

}

</script>

<style>

*{

  margin: 0;

  padding: 0;

  font-family: 'Arial', sans-serif;

}

/*Para centrar en la página*/

html, body{

   display:grid;

   height:100%;

   text-align: center;

   place-items: center;

   background: #dde6f0;

}

.progressArc{

  position: relative;

  display: flex;

  align-content: center;

  align-items: center;

  justify-content: center;

  text-align: center;

  height: 250px;

  width: 250px;

}

.progressArc div{

z-index:1;

}

.progressArc svg{

  background: rgb(134, 237, 81);

  border-radius: 50%;

  aspect-ratio: 1 / 1;

  height: 100%;

  position: absolute;

  top: 0;

  left: 0;

  z-index:0;

}

</style>

<body>

<div class="progressArc" id="arco" data-value = "75">

<div></div>

<svg><path fill="none" stroke="#446688" stroke-width="20"/></svg>

</div>

</body>

Explicación

La barra se logra que sea circular usando un arco de circunferencia de radio fijo y cuayo ángulo de apertura irá variando desde 0 hata un máximo.

Este arco se va a dibujar mediante un objeto SVG y los valores de radio y ángulo máximo están en el elemento HTML

El path o gráfico de un círculo en SVG usa coordenadas cartesianas para indicar el inicio del arco y final del arco, y los radios del arco (realmente un arco pra SVG es una porción de una elipse, si ambos radios son iguales tenemos una circunferencia).

Para nosotros es más intuitivo ver el arco omo un radio que barre (gira) un cierto ángulo. Si un radio gira 90 grados tenemos un cuadrante y si gira 360 grados tenemos una circunferencia. En HTML usamos radios y ángulos, por ello es necesaria una función que pase de un sistema de referencia (radio/ángulo: polar) a un sitema cartesian(coordenadas horizontal, vertical).

El arco parte del ángulo 0 (las 12) y el radio viene dado por el atributo height width del contenedor. Lógicamente deben ser iguales.

El núcleo del programa es una clase para crear objetos progress bar circulares, es la clase ProgressArc. Sus propiedades son el contenedor con el que se construye el arco, un punto final, un texto (para anotar el ángulo recorrido en %) y el radio. Las restantes propiedades son para uso de sus métodos: la imagen SVG (arc), posicion actual (actual).

Los métodos son play() para controlar la animación y draw() para dibujar el arco.

El método play usa el temporizador del navegador (setInterval) que llama al método draw cada 25ms. Le pasa un argumento (usando bind) que es el propio objeto.

El método draw va redibujando el objeto cada vez que es llamado. en cada paso incrementa el ángulo del arco hasta llegar al punto final, en ese momento detiene el temporizador

Para dibujar el arco usa la función describeArc(), con argumentos un punto de origen (x,y) y un inicio y final del arco (startAngle, endAngle). Para sibujar el arco (un path SVG) se necesitan usar coordenadas cartesianas por eso es necesaria la función polarToCartesian().