Диаграмма donut SVG. Как сделать анимацию закрашивания?
Долгое время вожусь с анимацией закрашивания круга в зависимости от процента. Необходимо реализовать такое:

На данный момент я смог реализовать так:

То есть все как надо, анимация закрашивания работает, но радиус синего круга очень маленький. На данный момент он составляет 69.85699, а необходимо 140. Когда я увеличиваю радиус - получается какая-то каша, то есть анимация начинается не с той же точки и заканчивается вот этим
Выходит, что при маленьком радиусе точка старта стоит на месте, а при большом она движется просто вокруг.
Вот код:
setTimeout(function() {
// var circle = document.getElementById('circle');
// console.log(circle.getTotalLength());
var time = 5; /* how long the timer will run (seconds) */
var initialOffset = '440';
var one_percent = initialOffset/100;
var i = 0;
$('.circle_animation').css('stroke-dashoffset', initialOffset-(1*(initialOffset/time)));
var interval = setInterval(function() {
$('body').find(".statistics-left__donut-number").text(i);
if (i == time) {
clearInterval(interval);
return;
}
$('.circle_animation').css('stroke-dashoffset', initialOffset-((i+1)*(initialOffset/time)));
i++;
}, 1000);
}, 0);
svg{
transform: rotate(-90deg);
}
.circle_animation{
stroke-dasharray: 440;
stroke-dashoffset: 440;
transition: all 1s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg fill="none" xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320">
<g filter="url(#filter0_dd)">
<circle cx="160" cy="160" r="100" fill="#F5F8FD"/>
</g>
<g filter="url(#filter1_ii)">
<mask id="a" fill="#fff">
<path d="M320 160c0 88.366-71.634 160-160 160S0 248.366 0 160 71.634 0 160 0s160 71.634 160 160zm-280.003 0c0 66.276 53.727 120.003 120.003 120.003 66.276 0 120.003-53.727 120.003-120.003 0-66.276-53.727-120.003-120.003-120.003C93.724 39.997 39.997 93.724 39.997 160z"/>
</mask>
<path d="M320 160c0 88.366-71.634 160-160 160S0 248.366 0 160 71.634 0 160 0s160 71.634 160 160zm-280.003 0c0 66.276 53.727 120.003 120.003 120.003 66.276 0 120.003-53.727 120.003-120.003 0-66.276-53.727-120.003-120.003-120.003C93.724 39.997 39.997 93.724 39.997 160z" stroke="#E3E6EC" stroke-width="80" mask="url(#a)"/>
</g>
<g>
<circle id="circle" class="circle_animation" cx="160" cy="160" r="140" stroke-width="40" stroke="#4277FF" stroke-linecap="round" fill="none" data-percent="30"/>
</g>
<defs>
<filter id="filter0_dd" x="22" y="22" width="276" height="276" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-18" dy="-18"/>
<feGaussianBlur stdDeviation="10"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="18" dy="18"/>
<feGaussianBlur stdDeviation="10"/>
<feColorMatrix values="0 0 0 0 0.819608 0 0 0 0 0.85098 0 0 0 0 0.901961 0 0 0 1 0"/>
<feBlend in2="effect1_dropShadow" result="effect2_dropShadow"/>
<feBlend in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
</filter>
<filter id="filter1_ii" x="-20" y="-20" width="360" height="360" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-20" dy="-20"/>
<feGaussianBlur stdDeviation="15"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="20" dy="20"/>
<feGaussianBlur stdDeviation="15"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix values="0 0 0 0 0.819608 0 0 0 0 0.85098 0 0 0 0 0.901961 0 0 0 1 0"/>
<feBlend in2="effect1_innerShadow" result="effect2_innerShadow"/>
</filter>
</defs>
</svg>
В JS скрипте, который отвечает за анимацию я пробовал увеличивать stroke-dashoffset, нашел как измерить длину круга (с помощью getTotalLength()) и подставил в переменную initialOffset - но получается еще хуже. Подскажите, что я делаю не так? Почему радиус так меняет круг?
Загрузил на JSFiddle: https://jsfiddle.net/robdev/d3kuvx1z/1/
Ответы (2 шт):
А можно так
function start() {
let reqAnimFrame = (function() {
return requestAnimationFrame ||
mozRequestAnimationFrame ||
webkitRequestAnimationFrame ||
oRequestAnimationFrame ||
msRequestAnimationFrame ||
function(callback) {
setTimeout(callback, 1000 / 60);
}
})();
let dataCircle = document.querySelectorAll('.progressbar__thumb');
function setProgress(percent, selector) {
let circle = selector.querySelector('.progressbar__thumb');
let total = Math.PI * circle.r.baseVal.value;
circle.style.strokeDasharray = `${total*percent/100} ${total*(1-percent/100)*2}`;
selector.querySelector('text').innerHTML = '<tspan>' + percent.toFixed(0) + '</tspan>%';
}
function circle(final, i) {
let number = -1;
i++;
let selector = '.progress__container:nth-child(' + i + ')';
let mainSelector = document.querySelector(selector);
let myReq = null;
function circleStep() {
myReq = reqAnimFrame(circleStep);
setProgress(number, mainSelector);
if (number >= final) {
cancelAnimationFrame(myReq);
}
number++;
}
circleStep();
}
for (let i = 0; i < dataCircle.length; i++) {
let num = dataCircle[i].getAttribute('data-circle');
circle(num, i);
}
}
document.addEventListener('DOMContentLoaded', () => {
start();
});
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
*,
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
margin: 0;
color: black;
font-size: 16px;
font-family: 'Open Sans', sans-serif;
}
.cards__progress__bar {
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: flex;
-webkit-flex-wrap: wrap;
-moz-flex-wrap: wrap;
-ms-flex-wrap: wrap;
-o-flex-wrap: wrap;
flex-wrap: wrap;
justify-content: space-around;
max-width: 1000px;
margin: 0 auto;
}
.progress__container {
width: 150px;
height: 150px;
margin-bottom: 50px;
}
.progressbar__track {
fill: transparent;
stroke: #ededed;
stroke-width: 2px;
}
.progressbar__thumb {
fill: transparent;
stroke: #ff0036;
stroke-width: 2px;
transform-origin: center;
stroke-dasharray: 0 1;
transform: rotate(180deg);
}
.progressbar text {
font-family: 'Lato', sans-serif;
font-weight: 400;
}
.progressbar text tspan {
fill: #ff0036;
}
<div class="cards__progress__bar">
<div class="progress__container">
<svg class="progressbar" viewbox="0 0 64 64">
<circle class="progressbar__track" cx="50%" cy="50%" r="30"></circle>
<circle class="progressbar__thumb" data-circle="100" cx="50%" cy="50%" r="30"></circle>
<text x=32 y=33 text-anchor=middle dominant-baseline=middle></text>
</svg>
</div>
<!-- .progress__container -->
<div class="progress__container">
<svg class="progressbar" viewbox="0 0 64 64">
<circle class="progressbar__track" cx="50%" cy="50%" r="30"></circle>
<circle class="progressbar__thumb" data-circle="90" cx="50%" cy="50%" r="30"></circle>
<text x=32 y=33 text-anchor=middle dominant-baseline=middle></text>
</svg>
</div>
<!-- .progress__container -->
<div class="progress__container">
<svg class="progressbar" viewbox="0 0 64 64">
<circle class="progressbar__track" cx="50%" cy="50%" r="30"></circle>
<circle class="progressbar__thumb" data-circle="80" cx="50%" cy="50%" r="30"></circle>
<text x=32 y=33 text-anchor=middle dominant-baseline=middle></text>
</svg>
</div>
<!-- .progress__container -->
<div class="progress__container">
<svg class="progressbar" viewbox="0 0 64 64">
<circle class="progressbar__track" cx="50%" cy="50%" r="30"></circle>
<circle class="progressbar__thumb" data-circle="70" cx="50%" cy="50%" r="30"></circle>
<text x=32 y=33 text-anchor=middle dominant-baseline=middle></text>
</svg>
</div>
<!-- .progress__container -->
</div>