TÓM TẮT
- 1 Giới thiệu chung về Circle Progress Bar
- 2 1. Vẽ Circle Progress Bar chỉ dùng HTML + CSS (Pure CSS)
- 3 2. Vẽ Circle Progress Bar bằng SVG
- 4 3. Sử dụng Canvas để vẽ Circle Progress Bar
- 5 4. Thư viện JavaScript Hỗ Trợ Circle Progress Bar
- 6 5. Tối ưu hoá Circle Progress Bar cho SEO và Trải nghiệm Người dùng
- 7 6. Các trường hợp thực tế và mẫu code
- 8 7. Tổng kết và lời khuyên cuối cùng
Giới thiệu chung về Circle Progress Bar
Circle Progress Bar (hoặc Circular Progress Indicator) là một dạng biểu đồ vòng tròn dùng để hiển thị tiến độ, tỷ lệ hoàn thành hoặc thời gian còn lại trong các ứng dụng web và mobile. Khác với thanh tiến độ dạng linear (đường thẳng), Circle Progress Bar mang lại cảm giác trực quan, hiện đại và dễ tùy biến, phù hợp cho các dashboard, landing page, ứng dụng fitness, học trực tuyến, và nhiều tình huống khác.
Lợi ích khi sử dụng Circle Progress Bar
- Trực quan hơn: Vòng tròn cho phép người dùng nhìn thấy phần trăm hoàn thành một cách nhanh chóng mà không cần phải đọc số liệu.
- Thẩm mỹ cao: Thiết kế vòng tròn có thể dễ dàng kết hợp với các màu sắc, gradient và animation để tạo nên giao diện sinh động.
- Tiết kiệm không gian: So với thanh tiến độ dạng ngang, vòng tròn có thể đặt ở bất kỳ vị trí nào mà không chiếm quá nhiều chiều rộng.
- Dễ tùy biến: Thông qua CSS, SVG, Canvas hay các thư viện JavaScript, chúng ta có thể tùy chỉnh độ dày, màu sắc, tốc độ chuyển động, và thậm chí là hình dạng.
Trong bài viết này, chúng ta sẽ đi sâu vào cách vẽ Circle Progress Bar bằng các công nghệ phổ biến như HTML + CSS, SVG, Canvas và một số thư viện JavaScript như Chart.js, ProgressBar.js và React component. Đồng thời, chúng ta sẽ cung cấp các ví dụ thực tế, giải thích chi tiết từng bước, và đưa ra các mẹo tối ưu hoá để đạt chuẩn SEO và trải nghiệm người dùng.
1. Vẽ Circle Progress Bar chỉ dùng HTML + CSS (Pure CSS)
1.1. Nguyên lý hoạt động
Pure CSS Circle Progress Bar thường dựa trên hai nửa vòng tròn (half-circle) được quay (rotate) bằng transform: rotate() và ẩn (clip) một phần để tạo cảm giác vòng tròn đang “đổ” dần. Khi phần trăm tiến độ vượt qua 50%, chúng ta sẽ thay đổi lớp CSS để hiển thị nửa vòng tròn còn lại.

