【问题标题】:javascript: save <svg> element to file on diskjavascript:将 <svg> 元素保存到磁盘上的文件
【发布时间】:2016-11-23 11:54:25
【问题描述】:

在我的 HTML 中,我有一个 SVG 元素。 它使用 d3js 渲染,并在 CSS 中应用了样式。

当我在浏览器中单击鼠标右键时,我可以选择“保存图像”。此操作将图像保存为应用了所有 css 样式的渲染。

我一直在寻找保存文件的好方法

  • 转到画布并导出画布
  • 保存为 SVG 的文件保护程序
  • 这些变化

但是,当我将文件保存到磁盘时,我的 css 中的额外样式不会应用于保存的图像。

问题:如何在应用了 css 的情况下将我的 SVG 保存为在浏览器中呈现的内容。

【问题讨论】:

    标签: javascript html css svg save


    【解决方案1】:

    CSS解析不是一件容易的事,CSS规则很复杂……

    我尝试write something 为我的 SVG2Bitmap 小脚本,但它仍然远非完美......

    基本上,它会解析文档中的所有样式表,并检查是否有任何 svg 节点与规则匹配(感谢 querySelectorElement.matches() 方法)。

    问题是一旦附加到 svg 文档中,规则可能不再匹配(例如 body&gt;svg&gt;rect 将失败)。我仍然没有找到一种优雅的方式来处理它,如果有人有,请告诉我。

    我面临的另一个问题是无效规则会使前面提到的方法引发错误。这不应该是一个太大的问题,但一些浏览器(Chrome 不告诉它的名字) 接受一些像[xlink\\:href] 这样的hacky 规则,但将其保存在cssRules 中作为[xlink\:href]将失败并因此引发错误...


    由于XMLSerializer 对象,“另存为文件”部分变得更加容易,它可以让浏览器创建它所解析内容的独立版本,以及所需的一切。

    要制作 100% 有效的 svg 文件,您还需要在顶部设置 Doctype 文件。

    让我们跳入代码:

    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 : 这个 sn-p 的下载部分在 FF 中无法使用,不过你可以在this fiddle 中尝试。

    【讨论】:

    • 适用于 Chrome 和 Firefox,但不适用于 Internet Explorer:HTTPS 安全性受损。我看看能不能解决这个问题
    • @JMan,这可能是因为页面上的某些 css 来自与页面本身不同的协议(https 与 http),但是,我认为脚本无论如何都应该运行,我会如果我有时间也试试看。您能告诉我您使用的是哪个版本的 IE 吗?
    • 我使用的是 IE11,当我在自己的 svg 上运行它时,它甚至没有给出错误,只是什么都不做
    • @JMan,我刚刚更新了 fiddle,它现在使用 FileSaver.js,因为 IE 不支持锚点的 download 属性,但主要部分仍然相同。在我的虚拟机上工作,让我知道。
    【解决方案2】:

    我认为这应该适合你:

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

    我了解到您正在为 SVG 标记使用外部样式表。所以我认为你需要一个三步解决方案:

    1. 将样式表声明应用于内联的 SVG 标记。这是最好的客户端。我现在在源代码中没有解决方案,但是应该可以使用 W3C DOM Level 2 Style 接口实现来找出适用于元素的选择器,以及在相应块中使用的声明(文档.defaultView.getComputedStyle() 单独可能会导致 SVG 片段具有过多的内联声明)。
    2. 将带有内联样式表的 SVG 标记转换为 PNG。这最好在服务器端完成(例如,使用 ImageMagick),因此您需要将 SVG 标记发送到服务器。
    3. 向用户提供 PNG 资源。

    这两个步骤可以在表单提交时执行,在 onsubmit 属性中您执行第 1 步,然后调用表单的 submit() 方法。

    【讨论】:

      猜你喜欢
      • 2012-01-12
      • 2012-01-12
      • 2018-09-14
      • 2016-07-22
      • 2013-12-19
      • 2010-09-23
      • 2010-11-08
      • 2021-11-03
      • 2019-10-30
      相关资源
      最近更新 更多