scroll-progress-ring
Декор·Шаблон: Webfolio - Creative Agency & Portfolio Next.js Template·Складність анімації: subtle·Адаптивний: Так

Файли-джерела
- out/home-main/index.html
div.progress-wrap
Summary
Fixed bottom-right circular SVG ring that fills as the user scrolls, doubling as a back-to-top button. Implemented with a single <path> whose stroke-dasharray / stroke-dashoffset are driven by window.scrollY / (scrollHeight - innerHeight) from common/scrollToTop.js.
HTML structure (minimal)
<div class="progress-wrap cursor-pointer">
<svg class="progress-circle svg-content" width="100%" height="100%" viewBox="-1 -1 102 102">
<path d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98" />
</svg>
</div>
Key SCSS tokens
.progress-wrap {
position: fixed;
right: 30px;
bottom: 30px;
width: 46px;
height: 46px;
border-radius: 50%;
background: rgba(255, 255, 255, .04);
cursor: pointer;
opacity: 0;
transform: translateY(15px);
transition: opacity .3s, transform .3s;
z-index: 50;
&.active-progress { opacity: 1; transform: translateY(0); }
svg path {
stroke: $main_color; /* #fd5b38 */
stroke-width: 3;
fill: none;
stroke-dasharray: 307.919;
stroke-dashoffset: 307.919;
transition: stroke-dashoffset 10ms linear;
}
}
Animation logic
// common/scrollToTop.js (paraphrased)
function scrollToTop() {
const path = document.querySelector('.progress-wrap path');
const wrap = document.querySelector('.progress-wrap');
const length = path.getTotalLength();
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;
function update() {
const scroll = window.scrollY;
const height = document.documentElement.scrollHeight - window.innerHeight;
const progress = length - (scroll * length) / height;
path.style.strokeDashoffset = progress;
wrap.classList.toggle('active-progress', scroll > 50);
}
window.addEventListener('scroll', update);
wrap.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
}
Notable details
- Single SVG path acts as both progress ring and back-to-top trigger — no separate button needed.
- Uses
path.getTotalLength()so it scales correctly if the viewBox is changed. - Hidden until 50px scrolled, with a subtle slide-up reveal (
active-progressclass).
Use when
- Long single-page layouts where users benefit from a quick way back to the top.
- Sites that want a small kinetic detail in the corner without busting the layout.
Caveats
- Click behavior assumes default scrolling, not GSAP ScrollSmoother. With ScrollSmoother on,
window.scrollTomay need to be replaced withScrollSmoother.get().scrollTo(0, true). - Ring is 46x46 in the bottom-right corner — screenshot capture may need a tighter region; pipeline likely returns a near-empty crop.