How to Perfectly Fit an SVG’s ViewBox to its Contents Using JavaScript
Tags: javascript, html, webdev, tutorial
Published: 2021-01-19 11:53:57

---
title: How to Perfectly Fit an SVG’s ViewBox to its Contents Using JavaScript
published: true
description: Using the SVG tag on websites is handy, but its interface can be different than we're used to. Here's a handy way to fit the viewport of an SVG to its contents every time.
tags: javascript, html, webdev, tutorial
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/33q9riki6jd9t10lc8tt.png
canonical_url: https://typeofnan.dev/how-to-perfectly-fit-an-svg-to-its-contents-using-javascript/
---

Using the SVG tag on websites is handy, but its interface can be different than we're used to. In this post, we're going to see how to fit the viewport of an SVG to its contents every time.

<hr />

### Please give this post a 💓, 🦄, and 🔖 if you want more SVG/visualization posts!

<hr />

# The Problem

In some cases, we might have an SVG with some arbitrary shapes or paths in it. Those shapes and paths may have specified dimentsions that don't always fit your viewport.

Let's consider the following SVG. We have two paths that I have taken from a map of the United States. One path is for the state of Maryland and the other is for the state of New York.

```html
<svg style="height: 300px;">
  <path
    d="m 822.9,269.3 0,-1.7 h -.8 l 0,1.8 z m 11.8,-3.9 1.2,-2.2 .1,-2.5 -.6,-.6 -.7,.9 -.2,2.1 -.8,1.4 -.3,1.1 -4.6,1.6 -.7,.8 -1.3,.2 -.4,.9 -1.3,.6 -.3,-2.5 .4,-.7 -.8,-.5 .2,-1.5 -1.6,1 v -2 l 1.2,-.3 -1.9,-.4 -.7,-.8 .4,-1.3 -.8,-.6 -.7,1.6 .5,.8 -.7,.6 -1.1,.5 -2,-1 -.2,-1.2 -1,-1.1 -1.4,-1.7 1.5,-.8 -1,-.6 v -.9 l .6,-1 1.7,-.3 -1.4,-.6 -.1,-.7 -1.3,-.1 -.4,1.1 -.6,.3 .1,-3.4 1,-1 .8,.7 .1,-1.6 -1,-.9 -.9,1.1 -1,1.4 -.6,-1 .2,-2.4 .9,-1 .9,.9 1.2,-.7 -.4,-1.7 -1,1 -.9,-2.1 -.2,-1.7 1.1,-2.4 1.1,-1.4 1.4,-.2 -.5,-.8 .5,-.6 -.3,-.7 .2,-2.1 -1.5,.4 -.8,1.1 1,1.3 -2.6,3.6 -.9,-.4 -.7,.9 -.6,2.2 -1.8,.5 1.3,.6 1.3,1.3 -.2,.7 .9,1.2 -1.1,1 .5,.3 -.5,1.3 v 2.1 l -.5,1.3 .9,1.1 .7,3.4 1.3,1.4 1.6,1.4 .4,2.8 1.6,2 .4,1.4 v 1 h -.7 l -1.5,-1.2 -.4,.2 -1.2,-.2 -1.7,-1.4 -1.4,-.3 -1,.5 -1.2,-.3 -.4,.2 -1.7,-.8 -1,-1 -1,-1.3 -.6,-.2 -.8,.7 -1.6,1.3 -1.1,-.8 -.4,-2.3 .8,-2.1 -.3,-.5 .3,-.4 -.7,-1 1,-.1 1,-.9 .4,-1.8 1.7,-2.6 -2.6,-1.8 -1,1.7 -.6,-.6 h -1 l -.6,-.1 -.4,-.4 .1,-.5 -1.7,-.6 -.8,.3 -1.2,-.1 -.7,-.7 -.5,-.2 -.2,-.7 .6,-.8 v -.9 l -1.2,-.2 -1,-.9 -.9,.1 -1.6,-.3 -.9,-.4 .2,-1.6 -1,-.5 -.2,-.7 h -.7 l -.8,-1.2 .2,-1 -2.6,.4 -2.2,-1.6 -1.4,.3 -.9,1.4 h -1.3 l -1.7,2.9 -3.3,.4 -1.9,-1 -2.6,3.8 -2.2,-.3 -3.1,3.9 -.9,1.6 -1.8,1.6 -1.7,-11.4 60.5,-11.8 7.6,27.1 10.9,-2.3 0,5.3 -.1,3.1 -1,1.8 z m -13.4,-1.8 -1.3,.9 .8,1.8 1.7,.8 -.4,-1.6 z"
  ></path>
  <path
    d="m 872.9,181.6 -1.3,.1 -.5,1 z m -30.6,22.7 .7,.6 1.3,-.3 1.1,.3 .9,-1.3 h 1.9 l 2.4,-.9 5.1,-2.1 -.5,-.5 -1.9,.8 -2,.9 .2,-.8 2.6,-1.1 .8,-1 1.2,.1 4.1,-2.3 v .7 l -4.2,3 4.5,-2.8 1.7,-2.2 1.5,-.1 4.5,-3.1 3.2,-3.1 3,-2.3 1,-1.2 -1.7,-.1 -1,1.2 -.2,.7 -.9,.7 -.8,-1.1 -1.7,1 -.1,.9 -.9,-.2 .5,-.9 -1.2,-.7 -.6,.9 .9,.3 .2,.5 -.3,.5 -1.4,2.6 h -1.9 l .9,-1.8 .9,-.6 .3,-1.7 1.4,-1.6 .9,-.8 1.5,-.7 -1.2,-.2 -.7,.9 h -.7 l -1.1,.8 -.2,1 -2.2,2.1 -.4,.9 -1.4,.9 -7.7,1.9 .2,.9 -.9,.7 -2,.3 -1,-.6 -.2,1.1 -1.1,-.4 .1,1 -1.2,-.1 -1.2,.5 -.2,1.1 h -1 l .2,1 h -.7 l .2,1 -1.8,.4 -1.5,2.3 z m -.8,-.4 -1.6,.4 v 1 l -.7,1.6 .6,.7 2.4,-2.3 -.1,-.9 z m -10.1,-95.2 -.6,1.9 1.4,.9 -.4,1.5 .5,3.2 2.2,2.3 -.4,2.2 .6,2 -.4,1 -.3,3.8 3.1,6.7 -.8,1.8 .9,2.2 .9,-1.6 1.9,1.5 3,14.2 -.5,2 1.1,1 -.5,15 .7,1 2.8,16.3 1.8,1.5 -3.5,3.4 1.7,2.2 -1.3,3.3 -1.5,1.7 -1.5,2.3 -.2,-.7 .4,-5.9 -14.6,-4.9 -1.6,-1.1 -1.9,.3 -3,-2.2 -3,-5.8 h -2 l -.4,-1.5 -1.7,-1.1 -70.5,13.9 -.8,-6 4.3,-3.9 .6,-1.7 3.9,-2.5 .6,-2.4 2.3,-2 .8,-1.1 -1.7,-3.3 -1.7,-.5 -1.8,-3 -.2,-3.2 7.6,-3.9 8.2,-1.6 h 4.4 l 3.2,1.6 .9,-.1 1.8,-1.6 3.4,-.7 h 3 l 2.6,-1.3 2.5,-2.6 2.4,-3.1 1.9,-.4 1.1,-.5 .4,-3.2 -1.4,-2.7 -1.2,-.7 2,-1.3 -.1,-1.8 h -1.5 l -2.3,-1.4 -.1,-3.1 6.2,-6.1 .7,-2.4 3.7,-6.3 5.9,-6.4 2.1,-1.7 2.5,.1 20.6,-5.2 z"
  ></path>
</svg>
```

