原生js实现饼图
使用Canvas绘制饼图
在HTML中创建<canvas>元素作为绘图区域,通过JavaScript计算数据比例并绘制扇形。
<canvas id="pieChart" width="400" height="400"></canvas>
function drawPieChart(data) {
const canvas = document.getElementById('pieChart');
const ctx = canvas.getContext('2d');
const total = data.reduce((sum, item) => sum + item.value, 0);
let startAngle = 0;
data.forEach((item, index) => {
const sliceAngle = (item.value / total) * 2 * Math.PI;
ctx.beginPath();
ctx.fillStyle = item.color || getRandomColor();
ctx.moveTo(200, 200);
ctx.arc(200, 200, 150, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fill();
startAngle += sliceAngle;
});
}
function getRandomColor() {
return `#${Math.floor(Math.random()*16777215).toString(16)}`;
}
// 示例数据
const chartData = [
{ value: 30, color: '#FF6384' },
{ value: 50, color: '#36A2EB' },
{ value: 20, color: '#FFCE56' }
];
drawPieChart(chartData);
添加交互效果
为饼图添加鼠标悬停高亮效果,通过检测鼠标位置是否在扇形区域内实现交互。

canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left - 200;
const y = e.clientY - rect.top - 200;
const distance = Math.sqrt(x*x + y*y);
if (distance <= 150) {
const angle = Math.atan2(y, x);
let cumulativeAngle = 0;
data.forEach((item, index) => {
const sliceAngle = (item.value / total) * 2 * Math.PI;
if (angle >= cumulativeAngle && angle < cumulativeAngle + sliceAngle) {
// 高亮处理逻辑
}
cumulativeAngle += sliceAngle;
});
}
});
添加图例和标签
在饼图旁添加说明性文字,显示各数据项的名称和占比。

function drawLegend() {
const legend = document.createElement('div');
legend.style.display = 'flex';
legend.style.flexDirection = 'column';
data.forEach(item => {
const legendItem = document.createElement('div');
legendItem.style.display = 'flex';
legendItem.style.alignItems = 'center';
const colorBox = document.createElement('div');
colorBox.style.width = '20px';
colorBox.style.height = '20px';
colorBox.style.backgroundColor = item.color;
colorBox.style.marginRight = '10px';
const label = document.createElement('span');
label.textContent = `${item.name}: ${((item.value/total)*100).toFixed(1)}%`;
legendItem.appendChild(colorBox);
legendItem.appendChild(label);
legend.appendChild(legendItem);
});
document.body.appendChild(legend);
}
响应式调整
监听窗口大小变化事件,动态调整饼图尺寸。
function resizeCanvas() {
const container = canvas.parentElement;
const size = Math.min(container.clientWidth, container.clientHeight) * 0.8;
canvas.width = size;
canvas.height = size;
drawPieChart(data);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
动画效果实现
为饼图添加加载动画效果,使用requestAnimationFrame逐步绘制。
function animatePieChart() {
let progress = 0;
const duration = 1000; // 动画持续时间(ms)
const startTime = performance.now();
function frame(timestamp) {
progress = Math.min((timestamp - startTime) / duration, 1);
ctx.clearRect(0, 0, canvas.width, canvas.height);
let currentAngle = 0;
data.forEach(item => {
const endAngle = currentAngle + (item.value / total) * 2 * Math.PI * progress;
ctx.beginPath();
ctx.fillStyle = item.color;
ctx.moveTo(200, 200);
ctx.arc(200, 200, 150, currentAngle, endAngle);
ctx.closePath();
ctx.fill();
currentAngle = endAngle;
});
if (progress < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
注意事项
- 确保数据总和不为零,否则会导致计算比例时出现除零错误
- 对于大量数据项,建议合并小比例项目为"其他"类别
- 考虑添加过渡动画时注意性能影响
- 移动端需要额外处理触摸事件






