HTML (SVG) and CSS Only Progress Slider

by AntowaKartowa

HTML

<!-- HTML (SVG) & CSS Only! -->

<div class="progress-snippet">
  <div class="progress-display">
    <svg viewBox="0 0 240 240" fill="none">
      <defs>
        <circle
          id="circle"
          r="100"
          cx="120"
          cy="120"
          pathLength="100"
          transform="rotate(-90)"
          transform-origin="center"
        />
      </defs>
      <use href="#circle" class="progress-track" />
      <use href="#circle" class="progress-indicator" />
    </svg>
    <output for="slider" class="big-number"></output>
  </div>

  <div class="progress-controls">
    <label for="slider">
      Progress:
    </label>
    <input
      id="slider"
      type="range"
      value="0"
      min="0"
      max="100"
      step="1"
    />
  </div>
</div>

CSS

/* base styles */

html {
  background: hsl(210deg 15% 6%);
  color: white;
}

body {
  margin: 0;
  padding: 0;
}

.progress-snippet {
  display: grid;
  height: 100vh;
  width: 100vw;
  grid-template-rows: 1fr auto;
}

.progress-display {
  position: relative;
  place-self: center;
}

.progress-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: hsl(0deg 0% 100% / 0.1);
  padding: 16px;
}

svg {
  width: 240px;
  stroke: hsl(45deg 100% 50%);
  stroke-width: 10px;
  stroke-linecap: round;
}

.progress-track {
  stroke: hsl(35deg 30% 20%);
}

.big-number {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 3rem;
  font-weight: bold;
  font-family: monospace;
  text-align: center;
}

#slider {
  display: block;
  width: 300px;
  max-width: 100%;
}

/* progress thumb animation styles */

@property --progress {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

@property --progress-int {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}

@keyframes progress {
  0% { 
    --progress: 1;
    --progress-int: 100;
  }
  100% {
    --progress: 0;
    --progress-int: 0;
  }
}

.progress-snippet {
  --offset-min: 100px;
  --offset-max: 0px;
  --stroke-dashoffset: calc(var(--offset-max) * var(--progress) + var(--offset-min) * (1 - var(--progress)));

  counter-reset: progress-value var(--progress-int);

  timeline-scope: --progress-timeline;
  animation: linear forwards;
  animation-name: progress;
  animation-timeline: --progress-timeline;
  animation-range: contain 0% contain 100%;
}

#slider {
  overflow: hidden;

  &::-webkit-slider-thumb {
    view-timeline: --progress-timeline inline;
  }

  &::-moz-range-thumb {
    view-timeline: --progress-timeline inline;
  }
}

.progress-indicator {
  stroke-dasharray: 100 100;
...