spb/svgarden Public
SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)
HTML 79.2%
Astro 10.3%
JavaScript 6%
CSS 3.7%
Shell 0.8%
1<!--2 ============================================================3 SVGarden — https://www.svgarden.dev4 Author : Simon-Pierre Boucher5 Contact: contact@spboucher.ai6 File : snippets/scroll/scroll-rotate-badge.html7 Desc : Circular text badge that spins with scroll progress8 ============================================================9-->10<!--svgarden11title: Scroll-spun badge12slug: scroll-rotate-badge13category: scroll14tags: [scroll, badge, textPath, rotate]15difficulty: intermediate16techniques: ["animation-timeline: scroll()", textPath, "@supports", progressive-enhancement]17how_it_works: >18 The round label is ordinary textPath typography on a hidden circle; the19 motion is one rotate keyframe used twice. By default it runs on the regular20 clock (a slow infinite spin), then an @supports block rebinds the very same21 keyframes to animation-timeline: scroll(nearest), so the badge's angle22 becomes a direct readout of how far the box is scrolled — flick fast and it23 whips around, creep and it crawls. Reusing one @keyframes for both the24 fallback and the enhancement is the cleanest progressive-enhancement shape25 for scroll-driven work.26support: "Scroll-linked rotation needs Chrome/Edge 115+; other browsers gracefully fall back to a slow time-based spin."27customizable:28 - { var: "--sg-color", label: "Color", type: color, default: "#7F77DD" }29 - { var: "--sg-size", label: "Box height", type: range, min: 160, max: 360, default: 240, unit: px }30 - { var: "--sg-duration", label: "Fallback spin", type: range, min: 4, max: 30, default: 14, unit: s }31created: 2026-08-1032-->33<div class="sg-scroll-rotate-badge" style="--sg-color:#7F77DD; --sg-size:240px; --sg-duration:14s;">34 <div class="sg-scroll-rotate-badge-track">35 <div class="sg-scroll-rotate-badge-sticky">36 <svg viewBox="0 0 100 100" aria-hidden="true" focusable="false">37 <defs>38 <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" />39 </defs>40 <g class="sg-scroll-rotate-badge-spin">41 <text class="sg-scroll-rotate-badge-text">42 <textPath href="#sg-scroll-rotate-badge-o">scroll · scroll · scroll · scroll · </textPath>43 </text>44 </g>45 <path class="sg-scroll-rotate-badge-arrow" d="M50 40 V60 M43 54 L50 61 L57 54" />46 </svg>47 <span class="sg-scroll-rotate-badge-hint">scroll ↓</span>48 </div>49 </div>50</div>51<style>52 .sg-scroll-rotate-badge {53 width: 100%;54 min-width: 200px;55 height: var(--sg-size);56 overflow-y: scroll;57 border: 1px solid rgba(128, 128, 128, 0.3);58 border-radius: 10px;59 }60 .sg-scroll-rotate-badge-track {61 height: calc(var(--sg-size) * 3);62 }63 .sg-scroll-rotate-badge-sticky {64 position: sticky;65 top: 0;66 height: var(--sg-size);67 display: grid;68 place-items: center;69 }70 .sg-scroll-rotate-badge-sticky svg {71 width: min(60%, calc(var(--sg-size) * 0.7));72 display: block;73 overflow: visible;74 }75 .sg-scroll-rotate-badge-hint {76 position: absolute;77 right: 12px;78 bottom: 8px;79 font-size: 12px;80 opacity: 0.6;81 }82 .sg-scroll-rotate-badge-text {83 font-family: inherit;84 font-size: 10.5px;85 font-weight: 600;86 letter-spacing: 2.5px;87 fill: var(--sg-color);88 }89 .sg-scroll-rotate-badge-arrow {90 fill: none;91 stroke: var(--sg-color);92 stroke-width: 2.5;93 stroke-linecap: round;94 stroke-linejoin: round;95 }96 .sg-scroll-rotate-badge-spin {97 transform-origin: 50% 50%;98 animation: sg-scroll-rotate-badge-turn var(--sg-duration) linear infinite;99 }100 @supports (animation-timeline: scroll()) {101 .sg-scroll-rotate-badge-spin {102 animation: sg-scroll-rotate-badge-turn linear both;103 animation-duration: auto;104 animation-timeline: scroll(nearest);105 }106 }107 @keyframes sg-scroll-rotate-badge-turn {108 from { transform: rotate(0turn); }109 to { transform: rotate(1turn); }110 }111</style>112