daugaard47's avatar

Radial progress bar

Is there a tutorial on Laracasts based on how to build a Radial progress bar, exactly like the ones on this website? If not here, anywhere else? Would like to use it in a project for a fundraiser to show the status of donations.

0 likes
5 replies
daugaard47's avatar

Got this fully working. I'll just leave this here in case in might help someone. https://codepen.io/daugaard47/pen/OGBbBK

HTML: The data-percent controls the stroke fill. This could be replaced with a dynamic value.

<div class="flex justify-center mt-10">
  <div class="w-1/2">
    <div class="svgbox">
      <div class="progressdiv" data-percent="75"> 
        <svg class="progress" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="50" fill="transparent" stroke-dasharray="314.159" stroke-dashoffset="0" ></circle>
  <circle class="bar" cx="50" cy="50" r="50" fill="transparent" stroke-dasharray="314.159" stroke-dashoffset="0"></circle>
        </svg>
        <img src="https://www.okayplayer.com/wp-content/uploads/2015/07/Barack-Obama-House-Music-Square.jpg" class="rounded-full absolute left-0 top-0" style="width: 92%;left: 50%; top:50%; transform: translate(-50% , -50%);">
      </div>
    </div>
  </div>
</div>

CSS:

body {
  background-color: #1e2d47;
}

.progress {
  display: block;
  margin: 0 auto;
  overflow: visible;
  transform: rotate(-90deg) rotateX(180deg);
}

.progress circle {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1s ease;
  stroke: #f5f5f5;
  stroke-width: 2px;
}

.progress .bar {
  stroke: #d66f6f;
}

.progressdiv {
  position: relative;
}

.svgbox {
  height: 0; /* collapse the container's height */
  width: 100%; /* specify any width you want (a percentage value, basically) */
  /* apply a padding using the following formula */
  /* this formula makes sure the aspect ratio of the container equals that of the svg graphic */
  padding-top: (svg height / svg width) * width-value;
  position: relative; /* create positioning context for svg */
}

/* Adds Text to center*/
/* .progressdiv:after {
  position: absolute;
  top: 50%;
  left: 50%;
 
  font-size: 35px;
  transform: translate(-50%, -50%);
  content: attr(data-percent) " %";
} */


JS:

(function() {
  window.onload = function() {
    var totalProgress, progress;
    const circles = document.querySelectorAll(".progress");
    for (var i = 0; i < circles.length; i++) {
      totalProgress = circles[i]
        .querySelector("circle")
        .getAttribute("stroke-dasharray");
      progress = circles[i].parentElement.getAttribute("data-percent");

      circles[i].querySelector(".bar").style["stroke-dashoffset"] =
        totalProgress * progress / 100;
    }
  };
})();
daugaard47's avatar

Still would like to know how Jeffery does it though.

shuch3n's avatar

Dear daugaard47,

Did you find out how Jeffery did it?

rodrigo.pedra's avatar

@shuch3n inspired by the component here I made this Vue component:

<template>
    <svg xmlns="http://www.w3.org/2000/svg" :width="size" :height="size">
        <circle
            v-if="progress"
            :r="radius"
            :cx="center"
            :cy="center"
            fill="transparent"
            stroke-linecap="round"
            :stroke="color"
            :stroke-width="stroke"
            :stroke-dasharray="progressLength"
            :stroke-dashoffset="perimeter"
            :transform="`rotate(270,${center},${center})`" />
    </svg>
</template>

<script>
export default {
    name: 'RadialProgress',

    props: {
        color: {type: String, default: '#328af1'},
        progress: {type: Number, default: 0},
        size: {type: Number, default: 100},
        stroke: {type: Number, default: 5, validator: (value) => value >= 1},
    },

    computed: {
        radius() {
            const padding = this.stroke + 4;

            return (this.size - padding) / 2;
        },

        center() {
            return this.size / 2;
        },

        perimeter() {
            return 2 * Math.PI * this.radius;
        },

        progressLength() {
            return (1 + Math.max(0, Math.min(this.progress, 1))) * this.perimeter;
        },
    },
};
</script>

The progress attribute should be a value between 0 and 1, so divide by 100 if you got a percentage.

If you don't use Vue, but have fixed values for width, height, stroke width and colors, I guess you could use an approach as @daugaard47 did and just update the stroke-dasharray attribute value with jQuery, using the formula on the progressLength computed property.

2 likes

Please or to participate in this conversation.