Home  >   Blog  >   Save HTML <svg> as an Image

September 4, 2021 | 3 min read | 614 words

Save HTML <svg> as an Image

After playing with interactive data visualization using D3.js and React, I wanted to have a "Download" function so that I can easily save the visualization as an image rather than taking a screenshot of a browser window:

In short, it can be achieved by writing a JavaScript snippet that does the following job:

  1. <svg> to Blob
  2. Blob to Image
  3. Image to <canvas>

Eventually, HTML Canvas enables you to save the content in the form of image file.

For the future reference, let me walk-through the process line-by-line since coming up with a complete snippet wasn't straightforward; there were some "tricky" parts that aren't fully covered by a piece of online resources e.g.:

Assume we have an SVG drawing index.html and its stylesheet e.g., style.css:

<svg xmlns="http://www.w3.org/2000/svg">
  <rect width=64 height=64 />
</svg>
svg rect {
  fill: blue;
}

SVG to Blob

The first and most confusing step is to convert SVG into a Blob object represented by XML:

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

// CSS must be explicitly embedded
const style = createStyleElementFromCSS();
svg.insertBefore(style, svg.firstChild);

const data = (new XMLSerializer()).serializeToString(svg);
const svgBlob = new Blob([data], {
    type: 'image/svg+xml;charset=utf-8'
});

// remove the temporarily injected CSS
style.remove();

To unlock the conversion, we have to keep the following points in our mind:

  1. Define <svg> with xmlns="http://www.w3.org/2000/svg"
  2. Embed style inside of the <svg> tag

Here, the inserted style element can be dynamically constructed as follows:

const createStyleElementFromCSS = () => {
  // assume index.html loads only one CSS file in <header></header>
  const sheet = document.styleSheets[0];

  const styleRules = [];
  for (let i = 0; i < sheet.cssRules.length; i++)
    styleRules.push(sheet.cssRules.item(i).cssText);

  const style = document.createElement('style');
  style.type = 'text/css';
  style.appendChild(document.createTextNode(styleRules.join(' ')))

  return style;
};

Although we defined the style in the separate .css file, it's important for <svg> to explicitly contain the information. Otherwise, a saved image loses all the information about color, font, shape, etc.

Consequently, the svgBlob object has the XML representation of an SVG element below:

<svg xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    svg rect { fill: blue; }
  </style>
  <rect width=64 height=64 />
</svg>

Blob to Image

Next, loading the Blob object to Image allows the application to treat the original SVG in a more handy way:

// convert the blob object to a dedicated URL
const url = URL.createObjectURL(svgBlob);

// load the SVG blob to a flesh image object
const img = new Image();
img.addEventListener('load', () => {
  // (Next step: Image to Canvas)
});
img.src = url;

For instance, url denotes a reference to the object as: blob:http://localhost:3000/a1c7704c-09a5-46f5-a102-7bc84d8ecbce

Image to Canvas

Once Image recognizes the Blob URL, we finally draw the image on an HTML Canvas and trigger a download operation.

img.addEventListener('load', () => {
  // draw the image on an ad-hoc canvas
  const bbox = svg.getBBox();

  const canvas = document.createElement('canvas');
  canvas.width = bbox.width;
  canvas.height = bbox.height;

  const context = canvas.getContext('2d');
  context.drawImage(img, 0, 0, bbox.width, bbox.height);

  URL.revokeObjectURL(url);

  // trigger a synthetic download operation with a temporary link
  const a = document.createElement('a');
  a.download = 'image.png';
  document.body.appendChild(a);
  a.href = canvas.toDataURL();
  a.click();
  a.remove();
});

Notice that the event handler cleanses the environment by removing url and a, an ad-hoc download link. Meanwhile, in case you want to obtain an image in a particular type, canvas.toDataURL() takes an optional parameter like canvas.toDataURL('image/jpeg').

That's it. Now we're capable of extracting an image file from an HTML-coded SVG illustration. My flight emission calculator app already has a Download button for the desired use case.

A set of code snippets below is a complete, compact example of things I explained in this article.

  Support

  Gift a cup of coffee

  Categories

Engineering

  See also

March 13, 2021
How Much CO2 Emissions Have Your Flights Made?
February 3, 2021
Practicing D3 Interactive Data Visualization with Fitbit Activity/Sleep Log
January 23, 2021
Datavis 2020: A Free Online Course About D3.js & React

  More

Last updated: September 4, 2021

  Author: Takuya Kitazawa

I am a product builder, mentor, and advocate for sustainable technology development with a decade of experience in AI/ML products, data systems, and digital transformation. Based in Canada and originally from Japan, I have lived and worked globally, including part-time residence in Malawi, Africa. Visit my portfolio to learn more about my work, or reach out to me at [email protected].

  Now

  Disclaimer

  • Opinions are my own and do not represent the views of organizations I am/was belonging to.
  • I use Grammarly and LanguageTool for correcting grammatical errors, but I do not rely on any other generative AI tools to create my blog content.
  • I am doing my best to ensure the accuracy and fair use of the information. However, there might be some errors, outdated information, or biased subjective statements due to the nature of a personal website. Visitors understand the limitations and rely on any information at their own risk.
  • If there are any issues with the content, please contact me so I can take the necessary action.