07@property 퍼센트 링
@property 로 등록한 숫자 커스텀 속성이 0에서 100까지 움직이며 링 진행률을 채운다.
쓰임실제 퍼센트가 있는 업로드 진행률, 온보딩 완료율 표시
@property0~100%conic-gradient

<main class="stage">
<div class="ring" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="진행률"></div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: 1.8s; // 0 → 100 → 0 한 사이클
$easing: ease-in-out;
$color: $subject-cream; // 채워지는 색
@include stage($stage-orange);
@property --percent {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
.ring {
--percent: 0;
position: relative;
width: clamp(64px, 18vw, 96px);
height: clamp(64px, 18vw, 96px);
border-radius: 50%;
background: conic-gradient($color calc(var(--percent) * 1%), rgba($subject-ink, .18) 0);
animation: fill $duration $easing infinite;
}
.ring::before {
content: "";
position: absolute;
inset: 8px;
border-radius: 50%;
background: $stage-orange; // 안쪽을 뚫어 링으로 보이게
}
@keyframes fill {
0% { --percent: 0; }
50% { --percent: 100; }
100% { --percent: 0; }
}
@media (prefers-reduced-motion: reduce) {
.ring { animation: none; --percent: 62; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
@property --percent {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
.ring {
--percent: 0;
position: relative;
width: clamp(64px, 18vw, 96px);
height: clamp(64px, 18vw, 96px);
border-radius: 50%;
background: conic-gradient(#fff7e6 calc(var(--percent) * 1%), rgba(23, 20, 26, 0.18) 0);
animation: fill 1.8s ease-in-out infinite;
}
.ring::before {
content: "";
position: absolute;
inset: 8px;
border-radius: 50%;
background: #ff4d1f;
}
@keyframes fill {
0% {
--percent: 0;
}
50% {
--percent: 100;
}
100% {
--percent: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ring {
animation: none;
--percent: 62;
}
}import type { CSSProperties } from "react";
import styles from "./PropertyProgressRing.module.scss";
export type PropertyProgressRingProps = {
/** 스피너 지름 (기본 80px) */
size?: number;
/** 0 → 100 → 0 한 사이클 시간 (기본 1800ms) */
duration?: number;
/** 채워지는 색 */
color?: string;
label?: string;
};
type Vars = CSSProperties & { "--size"?: string; "--duration"?: string; "--color"?: string };
export default function PropertyProgressRing({
size = 80,
duration = 1800,
color = "#fff7e6",
label = "Progress",
}: PropertyProgressRingProps) {
const vars: Vars = {
"--size": `${size}px`,
"--duration": `${duration}ms`,
"--color": color,
};
return (
<div
className={styles.ring}
style={vars}
role="progressbar"
aria-valuemin={0}
aria-valuemax={100}
aria-label={label}
/>
);
}07 @property 퍼센트 링 — 측정 누적면적 1.508% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · CSS 로딩 스피너 9종 — div 하나로 끝나는
이 데모가 속한 글
CSS 로딩 스피너 9종 — div 하나로 끝나는CSS 로딩 스피너는 서버 응답을 기다리는 동안 화면이 멈추지 않았다는 걸 보여주는 작은 반복 애니메이션으로, 이 글은 자바스크립트 없이 CSS만으로 돌아가는 아홉 가지 방식을 모았습니다. 아홉 개 중 세 개는 흔한 이 글의 데모 전부 보기


