JSFiddle - React, Tailwind, and code Playground

by RAX7

HTML

<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  viewBox="0 0 168 124">
  <defs>
    <mask id="mask">
      <path id="mask-path" d="M151.2,103.5a70,70,0,1,0-134.4,0"
        stroke="#fff" stroke-width="28" fill="none"
        stroke-dasharray="259.65142822265625"
        stroke-dashoffset="399.86319946289063"/>
    </mask>
  </defs>
  
  <circle cx="84" cy="84" r="40" fill="#f8d248"/>
  <text id="text"
    x="84" y="95" text-anchor="middle">
    54
  </text>

  <g id="chart" fill="none" stroke-width="28" stroke-dasharray="4.636632646833148">
    <path d="M151.2,103.5a70,70,0,1,0-134.4,0" stroke="#f2f3fa"/>
    <g mask="url(#mask)">
      <rect width="100%" height="100%"/>
      <path d="M151.2,103.5a70,70,0,1,0-134.4,0" stroke="#f8d248"/>
    </g>
  </g>
</svg>

CSS

body {
  margin: 0.5rem 3rem;
}

#text {
  font-family: Tahoma, sans-serif;
  font-weight: 700;
  font-size: 30px;
  fill: #fff;
}

JavaScript

function animChar(duration) {
  const mask = document.querySelector('#mask-path');
  const chart = document.querySelector('#chart');
  const text = document.querySelector('#text');

  const divideBySegmets = 28;
  const totalLen = mask.getTotalLength();
  const segmetLen = totalLen / (divideBySegmets * 2);

  mask.setAttribute('stroke-dasharray', totalLen);
  chart.setAttribute('stroke-dasharray', segmetLen);

  function setCharProgress(pct) {
    const piece = divideBySegmets * pct;
    mask.setAttribute('stroke-dashoffset', totalLen + segmetLen * 2 * piece);
    text.textContent = Math.round(pct * 100);
  }
  
  let begin;
  function draw(now) {
  	begin = begin || now;
    const t = Math.min(1, (now - begin) / duration);
    
    setCharProgress(t);
    
    if (t !== 1) {
      requestAnimationFrame(draw);
    }
  }
  requestAnimationFrame(draw);
}

animChar(5000);