SVGs Are Amazing

A deep dive into how browsers turn SVG into pixels and why a tiny state machine inside an SVG beats heavy video for lively UIs

Why I Actually Care About This Stuff

I was building a voice agent UI and wanted something that felt alive without shipping bloated video files or gifs and lottie's paid export system pissed me off. SVG seemed perfect for the job. Tiny payload, css editable, infinitely customizable. But then I started wondering: "Wait, if it's still DOM, how is this actually different from throwing around a bunch of <div>s or just drawing on a <canvas>?"

Turns out the answer is way more interesting than I expected.

Why SVGs Are Actually Cool

Here's the thing most people miss: SVG isn't a bitmap. It's a DOM scene graph of geometry and paint instructions that gets resolved into a display list and rasterized by your GPU (via Skia on Chrome or WebRender on Firefox).

For short UI motion and icon level interaction, this can be an order of magnitude smaller than video or image sequences while staying razor sharp.

This is why stateful SVGs are perfect for doing interactive UI stuff with art.

How Your Browser Actually Renders SVG: Step by Step

Let me walk you through exactly what happens when your browser encounters an SVG. I'm going to visualize every step so you can see the magic happening.

Step 1: Parse the XML into DOM Nodes

Your browser takes xml and builds a DOM tree. Each element becomes a node with properties and attributes.

Loading...

Element Tree:

Invalid XML

Step 2: Apply CSS and Resolve Units

The browser applies CSS cascade rules and converts all units to concrete pixel values. Those percentages, ems, viewport units, all become absolute pixels.

/* Input */
circle { r: 2em; fill: var(--primary-color); }

/* Resolved */
circle { r: 32px; fill: rgb(59, 130, 246); }

Step 3: Build Coordinate Transformation Matrices

Using computed styles from Step 2, the engine builds CTMs. viewBox, width, height, preserveAspectRatio, and transform are resolved to concrete numbers first, read more here. Only elements that establish a viewport apply viewBox and preserveAspectRatio, then this mapping composes with any transform. More on transforms

Formula CTM(element) = CTM(parent) × T(x,y) × V(viewBox, preserveAspectRatio) × M(transform)

[[,,],
,,]]
SVG

Step 4: Convert Everything to Paths

Here's where it gets spicy. The browser converts all shapes into path data:

  • Rectangle → Four lines with optional rounded corners
  • Circle → Parametric curve equations
  • Complex arcs → Broken into cubic Bézier segments

Step 5: Tessellation - Breaking Paths into Triangles

GPUs draw triangles. Engines flatten vector paths to triangles or keep analytic paths until the last stage, then rasterize with GPU help. In practice, Skia and WebRender often end up with triangles for the GPU. The example below is from lyon, which turns an SVG-like path into vertex and index buffers.

Tessellation

Step 6: Stroke Expansion

Strokes are tricky because they're rendered as fills. The browser expands the centerline into an outline at ±width/2 along the unit normal.

Join types matter:

  • Miter: Sharp corners (with miter limit math)
  • Round: Curved joins
  • Bevel: Cut corners

From what I've read, the broswer handles these things automatically. For example at very sharp angles a miter corner grows into a long spike. That looks ugly at small sizes, can self overlap, and will flicker when you animate geometry. The miter limit is the guardrail. When the spike gets too long the join switches to a bevel automatically. This keeps corners stable and avoids surprise artifacts. We won't be diving deep into the maths of that in this article.

Step 7: Dash Pattern Generation

For dashed lines, the browser walks the path by arc length, alternating on/off segments:

Animated dashed lineA curved path with a repeating dash pattern that marches along the line.

Step 8: Paint Server Resolution

Different paint types get resolved:

  • Solid Colors: Simple color lookup
  • Linear Gradients: Map each pixel through the inverse gradient transform. Gradients interpolate in sRGB by default. Use color-interpolation="linearRGB" only when you need linear space blending. W3C id you wanna read more.
  • Radial Gradients: Calculate distance from center, then interpolate stops
  • Patterns: Render the pattern tile to an off screen surface, then repeat it with the pattern transform. If the tile contains vectors, the engine renders those vectors into the tile surface before repetition

Step 9: Clipping and Masking

clipPath defines a vector clipping region. Browsers render it as a hard clip with antialiasing at the edges for visual smoothness.

mask renders the masked subtree to an off screen surface and applies either luminance or alpha as coverage. Control this with mask-type: luminance or mask-type: alpha. The common luminance calculation is Y = 0.2126R + 0.7152G + 0.0722B.

Step 10: Filter Graph Processing

Each filter primitive runs in its own pass over offscreen surfaces. Gaussian blur uses separable convolution:

Step 11: Display List Generation

The browser builds optimized draw commands:

  • Fill triangles with paint
  • Apply transforms
  • Composite with blending

Step 12: GPU Rasterization

Finally, triangles get rasterized with anti-aliasing. The GPU samples coverage at sub-pixel precision:

Wrapping Up

SVGs aren't just "scalable images", they're a sophisticated graphics pipeline that your browser optimizes heavily. Know the pipeline, choose your animations wisely, and you'll build interfaces that feel magical but run efficiently.

Your users will thank you.

Wrapping it a bit quicker near the end because I spent an unhealthy amount of time understanding the whole process as I was writing down this post. Absolutely fascinating and inspiring to see a glimpse of the magic that happens behind the scenes.