Có thể bạn quan tâm: Cách Vẽ Circle Progress Bar: Hướng Dẫn Từng Bước Chi Tiết Cho Người Mới Bắt Đầu
1.2. Cấu trúc HTML cơ bản
<div class="circular-progress" data-percentage="75"> <span class="progress-value">75%</span>
</div>
.circular-progress: là container của vòng tròn.data-percentage: thuộc tính tùy chỉnh lưu giá trị phần trăm, sẽ được CSS hoặc JavaScript đọc để tính góc quay..progress-value: hiển thị giá trị phần trăm ở trung tâm vòng tròn.
1.3. CSS chi tiết
.circular-progress { position: relative; width: 150px; height: 150px; border-radius: 50%; background: #e6e6e6; / Màu nền vòng tròn chưa đầy / overflow: hidden;
} / Hai lớp nửa vòng tròn /
.circular-progress::before,
.circular-progress::after { content: ""; position: absolute; width: 100%; height: 100%; border-radius: 50%; border: 10px solid transparent; / Độ dày vòng /
} / Nửa vòng tròn màu chính /
.circular-progress::before { border-top-color: #4caf50; / Màu phần đã hoàn thành / transform: rotate(calc(var(--percentage) 3.6deg));
} / Nửa vòng tròn thứ hai, chỉ hiển thị khi >50% /
.circular-progress::after { border-top-color: #4caf50; transform: rotate(180deg); opacity: 0;
} / Khi phần trăm > 50% thì bật lớp after /
.circular-progressdata-percentage="50"::after,
.circular-progressdata-percentage="75"::after,
.circular-progressdata-percentage="100"::after { opacity: 1;
} / Giá trị phần trăm ở giữa /
.circular-progress .progress-value { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: bold; color: #333;
}
Giải thích các đoạn quan trọng
--percentagelà CSS custom property (variable) chúng ta sẽ set bằng JavaScript dựa trêndata-percentage.calc(var(--percentage) 3.6deg)vì 100% = 360 độ → mỗi phần trăm = 3.6 độ.- Khi phần trăm > 50%, chúng ta bật
.circular-progress::afterđể vẽ nửa vòng tròn còn lại, tránh hiện tượng “đứt” ở 180 độ.
1.4. JavaScript để set percent
document.querySelectorAll('.circular-progress').forEach(el => { const percent = el.dataset.percentage; el.style.setProperty('--percentage', percent);
});
Sau khi chạy script này, mọi vòng tròn sẽ tự động hiển thị đúng mức độ tiến độ.
1.5. Mở rộng: Thêm gradient và animation
.circular-progress::before,
.circular-progress::after { border-top-color: transparent; border-image: linear-gradient(45deg, #ff6a00, #ffb400) 1;
}
.circular-progress::before { transition: transform 1s ease-out;
}
border-imagecho phép tạo gradient trên viền vòng tròn.transitiontạo hiệu ứng mượt khi thay đổi phần trăm.
2. Vẽ Circle Progress Bar bằng SVG
SVG (Scalable Vector Graphics) là giải pháp mạnh mẽ cho các biểu đồ vector, cho phép tính toán góc quay chính xác và hỗ trợ animation mượt mà.
2.1. Cấu trúc SVG cơ bản

Có thể bạn quan tâm: Cách Vẽ Chữ “đoàn Kết” Đẹp Và Ấn Tượng: Hướng Dẫn Chi Tiết Từng Bước
<svg class="svg-progress" width="200" height="200" viewBox="0 0 200 200"> <circle class="bg-circle" cx="100" cy="100" r="90" stroke-width="20" /> <circle class="progress-circle" cx="100" cy="100" r="90" stroke-width="20" /> <text class="progress-text" x="50%" y="50%" dy=".3em" text-anchor="middle">0%</text>
</svg>
.bg-circle: vòng tròn nền (màu xám)..progress-circle: vòng tròn tiến độ (màu xanh)..progress-text: hiển thị phần trăm ở trung tâm.
2.2. CSS cho SVG
.svg-progress { transform: rotate(-90deg); / Đặt điểm bắt đầu ở 12h /
}
.bg-circle { fill: none; stroke: #e6e6e6;
}
.progress-circle { fill: none; stroke: #4caf50; stroke-linecap: round; stroke-dasharray: 565.48; / 2πr (r=90) / stroke-dashoffset: 565.48; / Ẩn toàn bộ / transition: stroke-dashoffset 1s ease-out;
}
.progress-text { font-size: 2rem; fill: #333; transform: rotate(90deg); / Đảo lại vì SVG đã xoay /
}
2.3. Tính toán stroke-dashoffset
stroke-dasharray xác định độ dài vòng tròn. Với bán kính r = 90, chu vi = 2 π r ≈ 565.48. Khi muốn hiển thị p%, offset = 565.48 (1 - p/100).
function setProgress(svgEl, percent) { const circle = svgEl.querySelector('.progress-circle'); const text = svgEl.querySelector('.progress-text'); const radius = circle.r.baseVal.value; const circumference = 2 Math.PI radius; circle.style.strokeDasharray = `${circumference}`; const offset = circumference (1 - percent / 100); circle.style.strokeDashoffset = offset; text.textContent = `${percent}%`;
} // Ví dụ sử dụng
const svg = document.querySelector('.svg-progress');
setProgress(svg, 68);
2.4. Thêm animation khi tăng dần
function animateProgress(svgEl, from, to, duration = 1500) { const start = performance.now(); const step = (now) => { const elapsed = now - start; const progress = Math.min(elapsed / duration, 1); const current = from + (to - from) progress; setProgress(svgEl, Math.round(current)); if (progress < 1) requestAnimationFrame(step); }; requestAnimationFrame(step);
} // Gọi
animateProgress(svg, 0, 85);
3. Sử dụng Canvas để vẽ Circle Progress Bar
Canvas cho phép vẽ pixel-by-pixel, thích hợp khi cần tính toán phức tạp hoặc vẽ nhiều vòng tròn trên cùng một canvas.
3.1. HTML cấu trúc

Có thể bạn quan tâm: Cách Vẽ Chữ Đẹp Nhất: Hướng Dẫn Chi Tiết Từ Cơ Bản Đến Nâng Cao
<canvas id="canvasProgress" width="200" height="200"></canvas>
3.2. JavaScript vẽ
function drawCanvasProgress(ctx, percent, options = {}) { const { radius = 90, lineWidth = 20, bgColor = '#e6e6e6', progressColor = '#4caf50', textColor = '#333', font = '24px Arial' } = options; const centerX = ctx.canvas.width / 2; const centerY = ctx.canvas.height / 2; ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Vòng nền ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 Math.PI); ctx.strokeStyle = bgColor; ctx.lineWidth = lineWidth; ctx.stroke(); // Vòng tiến độ const startAngle = -Math.PI / 2; // Bắt đầu từ 12h const endAngle = startAngle + (2 Math.PI) (percent / 100); ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, endAngle, false); ctx.strokeStyle = progressColor; ctx.lineCap = 'round'; ctx.lineWidth = lineWidth; ctx.stroke(); // Text ctx.font = font; ctx.fillStyle = textColor; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(`${percent}%`, centerX, centerY);
} // Khởi tạo
const canvas = document.getElementById('canvasProgress');
const ctx = canvas.getContext('2d');
drawCanvasProgress(ctx, 0); // Animation
let current = 0;
const target = 73;
function animate() { if (current <= target) { drawCanvasProgress(ctx, current); current++; requestAnimationFrame(animate); }
}
animate();
3.5. Khi nào nên dùng Canvas?
- Khi cần vẽ nhiều vòng tròn trên một khung hình (ví dụ: dashboard với 10+ biểu đồ).
- Khi muốn tùy chỉnh độ mờ, gradient phức tạp mà CSS/SVG không hỗ trợ tốt.
- Khi muốn tối ưu hiệu năng trong trường hợp cập nhật thường xuyên (game, live data).
4. Thư viện JavaScript Hỗ Trợ Circle Progress Bar
4.1. ProgressBar.js
ProgressBar.js là một thư viện nhẹ (khoảng 15KB) cho phép tạo các progress bar dạng linear, semi-circle và circle.
Cài đặt
npm install progressbar.js
Ví dụ cơ bản
import ProgressBar from 'progressbar.js'; const bar = new ProgressBar.Circle('#container', { color: '#ff6a00', strokeWidth: 8, trailColor: '#eee', trailWidth: 8, duration: 1400, easing: 'easeInOut', text: { value: '0%', style: { color: '#333', position: 'absolute', left: '50%', top: '50%', padding: 0, margin: 0, transform: { prefix: true, value: 'translate(-50%, -50%)' } } }, from: { color: '#ff6a00' }, to: { color: '#4caf50' }, step: (state, circle) => { circle.path.setAttribute('stroke', state.color); const value = Math.round(circle.value() 100); circle.setText(`${value}%`); }
}); bar.animate(0.78); // 78%
4.2. Chart.js – Doughnut Chart
Chart.js cung cấp doughnut (bánh vòng) có thể được sử dụng như một progress bar.
Cài đặt
npm install chart.js
Cấu hình
import { Chart, DoughnutController, ArcElement, Tooltip, Legend } from 'chart.js';
Chart.register(DoughnutController, ArcElement, Tooltip, Legend); const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, { type: 'doughnut', data: { datasets: { data: 75, 25, // 75% tiến độ, 25% còn lại backgroundColor: '#4caf50', '#e6e6e6', borderWidth: 0 } }, options: { cutout: '80%', // Độ dày vòng tròn rotation: -90, plugins: { tooltip: { enabled: false }, legend: { display: false }, legend: false, doughnutlabel: { labels: { text: '75%', font: { size: '24' }, color: '#333' } } } }
});
Lưu ý: Đối với Chart.js 4+, cần cài đặt plugin
chartjs-plugin-doughnutlabelnếu muốn hiển thị text ở giữa.
4.3. React Circular Progress Bar
Nếu bạn đang dùng React, có thể dùng component react-circular-progressbar.
Cài đặt
npm install react-circular-progressbar
Sử dụng
import React from 'react';
import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
import 'react-circular-progressbar/dist/styles.css'; function MyProgress({ percent }) { return ( <div style={{ width: 200, height: 200 }}> <CircularProgressbar value={percent} text={`${percent}%`} styles={buildStyles({ rotation: 0.25, // bắt đầu từ 12h strokeLinecap: 'round', textSize: '16px', pathTransitionDuration: 1, pathColor: `rgba(62, 152, 199, ${percent / 100})`, textColor: '#333', trailColor: '#d6d6d6', backgroundColor: '#f8f8f8', })} /> </div> );
}
Component này tự động xử lý animation, responsive, và cho phép tùy biến màu sắc qua buildStyles.
5. Tối ưu hoá Circle Progress Bar cho SEO và Trải nghiệm Người dùng

Có thể bạn quan tâm: Cách Vẽ Chữ Trên Tay: Hướng Dẫn Chi Tiết Từ Cơ Bản Đến Nâng Cao
5.1. Tối ưu tốc độ tải trang
- Lazy Load: Nếu Circle Progress Bar nằm dưới fold, hãy khởi tạo khi người dùng cuộn tới (
IntersectionObserver). - Sử dụng SVG inline thay vì file
.svgđể giảm số request. - Compress CSS/JS: Dùng công cụ như
cssnano,terserđể nén file. - Cache: Đặt header
Cache-Controlcho các asset tĩnh.
5.2. Accessibility (Truy cập)
- Thêm
role="progressbar"vàaria-valuenow,aria-valuemin,aria-valuemaxđể trợ giúp screen reader.
<div class="circular-progress" role="progressbar" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100"> <span class="progress-value">68%</span>
</div>
- Đảm bảo màu sắc đủ độ tương phản (WCAG 2.1 AA: contrast ≥ 4.5:1).
5.3. Responsive Design
- Sử dụng
%hoặcvwđể kích thước vòng tròn tự động thích ứng.
.circular-progress { width: 30vw; height: 30vw;
}
- Với SVG, thuộc tính
viewBoxcho phép tự động co giãn.
5.4. Structured Data (Schema)
Nếu Circle Progress Bar hiển thị tiến độ dự án, bài học hay mục tiêu, bạn có thể sử dụng Schema.org ProgressBar (không có chuẩn chính thức, nhưng có thể dùng CreativeWork + interactionStatistic).
{ "@context": "https://schema.org", "@type": "CreativeWork", "name": "Tiến độ khóa học React", "interactionStatistic": { "@type": "InteractionCounter", "interactionType": "https://schema.org/FollowAction", "userInteractionCount": 78 }
}
Điều này giúp công cụ tìm kiếm hiểu nội dung và có thể hiển thị rich snippet.
6. Các trường hợp thực tế và mẫu code
6.1. Dashboard quản lý dự án

- Mỗi dự án có một Circle Progress Bar hiển thị phần trăm hoàn thành.
- Dữ liệu lấy từ API (JSON) và cập nhật bằng
fetch+setInterval.
async function loadProjects() { const res = await fetch('/api/projects'); const projects = await res.json(); const container = document.getElementById('projects-dashboard'); container.innerHTML = ''; projects.forEach(p => { const div = document.createElement('div'); div.className = 'project-card'; div.innerHTML = ` <svg class="svg-progress" width="120" height="120" viewBox="0 0 200 200"> <circle class="bg-circle" cx="100" cy="100" r="90" stroke-width="15"></circle> <circle class="progress-circle" cx="100" cy="100" r="90" stroke-width="15"></circle> <text class="progress-text" x="50%" y="50%" dy=".3em" text-anchor="middle"></text> </svg> <h3>${p.name}</h3> `; container.appendChild(div); setProgress(div.querySelector('.svg-progress'), p.progress); });
}
loadProjects();
6.2. Ứng dụng fitness – Đếm bước chân
- Vòng tròn hiển thị % mục tiêu bước chân trong ngày.
- Cập nhật mỗi 5 giây từ API của thiết bị.
setInterval(() => { const steps = getCurrentSteps(); // giả sử hàm trả về số bước hiện tại const percent = Math.min((steps / 10000) 100, 100); animateProgress(document.querySelector('#fitnessProgress'), 0, percent);
}, 5000);
6.3. Landing page – Countdown timer dạng vòng tròn
- Dùng
stroke-dashoffsetgiảm dần theo thời gian còn lại.
function startCountdown(duration) { const end = Date.now() + duration 1000; const svg = document.querySelector('#countdownSvg'); const circle = svg.querySelector('.progress-circle'); const radius = circle.r.baseVal.value; const circumference = 2 Math.PI radius; circle.style.strokeDasharray = `${circumference}`; function update() { const remaining = Math.max(0, end - Date.now()); const percent = (remaining / (duration 1000)) 100; const offset = circumference (1 - percent / 100); circle.style.strokeDashoffset = offset; if (remaining > 0) requestAnimationFrame(update); } update();
}
startCountdown(60); // 60 giây
7. Tổng kết và lời khuyên cuối cùng
Circle Progress Bar là một thành phần UI đa năng, giúp truyền tải thông tin tiến độ một cách sinh động và hiệu quả. Trong bài viết này, chúng ta đã:
- Hiểu nguyên lý hoạt động của Circle Progress Bar qua CSS, SVG, Canvas.
- Xây dựng các ví dụ thực tế từ Pure CSS, SVG + JavaScript, Canvas.
- Khám phá các thư viện như ProgressBar.js, Chart.js, React Circular Progress Bar để giảm thời gian phát triển.
- Áp dụng các kỹ thuật tối ưu cho SEO, tốc độ tải trang, accessibility và responsive design.
- Triển khai trong các trường hợp thực tế: dashboard dự án, ứng dụng fitness, countdown timer.
Những lưu ý quan trọng khi triển khai
- Lựa chọn công nghệ phù hợp: Pure CSS cho các trường hợp đơn giản, SVG cho độ chính xác và animation, Canvas cho đa dạng biểu đồ và hiệu năng.
- Đừng quên ARIA để người dùng khuyết tật cũng có thể hiểu được tiến độ.
- Sử dụng CSS Variables để dễ dàng thay đổi màu sắc và độ dày mà không phải sửa lại JavaScript.
- Kiểm tra trên các trình duyệt: Safari, Edge, Chrome, Firefox đều hỗ trợ tốt, nhưng một số thuộc tính
stroke-dasharraycó thể cần prefix trong phiên bản cũ. - Giữ mã sạch: Tách CSS, JS và HTML, sử dụng module hoặc component để tái sử dụng.
Bằng cách nắm vững các kiến thức trên, bạn hoàn toàn có thể tạo ra những Circle Progress Bar đẹp mắt, mượt mà và thân thiện với người dùng, đồng thời tối ưu hoá cho SEO và hiệu suất trang web. Chúc bạn thành công trong các dự án front‑end tiếp theo!
