Converting SVG to an Image via Blob

There are a few important things to keep in mind.

  1. Any CSS will be lost when converting to a Blob.
    • You’ll have to convert all styles to SVG attributes.
  2. SVG resources must be self contained.
    • Fonts will need to be converted to Base64 (this post has a handy tool)
    • Images will need to be converted to Base64

Adding @font-face to an SVG in JSX (React) will look like this:

<defs>
    <style type='text/css'>
      { `@font-face {
          font-family: SharpSansNo2;
          font-weight: 900;
          font-style: normal;
          src: url(data:application/font-woff;charset=utf-8;base64,${SharpSansDisplayNo2});
      }` }
    </style>
  </defs>
</svg>

Here is my JS for converting my SVG to a Blob. Notice that I clone the element before making modifications, such as converting CSS styles to SVG attributes.

const $clonedSvgElement = $svgElement.cloneNode(true) as SVGElement;

// make any tweaks to colors (such as replacing css variables with calculated values)
const $background = $svgElement.querySelector('[data-background]') as SVGGraphicsElement;
const $clonedBackground = $clonedSvgElement.querySelector('[data-background]') as SVGGraphicsElement;
$clonedBackground.setAttribute('fill', getComputedStyle($background).fill);

const $textElements = $svgElement.querySelectorAll('text');
const $clonedTextElements = $clonedSvgElement.querySelectorAll('text');
for (let i = 0; i < $clonedTextElements.length; i++) {
const $textElement = $textElements[i];
const $clonedTextElement = $clonedTextElements[i];
const computedStyles = getComputedStyle($textElement);
const attributes = {
  color: computedStyles.color,
  fill: computedStyles.color,
  'font-family': 'SharpSansNo2',
  'font-size': computedStyles.fontSize,
  'font-weight': computedStyles.fontWeight,
  'letter-spacing': computedStyles.letterSpacing
};
for (const key in attributes) {
  const value = key ? attributes[key] : '';
  if (value) {
    $clonedTextElement.setAttribute(key, value);
  }
}
}

// export current state to HTML
const data = new XMLSerializer().serializeToString($clonedSvgElement);

// generate blob with base64 data of image
const blob = new Blob([data], { type: 'image/svg+xml;charset=utf-8' });
const URL = window.URL || window.webkitURL || window;
const blobURL = URL.createObjectURL(blob);

// generate image with canvas data (to convert to PNG and other formats)
const image = new Image();
document.body.appendChild(image);
const canvas = document.createElement('canvas');
canvas.width = this.currentPoster.width * scale;
canvas.height = this.currentPoster.height * scale;
const context = canvas.getContext('2d');
let png: string;

// wait for image to load
image.onload = () => {
if (context) {
  context.drawImage(image, 0, 0, this.currentPoster.width * scale, this.currentPoster.height * scale);
  png = canvas.toDataURL();

  // trigger download
  const download = function (href, name) {
    const link = document.createElement('a');
    link.download = name;
    link.style.opacity = '0';
    document.body.append(link);
    link.href = href;
    link.click();
    link.remove();
  };
  download(png, 'sddw-poster.png');
}
};
image.src = blobURL;

Leave a Reply

Your email address will not be published. Required fields are marked *