Modules 01–03 taught you to draw charts on Canvas. Modules 05–08 taught you to describe charts with Chart.js (which renders to Canvas internally). The next three modules will use D3.js, which renders to SVG. But why does D3 use SVG? Why does Chart.js use Canvas? When should you use CSS, WebGL, or a static image instead? This module answers those questions by examining the five rendering surfaces available in the browser and the architectural dimensions that determine which one to choose.
Run: Open the HTML files directly in a browser. No libraries or build steps required.
Every chart in a web browser is ultimately drawn using one of five rendering technologies. Each has a different rendering model that determines its performance characteristics, interactivity story, and accessibility profile:
| Surface | Model | Output | Interactivity | Accessibility |
|---|---|---|---|---|
| Static image (PNG/JPEG/SVG-as-file) | Pre-rendered | Raster/vector file | None | alt text |
| CSS | Retained (DOM) | Styled HTML elements | Full DOM events | Full semantic HTML |
| Canvas 2D | Immediate (pixels) | Opaque bitmap | Manual hit-testing | Opaque (needs ARIA) |
| SVG (inline DOM) | Retained (DOM) | Structured DOM tree | Native DOM events per element | Traversable by assistive tech |
| WebGL / WebGPU | Immediate (GPU) | GPU-rendered pixels | Manual (raycasting) | Opaque (needs ARIA) |
The critical distinction is between immediate mode and retained mode rendering. This single concept explains most of the performance and interactivity trade-offs you will encounter.
These two rendering models are the architectural foundation of every rendering technology in the browser:
Why this matters: At 100 elements, the overhead of retained mode is negligible. At 5,000 elements, reflow and style recalculation become measurable. At 50,000 elements, the page freezes — not because rendering is slow, but because DOM bookkeeping is expensive. Every element participates in the CSS cascade, layout, and event dispatch system.
Open the stress test demo and drag the slider from 100 to 50,000 points. Watch the FPS counters: Canvas stays smooth while SVG degrades.
Each rendering surface has a comfortable operating range measured in data points or visual elements. Outside that range, performance degrades — sometimes gradually, sometimes catastrophically:
These ranges are approximate and depend on hardware, but the relative order is consistent. The key insight is that there is no single "best" technology — each has a sweet spot determined by its rendering model.
D3.js defaults to SVG for good reasons. Within its performance envelope, SVG offers advantages that no other rendering surface can match:
devicePixelRatio dance that Canvas requires:hover, :focus, and media queries<rect>, <circle>, or <path> receives click, mouseenter, and focus events individually<title>, <desc>, and ARIA attributes to make charts navigable<text> elements are selectable and searchable, unlike Canvas text which is painted pixelsTry the hit-testing demo to see SVG's native event handling in action compared to Canvas's manual approach.
SVG's strengths come from the DOM. But the DOM is also its weakness. Every SVG element is a full-fledged DOM node that participates in:
Render 10,000 <circle> elements and you have 10,000 DOM nodes. That is roughly 5 MB of memory just for the DOM objects, plus the ongoing cost of style recalculation on every frame. The rendering itself is fast — painting circles is cheap. The bottleneck is the DOM bookkeeping that happens before painting.
Canvas uses immediate-mode rendering: you issue draw commands and the result is a flat bitmap. Once drawn, shapes have no identity — they are just pixels.
Strengths:
getImageData() / putImageData()canvas.toDataURL() gives you a PNG immediately — no screenshot neededWeaknesses:
devicePixelRatio and scale the context to avoid blurry output on Retina displaysWhen data volumes exceed 100,000 points, even Canvas struggles with per-frame draw loops. WebGL (and its successor WebGPU) solve this by offloading rendering to the GPU, which can process millions of vertices in parallel.
WebGL is used by mapping libraries (Mapbox GL, deck.gl), scientific visualization tools (regl, three.js), and large-scale charting libraries (Plotly with scattergl). WebGPU, the modern successor, provides even lower-level GPU access with better performance and a cleaner API.
CSS can render simple charts without any JavaScript at all. Bars are <div> elements whose width or height is set via calc() or custom properties. This approach has unique advantages:
<dl>, <table>, or ARIA attributes for complete accessibility<!-- CSS-only bar chart using custom properties -->
<div class="chart">
<div class="bar" style="--value: 83">
<span class="label">/home</span>
<span class="value">1,250</span>
</div>
<div class="bar" style="--value: 29">
<span class="label">/about</span>
<span class="value">430</span>
</div>
</div>
<style>
.bar {
height: 28px;
width: calc(var(--value) * 1%);
background: #16a085;
margin: 4px 0;
transition: width 0.5s;
}
</style>
The limitation is that CSS charts are restricted to simple forms — horizontal bars, sparklines, basic progress indicators. Anything requiring axes, gridlines, or complex geometry needs Canvas or SVG. See the CSS bar chart demo for a complete example with progressive JavaScript enhancement.
Choosing a rendering technology is not a single-variable decision. It depends on at least six dimensions. This table maps each dimension to the technologies it favors:
| Dimension | Static Image | CSS | Canvas | SVG | WebGL |
|---|---|---|---|---|---|
| Purpose: Explanatory (reports, email) | Yes | Inline CSS | |||
| Purpose: Exploratory (interactive analysis) | Yes | Yes | Yes | ||
| Interaction: None | Yes | ||||
| Interaction: Tooltips / hover | Yes | Possible | Yes | Possible | |
| Interaction: Complex (filter, zoom, drag) | Yes | Yes | Yes | ||
| Data volume: < 50 points | Yes | Yes | |||
| Data volume: 50–5,000 points | Yes | Yes | |||
| Data volume: 5K–100K points | Yes | ||||
| Data volume: > 100K points | Yes | ||||
| Update rate: Static | Yes | Yes | Yes | Yes | Yes |
| Update rate: Periodic refresh | Yes | Yes | Yes | ||
| Update rate: Real-time streaming | Yes | Yes | |||
| Must work without JS | Yes | Yes | |||
| Must embed in email/PDF | Yes | Inline only |
Here is the full comparison across the dimensions that matter for charting:
| Dimension | CSS | Canvas 2D | SVG | WebGL |
|---|---|---|---|---|
| Rendering model | Retained (DOM) | Immediate (pixels) | Retained (DOM) | Immediate (GPU) |
| Element limit | ~100 | ~100K | ~5K–10K | Millions |
| Interactivity | Native DOM events | Manual hit-testing | Native DOM events | Manual raycasting |
| Accessibility | Full (semantic HTML) | Opaque (needs ARIA) | Good (title/desc/ARIA) | Opaque (needs ARIA) |
| CSS styling | Full | None | Full | None |
| Resolution independence | Yes | Requires dpr handling | Yes (vector) | Requires dpr handling |
| DevTools inspection | Full | Bitmap only | Full (Elements panel) | Bitmap only |
| Text rendering | Real, selectable text | Painted pixels | Real, selectable text | Painted pixels or SDF |
| Export to PNG | Screenshot only | toDataURL() |
Serialize to file | toDataURL() |
| Works without JS | Yes | No | Static SVG yes | No |
--value accordingly.