Progress Bar en forma de Arco
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().