Optimize output of HTML2Canvas

爱⌒轻易说出口 提交于 2019-12-12 04:36:49

问题


I created a chart with amchart. I will later have to export it as PDF.
As suggested here, I need to convert the chart first into SVG with html2canvas.

It works, but the chart looks "broken":

Is there any way to optimize the result?

Here is an extract of my code:

   /* --- 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: I know, there is a built in export function for every chart. The reason I can't use it, is that in the real case, there will be more content to export (several charts, tables, text, etc.), so that I need to export an entire DIV.


回答1:


I have tried a lot of solution, but the most i have found is to use a script for convert SVG (Thanks @Kaiido) and use html2canvas plugin.

I generated your chart and onclick event i assign ID to the SVG tag created.

After click on the button the SVG transform this to Canvas. Now you can just make what you want with this.

Please try bellow in full screen mode for show your label totally

$(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();
		}
	})
})
#chartdiv1 {
  width: 100%;
  height: 500px;
}
.amcharts-export-menu {
  display:none;
}
<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>



回答2:


On click of html2Canvas button add jQuery code to add height and width as attribute to your svg element (add as attribute and not in style) and it will export in exact format




回答3:


I have tried the same, and fix this by changing the SVG width and height. Here is the code used to fix the issue.

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, you can comment printOption before converting the image for the exported image changes.



来源:https://stackoverflow.com/questions/41933498/optimize-output-of-html2canvas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!