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

2021-09-04

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.

  Share


  Support (Thank you!)

  Gift a cup of coffee

Note that, as an Amazon Associate, I earn from qualifying purchases on amazon.ca.

  See also

2021-03-13
How Much CO2 Emissions Have Your Flights Made?
2021-02-03
Practicing D3 Interactive Data Visualization with Fitbit Activity/Sleep Log
2021-01-23
Datavis 2020: A Free Online Course About D3.js & React

  More

Last updated: 2022-09-02

  Author: Takuya Kitazawa

Takuya Kitazawa is a freelance software developer, minimalistic traveler, ultralight hiker & runner, and craft beer enthusiast. While my area of specialty is in data & AI ethics and machine learning productization, I have worked full-stack throughout the career e.g., as a frontend/backend engineer, OSS developer, technical evangelist, solution architect, data scientist, and product manager. You can find what I am doing lately at my "now" page, and your inquiry is always welcome at [email protected], including comments on my blog posts.

  Schedule a call with me

  Disclaimer

  • Opinions are my own and do not represent the views of organizations I am/was belonging to.
  • I am doing my best to ensure the accuracy and fair use of the information. However, there might be some errors or biased subjective statements because the main purpose of this blog is to jot down my personal thoughts as soon as possible before conducting an extensive investigation. Visitors understand the limitations and rely on any information at their own risk.
  • That said, if there is any issue with the content, please contact me so I can take the necessary action.