我有一个外部SVG文件,其中包含一些嵌入的图案图像标签。每当我使用将该SVG转换为PNG时toDataURL(),生成的PNG图像都不包含我已作为图案应用到某些SVG路径的图像。有什么办法解决这个问题?
toDataURL()
是的,有:将svg附加到文档中,并将所有包含的图像编码为dataURIs。
我正在编写一个脚本来执行此操作,还编写了一些其他内容,例如包括外部样式表以及toDataURL失败的其他修复方法(例如,通过xlink:hrefattribute或引用的外部元素<funciri>)。
xlink:href
<funciri>
这是我编写的用于解析图像内容的函数:
function parseImages(){ var xlinkNS = "http://www.w3.org/1999/xlink"; var total, encoded; // convert an external bitmap 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'); canvas.width = this.width; canvas.height = this.height; // draw the loaded image canvas.getContext('2d').drawImage(this, 0, 0); // set our <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'); }; // get an external svg doc to data String var parseFromUrl = function(url, element){ var xhr = new XMLHttpRequest(); xhr.onload = function(){ if(this.status === 200){ var response = this.responseText || this.response; var dataUrl = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(response); element.setAttributeNS(xlinkNS, 'href', dataUrl); if(++encoded === total) exportDoc(); } // request failed with xhr, try as an <img> else{ toDataURL(element); } }; xhr.onerror = function(){toDataURL(element);}; xhr.open('GET', url); xhr.send(); }; var images = svg.querySelectorAll('image'); total = images.length; encoded = 0; // loop through all our <images> elements for (var i = 0; i < images.length; i++) { var href = images[i].getAttributeNS(xlinkNS, 'href'); // check if the image is external if (href.indexOf('data:image') < 0){ // if it points to another svg element if(href.indexOf('.svg') > 0){ parseFromUrl(href, images[i]); } else // a pixel image toDataURL(images[i]); } // else increment our counter else if (++encoded === total) exportDoc(); } // if there were no <image> element if (total === 0) exportDoc(); }
此处svgDoc称为svg, 该exportDoc()函数可以编写为:
svg
exportDoc()
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.getBoundingClientRect(); if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width); if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height); // serialize our node var svgData = (new XMLSerializer()).serializeToString(svg); // remember to encode special chars var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData); var svgImg = new Image(); svgImg.onload = function () { var canvas = 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(svgImg, 0, 0, canvas.width, canvas.height); doSomethingWith(canvas) }; svgImg.src = svgURL; };
但是再一次,您必须首先将svg附加到文档中(通过xhr或到<iframe>或<object>元素中),并且必须确保所有资源都 符合CORS要求(或来自同一域)才能获得这些资源呈现。
<iframe>
<object>
var svg = document.querySelector('svg'); var doSomethingWith = function(canvas) { document.body.appendChild(canvas) }; function parseImages() { var xlinkNS = "http://www.w3.org/1999/xlink"; var total, encoded; // convert an external bitmap 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'); canvas.width = this.width; canvas.height = this.height; // draw the loaded image canvas.getContext('2d').drawImage(this, 0, 0); // set our <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 var href = image.getAttributeNS(xlinkNS, 'href'); // really weird bug that appeared since this answer was first posted // we need to force a no-cached request for the crossOrigin be applied img.src = href + (href.indexOf('?') > -1 ? + '&1': '?1'); }; // get an external svg doc to data String var parseFromUrl = function(url, element) { var xhr = new XMLHttpRequest(); xhr.onload = function() { if (this.status === 200) { var response = this.responseText || this.response; var dataUrl = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(response); element.setAttributeNS(xlinkNS, 'href', dataUrl); if (++encoded === total) exportDoc(); } // request failed with xhr, try as an <img> else { toDataURL(element); } }; xhr.onerror = function() { toDataURL(element); }; xhr.open('GET', url); xhr.send(); }; var images = svg.querySelectorAll('image'); total = images.length; encoded = 0; // loop through all our <images> elements for (var i = 0; i < images.length; i++) { var href = images[i].getAttributeNS(xlinkNS, 'href'); // check if the image is external if (href.indexOf('data:image') < 0) { // if it points to another svg element if (href.indexOf('.svg') > 0) { parseFromUrl(href, images[i]); } else // a pixel image toDataURL(images[i]); } // else increment our counter else if (++encoded === total) exportDoc(); } // if there were no <image> element if (total === 0) exportDoc(); } 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.getBoundingClientRect(); if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width); if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height); // serialize our node var svgData = (new XMLSerializer()).serializeToString(svg); // remember to encode special chars var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData); var svgImg = new Image(); svgImg.onload = function() { var canvas = 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(svgImg, 0, 0, canvas.width, canvas.height); doSomethingWith(canvas) }; svgImg.src = svgURL; }; window.onload = parseImages; canvas { border: 1px solid green !important; } <svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1"> <defs> <pattern id="Pattern" x="0" y="0" width=".25" height=".25"> <image xlink:href="https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png" width="100" height="100"/> </pattern> </defs> <rect fill="url(#Pattern)" x="0" y="0" width="200" height="200"/> </svg>