d3.js中导出SVG为PNG后嵌入外链图片不显示的问题
问题描述
使用d3.js创建包含Unsplash外链图片的SVG,添加下载按钮将SVG导出为PNG后,导出的PNG里嵌入的图片无法显示。
代码示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Random Image SVG with Download Button</title> <script src="https://d3js.org/d3.v7.min.js"></script> </head> <body> <svg id="svg-container" width="400" height="200"></svg> <br> <button onclick="downloadSvg()">Download as Image</button> <script> async function getRandomImageUrl() { const response = await fetch('https://source.unsplash.com/random'); const imageUrl = response.url; return imageUrl; } async function createRandomImageSVG() { const imageUrl = await getRandomImageUrl(); const svg = d3.select('#svg-container'); // Append the image directly to the SVG svg.append('image') .attr('x', 0) .attr('y', 0) .attr('width', 400) .attr('height', 200) .attr('href', imageUrl); // Use href instead of xlink:href for newer browsers } createRandomImageSVG(); function downloadSvg() { const svgElement = document.getElementById('svg-container'); const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(svgElement); // Create a temporary canvas const canvas = document.createElement('canvas'); canvas.width = 400; canvas.height = 200; const context = canvas.getContext('2d'); // Create a new image const img = new Image(); img.onload = function() { // Draw SVG onto the canvas context.drawImage(img, 0, 0); // Convert the canvas to data URL const dataUrl = canvas.toDataURL('image/png'); // Create a temporary link element const a = document.createElement('a'); a.href = dataUrl; a.download = 'random_image.png'; a.click(); }; // Encode the SVG string to Base64 and set as source of image element img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); } </script> </body> </html>
预期行为
点击“Download as Image”按钮时,SVG转换为PNG图片,下载的图片包含从Unsplash获取的随机图片。
实际行为
导出的PNG文件中未显示Unsplash的嵌入图片,仅为空白。
问题根源
SVG引用了跨域外链图片,浏览器出于安全限制,在将SVG转换为Data URL时阻止了跨域请求。
解决方案
方法1:将图片转为Base64嵌入SVG
把Unsplash图片下载后转为Base64格式嵌入SVG,彻底消除跨域问题。修改代码如下:
- 改造获取图片的函数,直接返回Base64编码:
async function getRandomImageBase64() { const response = await fetch('https://source.unsplash.com/random'); const blob = await response.blob(); return new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); }
- 修改SVG创建逻辑,使用Base64图片:
async function createRandomImageSVG() { const imageBase64 = await getRandomImageBase64(); const svg = d3.select('#svg-container'); svg.append('image') .attr('x', 0) .attr('y', 0) .attr('width', 400) .attr('height', 200) .attr('href', imageBase64); }
方法2:Canvas分层绘制图片与SVG内容
先将Unsplash图片绘制到Canvas,再叠加SVG内容,最后导出Canvas为PNG:
修改下载函数:
async function downloadSvg() { const canvas = document.createElement('canvas'); canvas.width = 400; canvas.height = 200; const ctx = canvas.getContext('2d'); // 绘制Unsplash图片到Canvas const img = new Image(); img.crossOrigin = 'anonymous'; const imageUrl = await getRandomImageUrl(); img.src = imageUrl; await new Promise((resolve) => img.onload = resolve); ctx.drawImage(img, 0, 0, 400, 200); // 绘制SVG内容到Canvas const svgElement = document.getElementById('svg-container'); const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(svgElement); const svgImg = new Image(); svgImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); await new Promise((resolve) => svgImg.onload = resolve); ctx.drawImage(svgImg, 0, 0); // 导出PNG const dataUrl = canvas.toDataURL('image/png'); const a = document.createElement('a'); a.href = dataUrl; a.download = 'random_image.png'; a.click(); }
内容的提问来源于stack exchange,提问作者Jitesh Boliya
相关产品推荐
相关产品推荐

