프로그레스 링

Progress Ring

원형 테두리가 채워지는 정도로 정확한 진행률(%)을 보여주는 확정적 로더.

다른 이름: Circular progressDeterminate ring
···
html
<div class="wrap">
  <div class="pr" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-live="polite">
    <svg viewBox="0 0 60 60" aria-hidden="true"><circle cx="30" cy="30" r="26" fill="none" stroke="var(--line)" stroke-width="5"/><circle class="prc" cx="30" cy="30" r="26" fill="none" stroke="var(--accent-3)" stroke-width="5" stroke-linecap="round"/></svg>
    <span class="prn">0%</span>
  </div>
  <span class="cap">stroke-dashoffset = 둘레 &times; (1 &minus; 진행률)</span>
</div>
css
.wrap{display:flex;flex-direction:column;align-items:center;gap:16px}
.pr{position:relative;width:clamp(46px,15vmin,84px);height:clamp(46px,15vmin,84px);display:grid;place-items:center}
.pr svg{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg)}
.prc{stroke-dasharray:163.4;transition:stroke-dashoffset .25s linear}
.prn{font-size:clamp(11px,3.2vmin,16px);font-weight:700;font-variant-numeric:tabular-nums}
.cap{font-size:clamp(9px,2.4vmin,11px);color:var(--muted);letter-spacing:.02em}
js
const C = 163.4;
const circle = document.querySelector('.prc');
const num = document.querySelector('.prn');
const wrap = document.querySelector('.pr');
let pct = 0;
function tick() {
  pct = (pct + 2) % 101;
  circle.style.strokeDashoffset = String(C * (1 - pct / 100));
  num.textContent = pct + '%';
  wrap.setAttribute('aria-valuenow', String(pct));
}
tick();
setInterval(tick, 60);

이 갤러리에서 유일하게 확정적인 로더입니다. 파일 업로드·다운로드·설치 진행률처럼 남은 작업량을 실제로 계산할 수 있을 때만 씁니다 — 계산할 수 없는데 숫자를 보여주면(가짜 진행률) 사용자의 신뢰를 잃습니다. 반대로 진행률을 알 수 있는데 앞서 나온 비확정적 로더(스피너, 점)를 쓰면 정보를 숨기는 셈이라 불필요하게 답답한 인상을 줍니다.

SVG <circle>의 stroke-dasharray로 원 둘레(2πr) 전체를 지정해두고, stroke-dashoffset을 "둘레 × (1 − 진행률)"로 계산해서 바꾸면 그만큼만 테두리가 그려진 것처럼 보입니다. 가운데에 같은 숫자를 텍스트로 함께 보여줘야 시각 장애가 있거나 색을 구분하기 어려운 사용자도 진행률을 알 수 있습니다.

전체 화면을 가리는 로딩이 아니라 특정 영역(카드, 인라인 버튼)의 진행률이라면 이 형태가 적합하고, 여러 파일을 순서대로 처리 중이라면 개별 파일마다 하나씩 대신 전체 개수 대비 처리된 개수를 함께 보여주는 게 낫습니다. role="progressbar"에 aria-valuenow·aria-valuemin="0"·aria-valuemax="100"을 실시간으로 갱신하고, aria-live="polite"인 영역에 텍스트로도 퍼센트를 반영합니다. prefers-reduced-motion에서는 링이 차오르는 트랜지션 시간을 줄이거나 즉시 값만 바뀌게 할 수 있습니다.