делаете svg в нем 2 circle
1 circle - незаполненный круг
2 circle - имитируем заполнение, джаваскриптом управляем атрибутами stroke-dasharray, stroke-dashoffset(при необходимости), transform для поворота
у stroke-dasharray есть dash(черта - это и будет заполнением) и gap(пробел) их вам надо расчитывать
пример моего кода из проекта на vuejs
<template lang="pug">
.order-status-wrap
svg.order-status(viewBox="0 0 166 166")
circle(cx="83" cy="83" r="78" fill="none" stroke="#3a3a3f" stroke-width="10")
circle(cx="83" cy="83" r="73" fill="none" stroke="#4dc54f" v-bind:stroke-dasharray="dash+' '+gap" stroke-dashoffset="" stroke-width="5" transform="rotate(-90 83 83)")
.order-status-text
.order-status-label Заказ выполнен на
.order-status-percent {{orderPercent}} %
</template>
<script>
export default {
data() {
return {
orderSteps: 7, // кол-во шагов оформления заказа
orderStepsComplete: 2, // кол-во пройденных шагов
circleRadius: 73, // радиус окружности
}
},
computed: {
orderPercent () { // процент выполнения заказа
return Math.round(this.orderStepsComplete/(this.orderSteps/100))
},
circleLength () { // длина окружности
return this.circleRadius*2*Math.PI;
},
dash () { // черта
return this.circleLength/100*this.orderPercent;
},
gap () { // пробел
return this.circleLength-this.dash;
},
},
}
</script>
ну и докрутить setInterval для анимации