Javascript - 将带有SVG的HTML div转换为图像

时间:2015-11-08 15:55:17

标签: javascript svg highcharts html2canvas

我使用Highcharts绘制图表。

我想添加“导出到PNG”选项,其中包括Highcharts图和外部div。使用Highcharts导出时,我无法添加包含图表的外部div。

我在这里看到几个使用html2canvas的例子。当我尝试使用它时,Highcharts的SVG元素不包含在输出图像中。

有没有人知道一个解决方案,比尝试在外部HTML div图像中合并SVG图像更好?

更新 -

使用最新版本的html2canvas解决了这个问题,但输出略有不同:

原件: enter image description here

html2canvas输出:

enter image description here

如您所见,某些元素在不同位置呈现两次。

任何人都知道如何解决它?

1 个答案:

答案 0 :(得分:8)

我认为你正在处理一些svg到位图转换的边缘情况。

我不确定html2canvas使用哪种方法,但它肯定缺少这里的东西。

我编写了一个函数来处理在调用html2canvas之前可以使用的一些边缘情况,因为它能够毫无问题地绘制位图。



// without converting the svg to png
html2canvas(contentDiv, {
    onrendered: function(can) {
      dirty.appendChild(can);
    }
  });
  
// first convert your svg to png
exportInlineSVG(svg, function(data, canvas) {
  svg.parentNode.replaceChild(canvas, svg);
  // then call html2canvas
  html2canvas(contentDiv, {
    onrendered: function(can) {
      can.id = 'canvas';
      clean.appendChild(can);
    }
  });
})


