优化HTML2Canvas的输出

时间:2017-01-30 10:21:55

标签: javascript jquery pdf svg html2canvas

我使用amchart创建了一个图表。我稍后将把它作为PDF导出 作为suggested here,我需要先将图表转换为带有html2canvas的SVG。

它有效,但图表看起来已经破坏":

chart

有没有办法优化结果?

以下是我的代码摘录:

   /* --- Chart --- */

var chart = AmCharts.makeChart("chartdiv1", {
  "type": "serial",
  // ...
});

/* --- HTML2Canvas --- */

$('#cmd').click(function() {
  html2canvas($("#output"), {
    onrendered: function(canvas) {
      document.body.appendChild(canvas);
    }
  });
})

Here is a fiddle

PS:我知道,每个图表都有内置的导出功能。我无法使用它的原因是,在实际情况下,将会有更多内容要导出(几个图表,表格,文本等),因此我需要导出整个DIV。

4 个答案:

答案 0 :(得分:0)

我也尝试过这样做,但如果您尝试在任何第三方图表中使用html2canvas(或任何其他库)进行打印,那么我相信大部分时间它都不起作用。有些时候由于安全原因或一些时间由于其他原因,

如果你真的想要它,那么我会建议你把它打印到某个地方,然后用html2canvas来对它进行操作,

答案 1 :(得分:0)

我尝试了很多解决方案,但我发现最多的是使用脚本转换 SVG (谢谢 @Kaiido )并使用html2canvas插件。

我生成了您的图表和onclick事件,我将 ID 分配给 SVG 标记。

点击按钮后, SVG 将其转换为画布。现在你可以用这个来制作你想要的东西。

请在全屏模式下尝试完全显示您的标签



$(document).ready(function(){
	var chart = AmCharts.makeChart("chartdiv1", {
	  "type": "serial",
	  "theme": "light",
	  "dataProvider": [{
		"country": "USA",
		"visits": 2025
	  }, {
		"country": "China",
		"visits": 1882
	  }, {
		"country": "Japan",
		"visits": 1809
	  }, {
		"country": "Germany",
		"visits": 1322
	  }, {
		"country": "UK",
		"visits": 1122
	  }, {
		"country": "France",
		"visits": 1114
	  }, {
		"country": "India",
		"visits": 984
	  }, {
		"country": "Spain",
		"visits": 711
	  }, {
		"country": "Netherlands",
		"visits": 665
	  }, {
		"country": "Russia",
		"visits": 580
	  }, {
		"country": "South Korea",
		"visits": 443
	  }, {
		"country": "Canada",
		"visits": 441
	  }, {
		"country": "Brazil",
		"visits": 395
	  }],
	  "valueAxes": [{
		"gridColor": "#FFFFFF",
		"gridAlpha": 0.2,
		"dashLength": 0
	  }],
	  "gridAboveGraphs": true,
	  "startDuration": 1,
	  "graphs": [{
		"balloonText": "[[category]]: <b>[[value]]</b>",
		"fillAlphas": 0.8,
		"lineAlpha": 0.2,
		"type": "column",
		"valueField": "visits"
	  }],
	  "chartCursor": {
		"categoryBalloonEnabled": false,
		"cursorAlpha": 0,
		"zoomable": false
	  },
	  "categoryField": "country",
	  "categoryAxis": {
		"gridPosition": "start",
		"gridAlpha": 0,
		"tickPosition": "start",
		"tickLength": 20
	  },
	  "export": {
		"enabled": true
	  }

	});

	$('#cmd').click(function() {
		$("svg").attr("id","svg") //Assign ID to SCG tag
		
		// without converting the svg to png
		html2canvas(chartdiv1, {         // chartdiv1 is your div
			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(chartdiv1, {       // chartdiv1 is your div
			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;
#chartdiv1 {
  width: 100%;
  height: 500px;
}
.amcharts-export-menu {
  display:none;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="https://www.amcharts.com/lib/3/serial.js"></script>
<script src="https://www.amcharts.com/lib/3/themes/light.js"></script>
<script src="https://www.amcharts.com/lib/3/plugins/export/export.min.js"></script>
<script src="https://github.com/niklasvh/html2canvas/releases/download/0.5.0-alpha1/html2canvas.js"></script>

<button id="cmd">HTML2Canvas</button>
<div id="output">
  <div id="chartdiv1">
  </div>
</div>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

在html2Canvas按钮上单击时,添加jQuery代码以将高度和宽度作为属性添加到svg元素(作为属性添加而不是样式添加),并且将以确切的格式导出

答案 3 :(得分:0)

我尝试了同样的方法,并通过更改SVG宽度和高度来解决此问题。这是用于解决此问题的代码。

function printOptions(type = 'before') {
    var svg = chart.div.querySelector('svg');
    if (svg) {
       if (type == 'after') { // remove the attributes after generating canvas
          svg.removeAttribute('width');
          svg.removeAttribute('height');
       } else { // set width and height according to parent container
          svg.setAttribute('width', chart.div.clientWidth);
          svg.setAttribute('height', chart.div.clientHeight);
       }
       chart.validateNow(); // validating am chart again.
     }
}    
// code for download image
downloadImage() {
   printOptions();
   html2canvas(document.getElementById('chart-div')).then(canvas => {
      printOptions('after');
      let a = document.createElement('a');
      a.href = canvas.toDataURL("image/png")
      a.download = 'Report.png';
      a.click();
    });
 }

Working Demo ,您可以在将图像转换为导出的图像更改之前注释printOption。