Skip to content

Gallery / scroll / Scroll-spun badge

Scroll-spun badge

Circular text badge that spins with scroll progress

scroll intermediate scrollbadgetextPathrotate
Live preview

⚠ Browser support Scroll-linked rotation needs Chrome/Edge 115+; other browsers gracefully fall back to a slow time-based spin.

Techniques: animation-timeline: scroll() · textPath · @supports · progressive-enhancement

Customizeupdates preview, code & clipboard
#7F77DD
240px
14s
Code snippets/scroll/scroll-rotate-badge.html
<!--
  ============================================================
  SVGarden — https://www.svgarden.dev
  Author : Simon-Pierre Boucher
  Contact: contact@spboucher.ai
  File   : snippets/scroll/scroll-rotate-badge.html
  Desc   : Circular text badge that spins with scroll progress
  ============================================================
-->
<div class="sg-scroll-rotate-badge" style="--sg-color:#7F77DD; --sg-size:240px; --sg-duration:14s;">
  <div class="sg-scroll-rotate-badge-track">
    <div class="sg-scroll-rotate-badge-sticky">
      <svg viewBox="0 0 100 100" aria-hidden="true" focusable="false">
        <defs>
          <path id="sg-scroll-rotate-badge-o" d="M50 50 m-36 0 a36 36 0 1 1 72 0 a36 36 0 1 1 -72 0" />
        </defs>
        <g class="sg-scroll-rotate-badge-spin">
          <text class="sg-scroll-rotate-badge-text">
            <textPath href="#sg-scroll-rotate-badge-o">scroll · scroll · scroll · scroll ·&#160;</textPath>
          </text>
        </g>
        <path class="sg-scroll-rotate-badge-arrow" d="M50 40 V60 M43 54 L50 61 L57 54" />
      </svg>
      <span class="sg-scroll-rotate-badge-hint">scroll ↓</span>
    </div>
  </div>
</div>
<style>
  .sg-scroll-rotate-badge {
    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-rotate-badge-track {
    height: calc(var(--sg-size) * 3);
  }
  .sg-scroll-rotate-badge-sticky {
    position: sticky;
    top: 0;
    height: var(--sg-size);
    display: grid;
    place-items: center;
  }
  .sg-scroll-rotate-badge-sticky svg {
    width: min(60%, calc(var(--sg-size) * 0.7));
    display: block;
    overflow: visible;
  }
  .sg-scroll-rotate-badge-hint {
    position: absolute;
    right: 12px;
    bottom: 8px;
    font-size: 12px;
    opacity: 0.6;
  }
  .sg-scroll-rotate-badge-text {
    font-family: inherit;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 2.5px;
    fill: var(--sg-color);
  }
  .sg-scroll-rotate-badge-arrow {
    fill: none;
    stroke: var(--sg-color);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .sg-scroll-rotate-badge-spin {
    transform-origin: 50% 50%;
    animation: sg-scroll-rotate-badge-turn var(--sg-duration) linear infinite;
  }
  @supports (animation-timeline: scroll()) {
    .sg-scroll-rotate-badge-spin {
      animation: sg-scroll-rotate-badge-turn linear both;
      animation-duration: auto;
      animation-timeline: scroll(nearest);
    }
  }
  @keyframes sg-scroll-rotate-badge-turn {
    from { transform: rotate(0turn); }
    to   { transform: rotate(1turn); }
  }
</style>

More scroll