SVG vs canvas: the difference and which to use
SVG keeps every shape as an element that CSS and scripts can reach. Canvas is a grid of pixels drawn by JavaScript. How they differ, with a speed test.
Published
SVG and canvas are the two ways to draw in a web page, and they work on opposite principles. SVG describes a picture as shapes, and the browser keeps each shape as an element it can restyle, move and listen to. Canvas is a rectangle of pixels that JavaScript paints on, and once a shape is painted the browser remembers only the pixels. Use SVG when the parts of the picture matter. Use canvas when there are too many parts to keep, or when the work is on pixels.
The same picture, both ways
SVG is markup:
<svg viewBox="0 0 200 100" width="400">
<rect x="20" y="20" width="60" height="60" fill="gold"/>
<circle cx="140" cy="50" r="30" fill="teal"/>
</svg>
Canvas is a list of instructions:
<canvas id="c" width="400" height="200"></canvas>
<script>
const ctx = document.querySelector('#c').getContext('2d');
ctx.scale(2, 2);
ctx.fillStyle = 'gold';
ctx.fillRect(20, 20, 60, 60);
ctx.fillStyle = 'teal';
ctx.beginPath();
ctx.arc(140, 50, 30, 0, Math.PI * 2);
ctx.fill();
</script>
Both show a gold square and a teal circle. After the SVG is drawn, the page holds a <rect> and a <circle>. After the canvas is drawn, the page holds one <canvas> and 80,000 pixels. To change the circle’s colour in the SVG, set one attribute. To change it on the canvas, paint over it, or clear the canvas and draw everything again.
The differences
| SVG | Canvas | |
|---|---|---|
| The picture is | Shapes, kept as elements | Pixels |
| Made with | Markup, or script | Script only |
| Works with scripts turned off | Yes | No |
| Styled with CSS | Yes | No. Colours and fonts are set in script |
| Events | On each shape: click, hover, focus | On the whole canvas. The script works out what was hit |
| Zoom and print | Sharp at any size | Fixed number of pixels, blurred when enlarged |
| Text | Real text: selectable, searchable, read by screen readers | Pixels in the shape of letters |
| Accessibility | Shapes can carry names and roles | One bitmap. Needs separate fallback content |
| Cost grows with | The number of elements | The number of pixels, and how much is drawn on each frame |
| Saved as | An .svg file, still made of shapes | PNG or JPEG, with toBlob() |
| Also a file format | Yes: works in <img>, CSS and editors | No. A canvas exists only in a running page |
Speed: how many things are moving
The usual advice is that SVG is fine for hundreds of shapes and canvas is needed for many thousands. That is about right, and it is easy to measure.
A test for this guide moved every circle on every frame, drawn as SVG <circle> elements and then as arcs on one 800 by 600 canvas, in Chrome 154 on an Apple M4 Pro. Chrome was run without a window, where it drew at most 60 frames a second, and each figure is the middle of four runs:
| Circles moving | SVG | Canvas |
|---|---|---|
| 1,000 | 60 frames a second | 60 |
| 5,000 | 59 | 60 |
| 20,000 | 21 | 60 |
| 50,000 | 8 | 59 |
A slower machine reaches the limit sooner, and the numbers will differ with larger shapes, strokes or filters. The pattern holds: each SVG element costs something to keep and to update, while a canvas does not care how many shapes made up its pixels.
Three things to weigh against that table:
- The test changes everything on every frame, which is the worst case for SVG. A drawing that sits still costs nothing after its first paint, and a chart where one bar changes costs one bar.
- Canvas has the opposite cost. Its work grows with its size in pixels, and a large canvas on a high-density screen is a lot of pixels. Browsers also cap the size of a canvas, and the limit depends on the browser and device.
- With canvas, everything SVG does for nothing has to be written: redrawing on change, working out which shape is under the pointer, keyboard access, sharpness on high-density screens.
Sharpness
An SVG is drawn afresh at whatever size it is shown, so it is sharp on any screen, at any zoom, and in print.
A canvas has the number of pixels given by its width and height attributes, 300 by 150 if they are left out. Stretching it with CSS scales the bitmap and blurs it. On a high-density screen a canvas looks soft unless it is given more pixels to match:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';
After this, drawing code keeps using CSS pixels and the canvas holds one bitmap pixel for each pixel of the screen.
Which to use
| Job | Use | Why |
|---|---|---|
| Icons, logos, illustrations | SVG | Sharp at every size, styled with CSS, cached as files |
| Charts with up to a few thousand marks | SVG | Tooltips, hover states and accessible labels come almost free |
| Diagrams and maps with parts to click | SVG | Each part is an element with its own events |
| Scatter plots and maps with tens of thousands of points | Canvas | Too many elements for the DOM |
| Games, particles, anything redrawn in full each frame | Canvas | Drawing a shape costs far less than updating an element |
| Editing photographs, filters, dithering | Canvas | The work is on pixels, through getImageData() |
| 3D | Canvas, with WebGL | SVG is two-dimensional |
| A file for cutting, plotting, engraving or print | SVG | Machines and editors need the paths, not a picture of them |
The two can be combined: a canvas for the dense layer of a chart, with an SVG placed over it for the axes, the labels and the highlighted item.
Draw an SVG on a canvas
A canvas can paint an SVG the way it paints any image, with drawImage(). For an SVG file:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
img.src = 'drawing.svg';
For an inline SVG already in the page, turn it into a file in memory first:
const svg = document.querySelector('svg');
const text = new XMLSerializer().serializeToString(svg);
const url = URL.createObjectURL(new Blob([text], { type: 'image/svg+xml' }));
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(url);
};
img.src = url;
The SVG is drawn fresh at the size asked for, so the result is sharp however large the canvas is. canvas.toBlob() then gives a PNG. This is how a browser converts SVG to PNG, and it is what the SVG to PNG page does for you.
Two limits. The SVG is treated as an image, so its scripts do not run and it cannot load outside files such as web fonts or linked pictures. And an SVG fetched from another site without CORS approval taints the canvas, after which toBlob() and getImageData() throw an error.
From canvas back to SVG
There is no way back built in. A canvas holds no record of the shapes that were drawn on it, only their pixels, so toBlob() can give a PNG and never an SVG.
- If you control the drawing code, draw the same shapes again as SVG elements. SVG and JavaScript shows how.
- If you only have the pixels, they have to be traced into shapes. Image to SVG does that, and raster vs vector explains what tracing can and cannot recover.
Common problems
| What you see | Cause | Fix |
|---|---|---|
| A canvas is blurred | It is stretched by CSS, or the screen is high-density | Set width and height in pixels and scale by devicePixelRatio |
| A canvas is blank after a resize | Setting width or height clears it | Redraw after resizing |
| An SVG chart slows with a lot of data | Tens of thousands of elements | Draw the dense layer on a canvas |
| Hover does nothing on a canvas shape | A canvas has no shapes, only pixels | Test the pointer position in script, for example with isPointInPath() |
| Canvas text cannot be selected or read aloud | It is pixels | Put the text in HTML or SVG over the canvas |
toBlob() throws a security error | An image from another origin was drawn | Serve the image with CORS headers and set crossOrigin |
| An SVG drawn on a canvas has the wrong font | An SVG used as an image cannot load web fonts | Convert the text to outlines |
Questions
Is canvas faster than SVG? For tens of thousands of moving shapes, yes, as the table shows. For a few hundred shapes there is no difference to see, and SVG is less code.
Is SVG better for accessibility? Yes. Canvas content is a bitmap that is not exposed to accessibility tools, so it needs fallback content written separately. SVG shapes can carry names and roles: see SVG title and alt text.
Which does D3 use? Either. D3’s shape generators return SVG path data by default, and draw on a canvas instead when they are given its context, so the choice can follow the size of the data.
Can an SVG contain a canvas? Yes, inside a <foreignObject> in an inline SVG. It is rarely needed: placing the two side by side or one over the other is simpler. See SVG foreignObject.