What we might expect to see is this:

![new york and maryland](https://dev-to-uploads.s3.amazonaws.com/i/dnbhruvyxosx4sxazi6o.png)

But if we were to fire up our web browser and navigate to this page, we wouldn't see a thing. Why is that? Well, it's because these paths were taken from a full map of the U.S., so the _origin_ (0, 0) point of the coordinate system being used is at the top-left of the _entire country_&mdash;next to Alaska.

This is no good as want our SVG to fit our two states perfectly.

# Our Options

We have a couple reasonable options:

1. _Transform_ each path such that it fits our current SVG view
2. _Change our SVG "viewBox"_ to fit our paths

I personally like the second option and will go through that one in this post!

# First, an Abstaction and a Tiny Bit of Math

Let's back off the states example for a moment and just use a couple arbitrary paths in an SVG:

![a diamond and a circle on a canvas](https://dev-to-uploads.s3.amazonaws.com/i/6di0m8qgostwyguvzrdt.png)

We can see that our SVG is 100x100, but our shapes actually only start at about `x = 40`, `y = 30` and end somewhew around `x = 80`, `y = 90`. What we'd love is for our SVG to zoom in on that area, so our view looks something like this:

![zoomed in on diamond and circle](https://dev-to-uploads.s3.amazonaws.com/i/c4vm2q385dhdqjq7amfc.png)

How can we do this? It turns out the `svg` HTML element has a handy `zoomBox` attribute that lets us pass the desired origin's `x` and `y` values along with the desired `width` and `height`.

In the case of our shapes, we have the following:

![box drawn around shapes](https://dev-to-uploads.s3.amazonaws.com/i/76xeej12mah9tkigzuj4.png)

We can see that our origin is at `x=40, y=30` and then we have to do a little math for our width and height:

```
width = xMax - xMin = 80 - 40 = 40
height = yMax - yMin = 90 - 30 = 60
```

Therefore, we might specify the following `viewBox` for our SVG.

```html
<svg viewBox="40 30 40 60">
  <!-- Shapes here -->
</svg>
```

And this works! Note that, by default, the SVG will center the objects in its `viewBox` rather than distorting them.

## That Seems Tedious

Yes, it would be _super_ tedious to do this math any time we want to use SVGs, and seems nearly impossible if we dynamic SVG children or many children.

Luckily, we're programmers! We enjoy automating things, so let's automate the process we just did in our heads.

# Automatically Finding the Boundary

The way I like to automatically find the boundary is to iterate through all children of the `svg`. I use a native svg method called `getBBox()` that will return the `x`, `y`, `width`, and `height` of an element. Then, just some simple comparison: if the `x` is lower than all the other `x` values we have seen so far, it's the new `xMin`. Same with `y`. To get `xMax` and `yMax`, we do a similar operation, except we have to make sure we're looking at `x + width` and `y + height` for each element since `x` and `y` only give us the top-left point of the element and we want the bottom-right.

Here's how this code might look:

```javascript
const svg = document.querySelector('svg');

const { xMin, xMax, yMin, yMax } = [...svg.children].reduce((acc, el) => {
  const { x, y, width, height } = el.getBBox();
  if (!acc.xMin || x < acc.xMin) acc.xMin = x;
  if (!acc.xMax || x + width > acc.xMax) acc.xMax = x + width;
  if (!acc.yMin || y < acc.yMin) acc.yMin = y;
  if (!acc.yMax || y + height > acc.yMax) acc.yMax = y + height;
  return acc;
}, {});
```

We use `document.querySelector('svg')` to grab the only SVG on our page (but make sure to use an ID or class if you need a different selector!). Next, I use `[...svg.children]` to (a) get all of the child elements of our `svg` and (b) use the spread operator (`...`) to convert the `HTMLCollection` to an array of elements. Have an array enables me to use the `reduce` method, which takes a _callback function_ and an initial accumulator (an empty object `{}`).

Within the `reduce` callback, I use the `getBBox()` method on each element to get the `x`, `y`, `width`, and `height`. Using the aformentioned logic, we conditionally overwrite the `xMin`, `xMax`, `yMin`, and `yMax`, properties on our accumulator.

Our one final step is to set he `viewBox` attribute of the parent `svg`. _Remember_, the `viewBox` is set to `x y width height`, so we have to convert our `xMax` and `yMax` to `width` and `height`, respectively!

We can do this with the math we discussed in our simplified example:

```
width = xMax - xMin
height = yMax - yMin
```

Let's put it all together here:

```javascript
const svg = document.querySelector('svg');

const { xMin, xMax, yMin, yMax } = [...svg.children].reduce((acc, el) => {
  const { x, y, width, height } = el.getBBox();
  if (!acc.xMin || x < acc.xMin) acc.xMin = x;
  if (!acc.xMax || x + width > acc.xMax) acc.xMax = x + width;
  if (!acc.yMin || y < acc.yMin) acc.yMin = y;
  if (!acc.yMax || y + height > acc.yMax) acc.yMax = y + height;
  return acc;
}, {});

const viewbox = `${xMin} ${yMin} ${xMax - xMin} ${yMax - yMin}`;

svg.setAttribute('viewBox', viewbox);
```

And we can see it in action with our state SVGs. In fact, we touted the flexibility of this solution, so it better be able to accommodate an additional state! Let's add North Carolina for good measure.

{% codesandbox friendly-noyce-2nxsg %}

# Conclusion

Thanks for playing with shapes, states, and even a little math with me today. Hopefully you learned something new today and how have a handy utility to fit items in your SVGs.

<hr />

### Please give this post a 💓, 🦄, and 🔖 if you learned a little something about SVGs!