Scaling SVGs without scaling their strokes

A forewarning: Unfortunately, I am going to share a roadblock I ran into, rather than a solution. I am sorry if this post is a bit of a downer.

Recently, I was working on a website for a client that included vector images of maps. The maps look something like this. These are vector maps in a line art style, where the maps appear hand drawn.

Here’s an example design with the map, so you can see what I’m working with:

An screenshot of a website mockup. The website mockup has map outlines on both the left and right side, with text in the centre. The text says: "We're on a mission to share Jesus with every home. We provide strategies and easy-to-use resources in printed and digital formats that Christians can use to present a gospel message to others, where they live and where they go. Learn how we do it."

What I wanted to do was place an inline SVG on the page, then scale its size along with the page itself. So I wrote code like this:

svg.map {
    display:block;
    margin:0;
    width:100%;
    height:auto;
}

You know, your bog-standard CSS.

This works as expected: the SVG scales along with the width of the page, and the aspect ratio of the SVG never changes. (The aspect ratio in this example is not a known quantity, and the client could change this map at any time to a different location.)

Here’s the problem: as the SVG scales, the strokes in the SVG scale as well. On a phone, the map looks correct: 1px strokes surrounding each country. On a desktop, it looks ridiculous: the strokes are 20px wide. It’s as if somebody drew the map with a highlighter.

To fix this, I turned to vector-effect — in particular, non-scaling-stroke. As always, the MDN Web Docs page is very helpful.

Here’s the problem with non-scaling-stroke: It only seems to work if you scale the SVG with transform="scale(X,Y)". It has no effect if you are merely resizing the SVG with CSS.

So this code does not work:

<style>
svg.map {
    display:block;
    margin:0;
    width:100%;
    height:auto;
}
</style>

<svg viewBox="0 0 500 240">
  <path
    vector-effect="non-scaling-stroke"
    d="M10,20 L40,100 L39,200 z"
    stroke="black"
    stroke-width="2px"
    fill="none"></path>
</svg>

Instead, the path must look like the following:

<path
    vector-effect="non-scaling-stroke"
  transform="translate(300, 0) scale(4, 1)"
  d="M10,20 L40,100 L39,200 z"
  stroke="black"
  stroke-width="2px"
  fill="none">
</path>

This was a huge disappointment for me, and for a few peers I ran this problem by. We all expected my solution to work because nowhere in the documentation does it say that scale is required.