Canvas与SVG深度对比:特性、性能与实战代码
一、核心技术差异
1. 渲染原理
- Canvas:基于像素的位图渲染技术,通过JavaScript API直接操作像素缓冲区
- SVG:基于XML的矢量图形描述语言,通过DOM节点树维护图形元素
2. 图形特性
-
Canvas:
- 绘制后立即光栅化,不保留图形对象信息
- 缩放会导致像素失真
- 适合动态生成图像内容
-
SVG:
- 保留矢量图形信息,无限缩放不失真
- 每个图形元素都是独立DOM节点
- 原生支持CSS样式和事件处理
二、性能对比分析
1. 静态图形渲染
场景 | Canvas | SVG |
---|---|---|
少量元素 | 初始化快 | 渲染稍慢但可接受 |
复杂路径 | 路径计算快 | 解析XML开销较大 |
文本渲染 | 像素化模糊 | 矢量清晰 |
2. 动态图形处理
场景 | Canvas | SVG |
---|---|---|
高频更新 | 60FPS流畅(游戏场景) | DOM操作性能瓶颈 |
元素数量 | 万级元素仍流畅 | 超过千级明显卡顿 |
动画控制 | 需手动实现 | CSS/JS原生支持 |
三、实战代码对比
示例1:绘制动态粒子系统(性能关键场景)
Canvas实现(高性能方案)
// 初始化1000个粒子
const particles = Array(1000).fill().map(() => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: Math.random() * 2 - 1,
vy: Math.random() * 2 - 1,
radius: Math.random() * 3 + 1
}));
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 批量绘制所有粒子
ctx.beginPath();
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
ctx.moveTo(p.x + p.radius, p.y);
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
});
ctx.fillStyle = 'rgba(255,0,0,0.5)';
ctx.fill();
requestAnimationFrame(animate);
}
animate();
SVG实现(交互友好但性能低)
const svg = document.getElementById('svg');
const particles = Array(1000).fill().map(() => {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('r', Math.random() * 3 + 1);
circle.setAttribute('fill', 'rgba(255,0,0,0.5)');
svg.appendChild(circle);
return {
element: circle,
x: Math.random() * 500,
y: Math.random() * 500,
vx: Math.random() * 2 - 1,
vy: Math.random() * 2 - 1
};
});
function animate() {
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
p.element.setAttribute('cx', p.x);
p.element.setAttribute('cy', p.y);
});
requestAnimationFrame(animate);
}
animate();
示例2:创建可交互的组织架构图(SVG优势场景)
SVG实现(支持原生交互)
// 创建
const deptGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
svg.appendChild(deptGroup);
// 添加可点击
const deptRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
deptRect.setAttribute('x', 100);
deptRect.setAttribute('y', 50);
deptRect.setAttribute('width', 200);
deptRect.setAttribute('height', 80);
deptRect.setAttribute('fill', '#4CAF50');
deptRect.style.cursor = 'pointer';
deptRect.addEventListener('click', () => alert('部门详情'));
deptGroup.appendChild(deptRect);
// 添加可拖拽
const employee = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
employee.setAttribute('cx', 200);
employee.setAttribute('cy', 180);
employee.setAttribute('r', 30);
employee.setAttribute('fill', '#2196F3');
employee.setAttribute('draggable', 'true');
deptGroup.appendChild(employee);
Canvas等效实现(需手动处理交互)
// 需要维护对象状态和手动检测交互
const orgChart = {
dept: { x: 100, y: 50, w: 200, h: 80, color: '#4CAF50' },
employee: { x: 200, y: 180, r: 30, color: '#2196F3' }
};
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = orgChart.dept.color;
ctx.fillRect(orgChart.dept.x, orgChart.dept.y, orgChart.dept.w, orgChart.dept.h);
ctx.beginPath();
ctx.arc(orgChart.employee.x, orgChart.employee.y, orgChart.employee.r, 0, Math.PI*2);
ctx.fillStyle = orgChart.employee.color;
ctx.fill();
}
// 手动实现点击检测
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x > orgChart.dept.x && x < orgChart.dept.x + orgChart.dept.w &&
y > orgChart.dept.y && y < orgChart.dept.y + orgChart.dept.h) {
alert('详情');
}
});
draw();
四、选型建议
优先选择Canvas当:
- 需要处理大量动态元素(如游戏、数据可视化)
- 要求高性能渲染(60FPS动画)
- 进行像素级操作(图像处理、滤镜)
- 不需要复杂DOM交互
优先选择SVG当:
- 需要矢量缩放(大屏展示、打印输出)
- 要求内置交互(点击、悬停效果)
- 图形需要CSS样式控制
- 元素数量少于1000个
混合使用策略:
- 主界面使用SVG保证清晰度和交互
- 动态特效使用Canvas提升性能
- 通过
<foreignObject>
在SVG中嵌入Canvas