如何用JavaScript获取Canvas全部内容(含隐藏部分)?基于D3.js V3
解决D3.js V3导出完整SVG内容(含隐藏区域)的问题
嘿,我看你在导出D3 SVG的时候碰到了只能捕获可见区域的问题,还不想改动原Canvas(应该是指你的SVG容器)的尺寸对吧?这问题我之前也遇到过,核心原因是当前导出逻辑没处理好SVG视口和完整内容边界的关系,下面给你一步步解决:
问题出在哪?
你代码里用了getBBox()来获取尺寸,但直接修改原SVG的style.width/height会打乱页面布局;而且如果原SVG因为父容器滚动、裁剪导致内容隐藏,光靠getBBox()还不够——得让导出用的SVG能包含所有内容的视口,同时不影响原页面的显示。
无需改动原容器尺寸的解决方案
思路很简单:创建一个原SVG的临时副本,给这个副本设置能容纳所有内容的viewBox和尺寸,然后导出这个副本,原SVG完全不动。
修改后的完整代码
var svgString; window.onload = function(){ setTimeout(function() { const exportSVG = document.getElementById("canvas"); exportSVG.style.fontFamily= "lato"; // 获取SVG所有内容的完整边界(包括隐藏部分) const bbox = exportSVG.getBBox(); console.log(`${bbox.width} / ${bbox.height}`); // 克隆原SVG,创建临时副本(不影响原页面显示) const tempSVG = exportSVG.cloneNode(true); // 设置viewBox,让临时SVG能看到所有内容 tempSVG.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`); // 给临时SVG设置完整内容的实际尺寸 tempSVG.setAttribute('width', bbox.width); tempSVG.setAttribute('height', bbox.height); // 用临时SVG生成导出用的字符串 svgString = getSVGString(tempSVG); svgString2Image(svgString, bbox.width, bbox.height, 'png', save); console.log("svg export code loaded"); }, 5000); }; function save(dataBlob, filesize) { saveAs(dataBlob, 'D3 vis exported to PNG.png'); } function getSVGString(svgNode) { svgNode.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); var cssStyleText = getCSSStyles(svgNode); appendCSS(cssStyleText, svgNode); var serializer = new XMLSerializer(); var svgString = serializer.serializeToString(svgNode); svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); return svgString; function getCSSStyles(parentElement) { var selectorTextArr = []; selectorTextArr.push('#' + parentElement.id); for (var c = 0; c < parentElement.classList.length; c++) if (!contains('.' + parentElement.classList[c], selectorTextArr)) selectorTextArr.push('.' + parentElement.classList[c]); var nodes = parentElement.getElementsByTagName("*"); for (var i = 0; i < nodes.length; i++) { var id = nodes[i].id; if (!contains('#' + id, selectorTextArr)) selectorTextArr.push('#' + id); var classes = nodes[i].classList; for (var c = 0; c < classes.length; c++) if (!contains('.' + classes[c], selectorTextArr)) selectorTextArr.push('.' + classes[c]); } var extractedCSSText = ""; for (var i = 0; i < document.styleSheets.length; i++) { var s = document.styleSheets[i]; try { if (!s.cssRules) continue; } catch (e) { if (e.name !== 'SecurityError') throw e; continue; } var cssRules = s.cssRules; for (var r = 0; r < cssRules.length; r++) { if (contains(cssRules[r].selectorText, selectorTextArr)) extractedCSSText += cssRules[r].cssText; } } return extractedCSSText; function contains(str, arr) { return arr.indexOf(str) !== -1; } } function appendCSS(cssText, element) { var styleElement = document.createElement("style"); styleElement.setAttribute("type", "text/css"); styleElement.innerHTML = cssText; var refNode = element.hasChildNodes() ? element.children[0] : null; element.insertBefore(styleElement, refNode); } } function svgString2Image(svgString, width, height, format, callback) { var format = format ? format : 'png'; var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); canvas.width = width; canvas.height = height; var image = new Image(); image.onload = function() { context.clearRect(0, 0, width, height); context.drawImage(image, 0, 0, width, height); canvas.toBlob(function(blob) { var filesize = Math.round(blob.size / 1024) + ' KB'; if (callback) callback(blob, filesize); }); }; image.src = imgsrc; }
关键修改说明
- 临时SVG副本:用
cloneNode(true)复制原SVG,所有导出相关的修改都在这个副本上操作,完全不影响页面上的原SVG显示。 - 设置viewBox:这是核心!
viewBox定义了SVG能看到的范围,我们用getBBox()拿到的所有元素的边界来设置,这样临时SVG就能包含所有隐藏的内容了。 - 移除原尺寸修改:删掉了原来修改
style.width/height的代码,保证原页面布局丝毫不改。
额外提醒
- 如果你的SVG里有外部图片,要确保图片是同域的,或者服务器设置了CORS,不然导出时图片会显示不出来。
- 检查原SVG的
overflow属性,要是设了hidden也没关系,getBBox()还是能拿到完整边界,临时SVG默认overflow: visible,能显示所有内容。
内容的提问来源于stack exchange,提问作者im1tta
相关产品推荐
相关产品推荐

