OHP 필름을 겹치듯 — conic-gradient와 attr()로 순수 CSS 파이 차트 만들기
conic-gradient, CSS 커스텀 프로퍼티, 새로운 attr() 타입 구문을 활용해 JavaScript 없이 시맨틱한 파이 차트를 구현하는 방법을 다룬다.
파이 차트를 그리겠다고 Chart.js나 D3를 꺼내드는 건 자연스러운 일이다. 하지만 CSS의 conic-gradient()가 등장한 이후, "순수 CSS로도 가능하지 않을까?"라는 질문이 계속 나온다. 가능하다. 다만 예쁘기만 하고 의미 없는 원이 아니라, 시맨틱하고 접근 가능한 차트를 만들려면 몇 가지 함정을 피해야 한다.
단순한 접근의 함정
가장 직관적인 방법은 하나의 div에 conic-gradient()를 때려 넣는 것이다.
.pie {
width: 200px;
height: 200px;
border-radius: 50%;
background: conic-gradient(
#ff6666 0% 35%,
#4fff66 35% 60%,
#66ffff 60% 85%,
#b366ff 85% 100%
);
}이건 비유하면 포스터 한 장에 데이터를 그려놓는 것이다. 사람 눈에는 보이지만, 스크린 리더는 이것이 "초콜릿 35%, 젤리 25%"라는 데이터를 담고 있다는 사실을 전혀 알 수 없다. CSS 그라디언트는 본질적으로 이미지이고, 이미지에는 시맨틱 정보가 없다.
문제가 하나 더 있다. 슬라이스가 늘어날수록 conic-gradient() 안의 색상 정지점을 일일이 수동 계산해야 한다. 이전 슬라이스의 끝을 다음 슬라이스의 시작으로 넘겨야 하니, 유지보수가 악몽이 된다.
[💡 잠깐! 이 용어는?]
conic-gradient(): 원뿔형 그라디언트. 중심점을 기준으로 시계 방향으로 색상이 전환되며, 파이 차트나 도넛 차트를 만드는 데 활용된다. linear-gradient()가 직선이라면, conic-gradient()는 원이다.
시맨틱 HTML 설계 — 데이터는 목록이다
파이 차트의 데이터는 "항목 A가 35%, 항목 B가 25%…"처럼 각 항목의 비중을 나열하는 것이니, HTML에서도 목록으로 표현하는 것이 자연스럽다. 구조가 탄탄한 집에 인테리어를 입히는 것이 맞는 순서다.
<figure>
<figcaption>지난달 사탕 판매 비율</figcaption>
<ul class="pie-chart" role="list">
<li data-percentage="35" data-color="#ff6666" aria-label="초콜릿 35%">
<strong>초콜릿</strong>
</li>
<li data-percentage="25" data-color="#4fff66" aria-label="젤리 25%">
<strong>젤리</strong>
</li>
<li data-percentage="25" data-color="#66ffff" aria-label="하드캔디 25%">
<strong>하드캔디</strong>
</li>
<li data-percentage="15" data-color="#b366ff" aria-label="풍선껌 15%">
<strong>풍선껌</strong>
</li>
</ul>
</figure>figure와 figcaption으로 차트의 맥락을 제공하고, ul > li로 각 데이터 항목을 표현한다. data-percentage와 data-color는 CSS에서 시각적 렌더링에 사용하고, aria-label은 스크린 리더가 각 항목의 의미를 전달할 수 있게 보장한다.
핵심 트릭 — 투명 필름을 겹쳐 쌓기
여기서 핵심 아이디어는 각 li가 자기 슬라이스만 그리고, 전부 같은 위치에 겹쳐놓는 것이다. 마치 투명한 OHP 필름 여러 장을 겹치는 것과 비슷하다. 각 필름에는 파이의 한 조각만 그려져 있고, 전부 합치면 완성된 원이 나타난다.
.pie-chart {
--radius: 100px;
--size: calc(var(--radius) * 2);
position: relative;
width: var(--size);
height: var(--size);
list-style: none;
padding: 0;
margin: 0;
}
.pie-chart li {
position: absolute;
inset: 0;
border-radius: 50%;
}모든 li를 position: absolute로 같은 위치에 쌓고, border-radius: 50%로 원형을 만든다. 이제 각 슬라이스에 conic-gradient()를 적용하면 된다.
[💡 잠깐! 이 용어는?]
attr() 타입 구문: CSS attr() 함수의 확장 기능으로, HTML 속성 값을 특정 타입(number, length 등)으로 파싱해 CSS 수학 연산에 사용할 수 있게 한다. attr(data-percentage number)처럼 사용한다.
attr()로 데이터와 CSS 연결
CSS의 새로운 attr() 타입 구문으로 data-percentage 값을 직접 숫자로 파싱할 수 있다. 각 슬라이스의 시작 각도는 인라인 스타일로 누적값을 전달하는 방식이 현실적이다.
<li data-percentage="35" data-color="#ff6666" style="--start: 0" aria-label="초콜릿 35%">
<strong>초콜릿</strong>
</li>
<li data-percentage="25" data-color="#4fff66" style="--start: 35" aria-label="젤리 25%">
<strong>젤리</strong>
</li>
<li data-percentage="25" data-color="#66ffff" style="--start: 60" aria-label="하드캔디 25%">
<strong>하드캔디</strong>
</li>
<li data-percentage="15" data-color="#b366ff" style="--start: 75" aria-label="풍선껌 15%">
<strong>풍선껌</strong>
</li>--start는 이전 슬라이스들의 퍼센트 합계다. 초콜릿은 0에서 시작, 젤리는 35에서 시작(초콜릿 35%), 하드캔디는 60에서 시작(35+25%), 이런 식이다. 비유하면, 릴레이 경주에서 이전 주자가 끝난 지점에서 다음 주자가 출발하는 것과 같다.
.pie-chart li {
--pct: attr(data-percentage number, 0);
--col: attr(data-color type(<color>), gray);
--from: calc(var(--start) * 3.6deg);
--to: calc((var(--start) + var(--pct)) * 3.6deg);
background: conic-gradient(
transparent 0deg var(--from),
var(--col) var(--from) var(--to),
transparent var(--to) 360deg
);
}3.6deg은 360 / 100이다. 퍼센트 1%가 3.6도에 해당하므로, 퍼센트 값에 3.6을 곱하면 각도로 변환된다. 각 슬라이스는 자기 영역만 색칠하고 나머지는 transparent로 비워두기 때문에, 겹쳐도 간섭 없이 완전한 원이 완성된다.
[💡 잠깐! 이 용어는?]
색상 정지점(Color Stop): 그라디언트에서 특정 색상이 시작되거나 끝나는 위치. conic-gradient(red 0deg 90deg, blue 90deg 180deg)에서 0deg, 90deg, 180deg가 각각 색상 정지점이다.
구현 방식 비교
| 기준 | 단일 conic-gradient | 슬라이스 겹치기 | JavaScript 라이브러리 |
|---|---|---|---|
| 시맨틱 HTML | 불가 | 가능 (ul > li) | 라이브러리 의존 |
| 접근성 | 없음 | aria-label 활용 | 라이브러리 의존 |
| 유지보수 | 정지점 수동 계산 | data 속성만 변경 | API 기반 |
| JavaScript 의존 | 없음 | 없음 | 필수 |
| 브라우저 지원 | 우수 | Chromium 중심 | 우수 |
| 번들 크기 | 0KB | 0KB | 30~200KB |
attr() 타입 구문이 아직 Chromium 기반 브라우저에서만 지원되므로, 프로덕션에서는 폴백이 필요하다.
[💡 잠깐! 이 용어는?]
폴백(Fallback): 특정 기능이 지원되지 않는 환경에서 대체 동작을 제공하는 것. @supports로 attr() 타입 구문 지원 여부를 감지하고, 미지원 시 JavaScript 기반 차트를 로드하는 식으로 구현한다.
마무리
CSS conic-gradient()와 attr() 타입 구문을 조합하면 JavaScript 없이도 시맨틱하고 접근 가능한 파이 차트를 만들 수 있다. 핵심은 단일 그라디언트에 모든 데이터를 우겨넣는 게 아니라, 각 슬라이스를 독립적인 리스트 아이템으로 분리한 뒤 OHP 필름처럼 겹치는 것이다. attr() 타입 구문의 브라우저 지원이 확대되면, 간단한 데이터 시각화에 별도 라이브러리가 필요 없는 날이 올 것이다. 지금 당장은 Chromium 한정이지만, 기법을 익혀두면 충분히 가치가 있다.
참고:
- CSS-Tricks: https://css-tricks.com/trying-to-make-the-perfect-pie-chart-in-css/
- MDN conic-gradient(): https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient
같은 카테고리 · CSS
비슷한 주제의 최신 글
태그가 겹치는 글
공통 태그가 많을수록 위에 보인다