[removed] save <svg> element to file on disk

2019-02-16 00:27发布

On my HTML i have an SVG element. It is rendered with d3js and has stylings applied in CSS.

When i right click in my browser i can select "Save image". This actions saves the image as rendered with all the css stylings applied.

I have been searching for a good way to save the file

  • Going to canvas and exporting canvas
  • Filesaver with saving as SVG
  • Variations on those

However when i get the file to disk the extra stylings from my css is not applied to the saved image.

Question: How can i save my SVG as rendered in the browser with css applied.

2条回答
神经病院院长
2楼-- · 2019-02-16 01:08

The CSS parsing is not an easy task, CSS rules are complicated...

I tried to write something for my SVG2Bitmap little script, but it is still far from being perfect...

Basically, it parses all the stylesheets in the document, and check if any of the svg's nodes match the rule (thanks to querySelector and Element.matches() methods).

The problem is that once appended into the svg doc, the rules may not match anymore (e.g body>svg>rect will fail). I still didn't find an elegant way to deal with it, also if anyone has one, please let me know.

An other issue I faced is that invalid rules will make previously mentioned methods to throw an error. This shouldn't be too much of a concern, but some browsers (Chrome to not tell its name) accept some hacky rules like [xlink\\:href] but save it in the cssRules as [xlink\:href] which will fail and thus throw an error...


The "save as file" part however is way easier thanks to the XMLSerializer object, which will let the browser create a standalone version of what it parsed, with all that is needed.

To make a 100% valid svg file, you'll also need to set a Doctype at top of your document.

So let's jump in the code :

var exportSVG = function(svg) {
  // first create a clone of our svg node so we don't mess the original one
  var clone = svg.cloneNode(true);
  // parse the styles
  parseStyles(clone);

  // create a doctype
  var svgDocType = document.implementation.createDocumentType('svg', "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
  // a fresh svg document
  var svgDoc = document.implementation.createDocument('http://www.w3.org/2000/svg', 'svg', svgDocType);
  // replace the documentElement with our clone 
  svgDoc.replaceChild(clone, svgDoc.documentElement);
  // get the data
  var svgData = (new XMLSerializer()).serializeToString(svgDoc);

  // now you've got your svg data, the following will depend on how you want to download it
  // e.g yo could make a Blob of it for FileSaver.js
  /*
  var blob = new Blob([svgData.replace(/></g, '>\n\r<')]);
  saveAs(blob, 'myAwesomeSVG.svg');
  */
  // here I'll just make a simple a with download attribute

  var a = document.createElement('a');
  a.href = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData.replace(/></g, '>\n\r<'));
  a.download = 'myAwesomeSVG.svg';
  a.innerHTML = 'download the svg file';
  document.body.appendChild(a);

};

var parseStyles = function(svg) {
  var styleSheets = [];
  var i;
  // get the stylesheets of the document (ownerDocument in case svg is in <iframe> or <object>)
  var docStyles = svg.ownerDocument.styleSheets;

  // transform the live StyleSheetList to an array to avoid endless loop
  for (i = 0; i < docStyles.length; i++) {
    styleSheets.push(docStyles[i]);
  }

  if (!styleSheets.length) {
    return;
  }

  var defs = svg.querySelector('defs') || document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  if (!defs.parentNode) {
    svg.insertBefore(defs, svg.firstElementChild);
  }
  svg.matches = svg.matches || svg.webkitMatchesSelector || svg.mozMatchesSelector || svg.msMatchesSelector || svg.oMatchesSelector;


  // iterate through all document's stylesheets
  for (i = 0; i < styleSheets.length; i++) {
    var currentStyle = styleSheets[i]

    var rules;
    try {
      rules = currentStyle.cssRules;
    } catch (e) {
      continue;
    }
    // create a new style element
    var style = document.createElement('style');
    // some stylesheets can't be accessed and will throw a security error
    var l = rules && rules.length;
    // iterate through each cssRules of this stylesheet
    for (var j = 0; j < l; j++) {
      // get the selector of this cssRules
      var selector = rules[j].selectorText;
      // probably an external stylesheet we can't access
      if (!selector) {
        continue;
      }

      // is it our svg node or one of its children ?
      if ((svg.matches && svg.matches(selector)) || svg.querySelector(selector)) {

        var cssText = rules[j].cssText;
        // append it to our <style> node
        style.innerHTML += cssText + '\n';
      }
    }
    // if we got some rules
    if (style.innerHTML) {
      // append the style node to the clone's defs
      defs.appendChild(style);
    }
  }

};

exportSVG(document.getElementById('mySVG'));
svg >rect {
  fill: yellow
}
/* this will fail, it could work with a check for document.querySelector instead of svg.querySelector, but that would just be a kill for performances with a lot of cssRules..., and would need to set the elements' style attribute instead of using a <style> tag */

body > svg >rect {
  stroke: red
}
<svg width="120" height="120" viewBox="0 0 120 120" id="mySVG">
  <rect x="10" y="10" width="100" height="100" />
</svg>

Ps : the download part of this snippet won't work in FF, you can try it in this fiddle though.

查看更多
神经病院院长
3楼-- · 2019-02-16 01:19

I think this should work for you:

https://stackoverflow.com/a/8694938/2308019

I understand that you are using an external stylesheet for your SVG markup. So I think you need a three-step solution:

  1. Make the stylesheet declarations that apply to the SVG markup inline. This is done best client-side. I do not have a solution in source code right now, but it should be possible to use W3C DOM Level 2 Style interface implementations to find out the selectors that apply to an element, and the declarations that have been used in the corresponding blocks (document.defaultView.getComputedStyle() alone will probably result in an SVG fragment having too many inline declarations).
  2. Convert SVG markup with inline stylesheets to PNG. This is best done server-side (e. g., with ImageMagick), so you would need to send the SVG markup to the server.
  3. Serve the PNG resource to the user.

Those two steps could be performed on form submission where in the onsubmit attribute you do step #1 and then call the form's submit() method.

查看更多
登录 后发表回答