Topic 12.1
Measuring SVG vs Canvas
In one line
SVG keeps one page element per mark, and the browser pays for every one. Canvas keeps one element and a grid of pixels. You measure the difference on your own device and learn where each one breaks before you choose.
Think of it like this
A wall of sticky notes against a poster. Every note is separate: you can peel it off, move it, write on it. That is wonderful for 25 notes and a nightmare for 25,000, because someone must keep track of every one. A poster is one sheet: it cannot lose a note, but you cannot peel anything off it. SVG is the wall of notes. Canvas is the poster.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- DOM node
- One item in the page's tree, such as
<circle>or<canvas>. The browser tracks every node. - mark
- One drawn item of a chart: a bar, a dot, a line segment.
- frames per second
- How many pictures the page shows each second. Below about 30, motion looks jerky.
- layout and paint
- The browser's steps that place every element and turn it into pixels. Both run after your script.
Step by step
01Same 10,000 marks, two ways
The first example draws 10,000 made-up marks (from d3.randomLcg(42), so they never change) as SVG circles and as canvas dots. The pictures match. The page holds 10,000 elements against one canvas, and only 2,483 marks are inside the picture. Timings appear on the page, not in the Console, because they change from machine to machine.
02Count what you cannot see
The marks spread wider than the picture on purpose. The 7,517 outside it are still nodes the browser stores and processes.
03Race them
The widget draws N marks with a live frames-per-second meter. Slide N up and note where each approach drops frames on your device. That number is your own limit.
04Turn the numbers into a rule
The second example, choose, takes the number of marks, whether they are clickable and how often they change. Its table covers zero marks, one mark, NaN and 200,000 marks. Replace its limits with the numbers you measured.
Try it yourself
- 1
Fewer marks
In the first example, change
const N = 10000;to1000. The Console now says 1,000 circle elements and 261 marks inside the picture. The canvas is still one element. - 2
Many more marks
Change
const N = 10000;to100000. The Console says 100,000 circle elements and 25,298 marks inside the picture. The page line now reads (on the machine this was checked on, one reading) about 540 ms for the SVG against about 40 ms for the canvas: ten times the marks, five to seven times the time, and the canvas still one element. Your numbers will differ: that is the point of measuring. - 3
Keep every mark in view
Replace
random() * 520 - 130withrandom() * 260andrandom() * 260 - 65withrandom() * 130. All 10,000 marks now land inside the picture, so the Console saysmarks inside the picture: 10,000. The element count does not change: SVG paid for the 7,517 hidden marks before, and pays the same 10,000 now. - 4
Test the chooser's edges
In the chooser example, add the cases
{ marks: Infinity, clickable: false },{ marks: -5, clickable: true }and{ marks: "500", clickable: true }. All three printinvalid: marks must be a number, 0 or more. A text "500" is rejected on purpose, becauseNumber.isFinitedoes not turn text into a number. Then add{ marks: 800, clickable: false, updatesPerSecond: 60 }: it still says SVG, which is Practice 1.
Code & diagrams
The timings are drawn on the page, not logged, because they change from machine to machine. About half of the marks are outside the 260 by 130 picture on purpose.
Preview
Console
Nothing logged yet.Expected console output
marks in the data: 10,000
SVG circle elements in the page: 10,000
canvas elements in the page: 1
marks inside the picture: 2,483 (the rest are still elements)
elements you can click one by one: SVG 10,000 - canvas 0Try it: interactive
Race SVG against canvas
Slide the number of marks up and watch each meter. Note the count where SVG's frames per second fall below 30 on your device.
The limits are rules of thumb from one machine. The table includes the edges: no marks, one mark, NaN, a huge number, and clickable marks.
Console
(nothing was printed)Expected console output
0 marks, clickable true, 0 updates a second: nothing to draw: show an empty-state sentence
1 marks, clickable true, 0 updates a second: SVG: every mark is an element with its own events and focus
25 marks, clickable true, 0 updates a second: SVG: every mark is an element with its own events and focus
365 marks, clickable false, 0 updates a second: SVG: small enough, easy to inspect and style
3000 marks, clickable false, 0 updates a second: SVG works if the chart never changes: measure first
10000 marks, clickable true, 0 updates a second: Canvas: one element, you redraw it and hit-test it yourself
10000 marks, clickable true, 60 updates a second: Canvas: one element, you redraw it and hit-test it yourself
100000 marks, clickable false, 60 updates a second: WebGL: 6,000,000 mark-draws a second is too many for 2D canvas
200000 marks, clickable false, 0 updates a second: Canvas for a still picture, WebGL if it moves or zooms
NaN marks, clickable true, 0 updates a second: invalid: marks must be a number, 0 or moreBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget moveTo before each arc
In the first example, delete the line ctx.moveTo(m.x + 2, m.y); from the canvas loop.
Myth vs fact
Myth
Canvas is always faster than SVG.
Fact
For a few hundred marks SVG is just as fast, and gives you events, styling and accessibility for free. Canvas wins when the number of elements becomes the cost.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Script time is not frame time. After your loop the browser runs style, layout and paint, and for 10,000 SVG nodes those can cost more than the script. In the browser's Performance panel, a long "Recalculate Style" or "Layout" bar after your script is the cost of the DOM.
Remember this
- 1
The problem this solves: teams pick SVG or canvas by habit, and then a real dataset freezes the page. Measuring first tells you where your own limit is, on the phones and laptops your users really have.
- 2
What an SVG mark costs. Each
<circle>is a node in the page's tree (the DOM). The browser stores it, styles it, lays it out, paints it and keeps it ready for events and screen readers, and repeats that when anything changes. The cost grows with the node count, even for unseen nodes: the first example holds 10,000 circles while only 2,483 are inside the picture. - 3
What a canvas mark costs. A canvas is one element with a grid of pixels. Drawing a dot sets pixels, and then the dot is gone: the browser keeps no record of it. A hundred thousand dots cost a hundred thousand drawing calls but no hundred thousand nodes. The price: you keep the data, redraw when it changes and work out which mark is under the pointer (Topic 12.3).
- 4
Measured, on one machine. Building 10,000 circles took about 75 to 100 milliseconds of script time over several runs, drawing 10,000 canvas dots about 5 to 6. Those are single readings, not laws, and they leave out the layout and paint that follow and cost SVG more. Phones are usually slower: use the race widget on the device that matters.
- 5
Where each one breaks. SVG breaks on the number of elements and on how often they change: every update makes the browser look at all of them again. Canvas breaks on the number of drawing calls per frame and, for very large canvases, on memory (a canvas of many millions of pixels is a big bitmap, and browsers set their own maximum size). Past that, WebGL hands the drawing to the graphics card, which can draw millions of simple marks, at the price of writing shaders or using a library. The later 3D Web course is coming and covers it; this course stays with 2D canvas.
Explain it without notes
Why does an SVG with 10,000 circles cost more than a canvas with 10,000 dots that looks the same?
Which things does SVG give you for free that canvas does not?
Practice
Change the chooser so 800 marks that change 60 times a second are advised canvas, while 800 static marks stay SVG. (Run it with both cases to prove it.)
Write insideView(marks, width, height) that returns how many marks are inside the picture, and 0 for an empty array.
Trade-offs
- ↔
SVG gives inspectability and free accessibility, and costs memory and layout time per mark. Canvas gives speed at scale, and costs you hit-testing, accessibility and redrawing.
Done when you can
I can say what the browser stores for an SVG circle and for a canvas dot.
I know marks outside the view still cost in SVG.
I raised the race widget's count until my device dropped below 30 frames per second.
I can name what I give up when I choose canvas, and when WebGL comes in.