Skip to content

Gallery / scroll / Scroll gauge

Scroll gauge

Circular gauge that fills with scroll position

scroll advanced scrollgaugeprogressscroll-timeline
Live preview

⚠ Browser support Scroll-driven animations need Chrome/Edge 115+; other browsers show the gauge fully filled, static.

Techniques: animation-timeline: scroll() · pathLength · scale-property · @supports

Customizeupdates preview, code & clipboard
#7F77DD
260px
8px
Code snippets/scroll/scroll-counter-gauge.html
<!--
  ============================================================
  SVGarden — https://www.svgarden.dev
  Author : Simon-Pierre Boucher
  Contact: contact@spboucher.ai
  File   : snippets/scroll/scroll-counter-gauge.html
  Desc   : Circular gauge that fills with scroll position
  ============================================================
-->
<div class="sg-scroll-counter-gauge" style="--sg-color:#7F77DD; --sg-size:260px; --sg-thick:8px;">
  <div class="sg-scroll-counter-gauge-track">
    <div class="sg-scroll-counter-gauge-sticky">
      <svg viewBox="0 0 100 100" aria-hidden="true" focusable="false">
        <circle class="sg-scroll-counter-gauge-rail" cx="50" cy="50" r="42" pathLength="100" />
        <circle class="sg-scroll-counter-gauge-arc" cx="50" cy="50" r="42" pathLength="100" />
        <text class="sg-scroll-counter-gauge-label" x="50" y="55">scroll</text>
      </svg>
      <span class="sg-scroll-counter-gauge-hint">scroll ↓</span>
    </div>
  </div>
</div>
<style>
  .sg-scroll-counter-gauge {
    width: 100%;
    min-width: 200px;
    height: var(--sg-size);
    overflow-y: scroll;
    border: 1px solid rgba(128, 128, 128, 0.3);
    border-radius: 10px;
  }
  .sg-scroll-counter-gauge-track {
    height: calc(var(--sg-size) * 3);
  }
  .sg-scroll-counter-gauge-sticky {
    position: sticky;
    top: 0;
    height: var(--sg-size);
    display: grid;
    place-items: center;
  }
  .sg-scroll-counter-gauge-sticky svg {
    width: min(55%, calc(var(--sg-size) * 0.65));
    display: block;
  }
  .sg-scroll-counter-gauge-hint {
    position: absolute;
    right: 12px;
    bottom: 8px;
    font-size: 12px;
    opacity: 0.6;
  }
  .sg-scroll-counter-gauge-rail,
  .sg-scroll-counter-gauge-arc {
    fill: none;
    stroke: var(--sg-color);
    stroke-width: var(--sg-thick);
    stroke-linecap: round;
    transform: rotate(-90deg);
    transform-origin: 50% 50%;
  }
  .sg-scroll-counter-gauge-rail { opacity: 0.15; }
  .sg-scroll-counter-gauge-arc {
    stroke-dasharray: 100;
    stroke-dashoffset: 0; /* fallback: full */
  }
  .sg-scroll-counter-gauge-label {
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    fill: var(--sg-color);
    text-anchor: middle;
  }
  @supports (animation-timeline: scroll()) {
    .sg-scroll-counter-gauge-arc {
      animation: sg-scroll-counter-gauge-fill linear both;
      animation-duration: auto;
      animation-timeline: scroll(nearest);
    }
    .sg-scroll-counter-gauge-sticky svg {
      animation: sg-scroll-counter-gauge-breathe linear both;
      animation-duration: auto;
      animation-timeline: scroll(nearest);
    }
  }
  @keyframes sg-scroll-counter-gauge-fill {
    from { stroke-dashoffset: 100; }
    to   { stroke-dashoffset: 0; }
  }
  @keyframes sg-scroll-counter-gauge-breathe {
    from { scale: 0.85; }
    to   { scale: 1; }
  }
</style>

More scroll