Canvas vs SVG for React Charting
Canvas and SVG are the two paths to interactive graphics in the browser, and each excels in different scenarios. SVG renders individual shapes as DOM elements, making them easy to interact with and style; Canvas is a drawing surface where you paint pixels imperatively, offering unmatched performance on large datasets. This article compares them head-to-head: when to use Canvas, when to use SVG, and how to decide for your React chart.
Understanding the Rendering Model: Immediate vs Retained Graphics
SVG uses a retained mode graphics model: you declare shapes (<circle>, <path>, <rect>), and the browser maintains them as DOM elements. Want to change a circle's color? Modify its fill attribute, and the browser re-renders that element. This is declarative and comfortable for React.
Canvas uses an immediate mode model: you call drawing functions (ctx.fillRect(), ctx.strokePath(), ctx.arc()) to paint pixels on a bitmap. The canvas has no memory of shapes; once drawn, they're indistinguishable pixels. To change a shape, you typically clear the canvas and redraw everything. This is imperative and fast.
| Aspect | SVG | Canvas |
|---|---|---|
| Rendering | Retained DOM elements | Immediate pixel painting |
| Element count limit | 10k–30k (then slow) | 1M+ pixels per frame |
| Event handling | Native DOM events per element | Manual collision detection |
| Accessibility | Built-in with <title>, <desc>, ARIA | Requires fallback content |
| Interactivity | Simple hover/click via React | Complex (track state manually) |
| Learning curve | Low (React-like) | Medium-high (graphics knowledge) |
| Exporting | Trivial (copy SVG DOM) | Requires serialization (.toDataURL()) |
| Mobile performance | Good up to 5k elements | Excellent |
| Customization | High (style each element) | Maximum (pixel-level control) |
When to Use SVG
SVG excels when:
-
Dataset is small-to-medium (<10k points). Each point might be a circle or marker; rendering 10k circles as SVG elements is still fast enough on modern hardware.
-
You need rich interactivity. Hover over a bar to highlight it, click to filter, drag to zoom. SVG elements are DOM nodes, so native events work directly:
onClick,onMouseEnter,onMouseLeave. No collision detection code needed. -
You want accessibility out of the box. SVG supports
<title>,<desc>, and ARIA labels. Screen readers can announce chart content. Canvas has no native accessibility; you must build it manually. -
You're building with a library. Recharts, Visx, and most React charting libraries default to SVG. Switching to Canvas for these is often overkill.
-
You need to export as an image. Copying SVG DOM or exporting to PDF is straightforward. Canvas requires serialization and extra steps.
// SVG example: simple, interactive bar chart
export function InteractiveBarChartSVG({ data }) {
const [hoveredIndex, setHoveredIndex] = useState(null);
return (
<svg width="400" height="300">
{data.map((d, i) => (
<rect
key={i}
x={i * 40}
y={300 - d.value}
width="35"
height={d.value}
fill={hoveredIndex === i ? "orange" : "steelblue"}
onMouseEnter={() => setHoveredIndex(i)}
onMouseLeave={() => setHoveredIndex(null)}
style={{ transition: "fill 0.2s", cursor: "pointer" }}
/>
))}
</svg>
);
}
The interactivity is trivial: React state drives the fill color, and browser events update the state.
When to Use Canvas
Canvas excels when:
-
Dataset is huge (100k+ points). Rendering 100k circles as SVG elements will freeze the browser. Canvas draws 1M points per frame without breaking a sweat.
-
You need custom pixel-level control. Gradients, blurs, complex clipping, or effects that SVG doesn't support natively? Canvas lets you paint arbitrary pixels.
-
You're building for real-time data. Stock tickers, live sensor streams, or game-like visualizations benefit from Canvas's imperative, loop-based update model. You can optimize redraw loops at a level SVG doesn't expose.
-
Your chart is mostly static. If users aren't hovering/clicking every element, the interactive complexity of Canvas is worthwhile. Example: a pre-rendered heat map or network diagram.
Example: drawing 100,000 random circles as a performance test:
export function HighPerformanceCanvasChart({ data }) {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const render = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw all points
data.forEach((d) => {
ctx.beginPath();
ctx.arc(d.x, d.y, d.radius, 0, Math.PI * 2);
ctx.fillStyle = d.color;
ctx.fill();
});
requestAnimationFrame(render); // 60 FPS
};
render();
}, [data]);
return <canvas ref={canvasRef} width="800" height="600" />;
}
This renders smoothly even with 100k+ points because Canvas just paints pixels, not DOM.
Real-World Comparison: Zooming a Time-Series Chart
Let's compare SVG and Canvas when building a zoomable time-series (a common dashboard task):
SVG approach: Store zoom domain in state, recompute scales, re-render SVG paths. React handles the declarative update.
Canvas approach: Store zoom domain in state, clear canvas on every state change, redraw paths using imperative calls. You must manually manage the rendering loop.
// SVG: declarative
export function SVGTimeSeriesZoom({ data }) {
const [domain, setDomain] = useState([0, data.length - 1]);
const filteredData = data.slice(domain[0], domain[1]);
const xScale = scaleLinear()
.domain(domain)
.range([0, 600]);
const yScale = scaleLinear()
.domain([0, Math.max(...filteredData.map((d) => d.value))])
.range([300, 0]);
return (
<div>
<svg width="600" height="300">
{/* SVG paths computed from scales and data */}
{/* ... path d={...} ... */}
</svg>
<button onClick={() => setDomain([domain[0] + 10, domain[1] - 10])}>
Zoom In
</button>
</div>
);
}
Canvas: imperative
export function CanvasTimeSeriesZoom({ data }) {
const canvasRef = useRef(null);
const [domain, setDomain] = useState([0, data.length - 1]);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const filteredData = data.slice(domain[0], domain[1]);
const xScale = scaleLinear()
.domain(domain)
.range([0, 600]);
const yScale = scaleLinear()
.domain([0, Math.max(...filteredData.map((d) => d.value))])
.range([300, 0]);
// Redraw on every state change
ctx.clearRect(0, 0, 600, 300);
ctx.strokeStyle = "steelblue";
ctx.beginPath();
filteredData.forEach((d, i) => {
const x = xScale(domain[0] + i);
const y = yScale(d.value);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}, [domain, data]);
return (
<div>
<canvas ref={canvasRef} width="600" height="300" />
<button onClick={() => setDomain([domain[0] + 10, domain[1] - 10])}>
Zoom In
</button>
</div>
);
}
The Canvas version is more verbose because you're managing rendering imperatively. SVG is concise because React handles re-rendering declaratively.
Performance Numbers: SVG vs Canvas in 2026
Recent benchmarks (measuring FPS and memory on modern hardware):
| Scenario | SVG | Canvas |
|---|---|---|
| 1k points, 60 FPS re-render | 60 FPS | 60 FPS |
| 10k points, 60 FPS re-render | 45–50 FPS | 60 FPS |
| 100k points, 60 FPS re-render | Freezes | 55–60 FPS |
| 10k points + hover interaction | 60 FPS (trivial) | 55 FPS (calc collision) |
| Export to image | Immediate | ~100 ms serialization |
The takeaway: SVG handles typical dashboards effortlessly; Canvas is essential only for very large datasets or real-time applications.
Key Takeaways
- SVG is DOM-based, declarative, and React-friendly; best for interactive charts with fewer than 10k elements.
- Canvas is bitmap-based, imperative, and pixel-perfect; best for large datasets and custom graphics.
- Choose SVG for accessibility and ease; Canvas for performance and customization.
- Most React charting libraries use SVG; learn Canvas when you hit a real performance bottleneck.
- Hybrid approaches (e.g., Canvas for large plot, SVG for overlay labels) are common in production dashboards.
Frequently Asked Questions
Can I mix Canvas and SVG in one chart?
Yes. Draw data on Canvas for performance, overlay interactive labels or legends in SVG. This is common: Canvas for the expensive computation, SVG for the accessible, interactive UI layer.
How do I add tooltips to a Canvas chart?
Track mouse position and calculate which data point is nearest. Update a hidden state, then render a tooltip div (or SVG overlay) in React. This combines the performance of Canvas with the interactivity of DOM elements.
Is there a "best" library that abstracts Canvas and SVG?
Some libraries (Plotly.js, Apache ECharts) auto-select based on data size. For React, you typically choose Recharts (SVG) or a custom Canvas solution. Visx (from Airbnb) offers low-level primitives for both.
What about WebGL for super-large datasets?
WebGL is for millions of points in 3D or with complex effects. Libraries like Luma.gl and deckGL handle this. It's overkill for typical business dashboards but essential for scientific visualization and geospatial maps.
Can I use both Canvas and React hooks together?
Yes, use useRef to access the canvas DOM element and useEffect to manage drawing. The Canvas is a "black box" to React; React doesn't track its internal state. Manage zoom, pan, and selection as React state, then let Canvas render based on that state.