JSFiddle - React, Tailwind, and code Playground

HTML

<div style="height: 5000px">
  Scroll down
  <a class="scroll-top" href="#">
    <!-- Hier kann z.B. ein Icon platziert werden -->
    <span>↑</span>
    <svg class="scroll-top__circle" viewBox="0 0 40 40">
      <!-- Die Hälfte der Stärke des Kreises muss vom Radius abgezogen werden, ansonsten wird die Grafik angeschnitten -->
      <circle class="scroll-top__progress" cx="20" cy="20" r="18"/>
    </svg>
  </a>
</div>

CSS

html {
  scroll-behavior: smooth;
}

.scroll-top {
  --size: 40px;
  position: fixed;
  right: 10px;
  bottom: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size);
  height: var(--size);
  color: red;
  text-decoration: none;
}
.scroll-top__circle {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
  pointer-events: none;
  paint-order: stroke;
}
.scroll-top__progress {
  fill: none;
  stroke: currentColor;
  stroke-width: 2; /* Radius beachten */
}

JavaScript

// Element referenzieren
const scrollProgress = document.querySelector('.scroll-top__progress');

// Die CSS-Eigenschaften «stroke-dasharray» und «stroke-dashoffset» während des Scrollens aktualisieren
window.addEventListener('scroll', () => {
  if (scrollProgress) {
    const scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - document.documentElement.clientHeight) * 100);
    const radius = parseFloat(scrollProgress.getAttribute('r'));
    const circumference = 2 * Math.PI * radius;
    const offset = circumference * (1 - (scrollPercentage / 100));
    scrollProgress.style.strokeDasharray = circumference + ' ' + circumference;
    scrollProgress.style.strokeDashoffset = offset;
  }
});

// Scroll-Event auslösen, um die CSS-Eigenschaften erstmalig anzuwenden
window.dispatchEvent(new Event('scroll'));