function exportInlineSVG(svg, receiver, params, quality) {
  if (!svg || !svg.nodeName || svg.nodeName !== 'svg') {
    console.error('Wrong arguments : should be \n exportSVG(SVGElement, function([dataURL],[canvasElement]) || IMGElement || CanvasElement [, String_toDataURL_Params, Float_Params_quality])')
    return;
  }

  var xlinkNS = "http://www.w3.org/1999/xlink";
  var clone;
  // This will convert an external image to a dataURL
  var toDataURL = function(image) {

    var img = new Image();
    // CORS workaround, this won't work in IE<11
    // If you are sure you don't need it, remove the next line and the double onerror handler
    // First try with crossorigin set, it should fire an error if not needed
    img.crossOrigin = 'Anonymous';

    img.onload = function() {
      // we should now be able to draw it without tainting the canvas
      var canvas = document.createElement('canvas');
      var bbox = image.getBBox();
      canvas.width = bbox.width;
      canvas.height = bbox.height;
      // draw the loaded image
      canvas.getContext('2d').drawImage(this, 0, 0, bbox.width, bbox.height);
      // set our original <image>'s href attribute to the dataURL of our canvas
      image.setAttributeNS(xlinkNS, 'href', canvas.toDataURL());
      // that was the last one
      if (++encoded === total) exportDoc()
    }

    // No CORS set in the response		
    img.onerror = function() {
        // save the src
        var oldSrc = this.src;
        // there is an other problem
        this.onerror = function() {
            console.warn('failed to load an image at : ', this.src);
            if (--total === encoded && encoded > 0) exportDoc();
          }
          // remove the crossorigin attribute
        this.removeAttribute('crossorigin');
        // retry
        this.src = '';
        this.src = oldSrc;
      }
      // load our external image into our img
    img.src = image.getAttributeNS(xlinkNS, 'href');
  }

  // The final function that will export our svgNode to our receiver
  var exportDoc = function() {
      // check if our svgNode has width and height properties set to absolute values
      // otherwise, canvas won't be able to draw it
      var bbox = svg.getBBox();
      // avoid modifying the original one
      clone = svg.cloneNode(true);
      if (svg.width.baseVal.unitType !== 1) clone.setAttribute('width', bbox.width);
      if (svg.height.baseVal.unitType !== 1) clone.setAttribute('height', bbox.height);

      parseStyles();

      // serialize our node
      var svgData = (new XMLSerializer()).serializeToString(clone);
      // remember to encode special chars
      var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);

      var svgImg = new Image();

      svgImg.onload = function() {
        // if we set a canvas as receiver, then use it
        // otherwise create a new one
        var canvas = (receiver && receiver.nodeName === 'CANVAS') ? receiver : document.createElement('canvas');
        // IE11 doesn't set a width on svg images...
        canvas.width = this.width || bbox.width;
        canvas.height = this.height || bbox.height;
        canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);

        // try to catch IE
        try {
          // if we set an <img> as receiver
          if (receiver.nodeName === 'IMG') {
            // make the img looks like the svg
            receiver.setAttribute('style', getSVGStyles(receiver));
            receiver.src = canvas.toDataURL(params, quality);
          } else {
            // make the canvas looks like the canvas
            canvas.setAttribute('style', getSVGStyles(canvas));
            // a container element
            if (receiver.appendChild && receiver !== canvas)
              receiver.appendChild(canvas);
            // if we set a function
            else if (typeof receiver === 'function')
              receiver(canvas.toDataURL(params, quality), canvas);
          }
        } catch (ie) {
          console.warn("Your ~browser~ has tainted the canvas.\n The canvas is returned");
          if (receiver.nodeName === 'IMG') receiver.parentNode.replaceChild(canvas, receiver);
          else receiver(null, canvas);
        }
      }
      svgImg.onerror = function(e) {
        if (svg._cleanedNS) {
          console.error("Couldn't export svg, please check that the svgElement passed is a valid svg document.");
          return;
        }
        // Some non-standard NameSpaces can cause this issues
        // This will remove them all
        function cleanNS(el) {
          var attr = el.attributes;
          for (var i = 0; i < attr.length; i++) {
            if (attr[i].name.indexOf(':') > -1) el.removeAttribute(attr[i].name)
          }
        }
        cleanNS(svg);
        for (var i = 0; i < svg.children.length; i++)
          cleanNS(svg.children[i]);
        svg._cleanedNS = true;
        // retry the export
        exportDoc();
      }
      svgImg.src = svgURL;
    }
    // ToDo : find a way to get only usefull rules
  var parseStyles = function() {
    var styleS = [],i;
    // transform the live StyleSheetList to an array to avoid endless loop
    for (i = 0; i < document.styleSheets.length; i++)
      styleS.push(document.styleSheets[i]);
    // Do we have a `<defs>` element already ?
    var defs = clone.querySelector('defs') || document.createElementNS('http://www.w3.org/2000/svg', 'defs');
    if (!defs.parentNode)
      clone.insertBefore(defs, clone.firstElementChild);

    // iterate through all document's stylesheets
    for (i = 0; i < styleS.length; i++) {
      var style = document.createElement('style');
      var rules = styleS[i].cssRules,
        l = rules.length;
      for (var j = 0; j < l; j++)
        style.innerHTML += rules[j].cssText + '\n';

      defs.appendChild(style);
    }
    // small hack to avoid border and margins being applied inside the <img>
    var s = clone.style;
    s.border = s.padding = s.margin = 0;
    s.transform = 'initial';
  }
  var getSVGStyles = function(node) {
    var dest = node.cloneNode(true);
    svg.parentNode.insertBefore(dest, svg);
    var dest_comp = getComputedStyle(dest);
    var svg_comp = getComputedStyle(svg);
    var mods = "";
    for (var i = 0; i < svg_comp.length; i++) {
      if (svg_comp[svg_comp[i]] !== dest_comp[svg_comp[i]])
        mods += svg_comp[i] + ':' + svg_comp[svg_comp[i]] + ';';
    }
    svg.parentNode.removeChild(dest);
    return mods;
  }

  var images = svg.querySelectorAll('image'),
    total = images.length,
    encoded = 0;
  // Loop through all our <images> elements
  for (var i = 0; i < images.length; i++) {
    // check if the image is external
    if (images[i].getAttributeNS(xlinkNS, 'href').indexOf('data:image') < 0)
      toDataURL(images[i]);
    // else increment our counter
    else if (++encoded === total) exportDoc()
  }
  // if there were no <image> element
  if (total === 0) exportDoc();
}
&#13;
rect {
  fill: blue;
  transform: translate(35px) rotate(45deg);
}
div {
  width: 250px;
  display: inline-block
}
#svg {
  border: 1px solid green;
}
#canvas { border: 1px solid red;}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<div id="contentDiv">
  <p>Some html content</p>

  <svg xmlns="http://www.w3.org/2000/svg" id="svg" width="200">
    <defs>
      <filter id="Alien" color-interpolation-filters="sRGB">
        <feComponentTransfer>
          <fefuncR type="table" tablevalues="1 0 1" />
        </feComponentTransfer>
      </filter>
    </defs>
    <image filter="url(#Alien)" xlink:href="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png" width="100%" height="100%" />
    <rect x="0" y="0" width="50" height="50" />
  </svg>
</div>
<div id="clean">clean:<br></div>
<div id="dirty">dirty :<br></div>
&#13;
&#13;
&#13;

注意:
从这个问题开始,我开始编写一个完整的exportInlineSVG函数,你可以找到here