英文:
Download svg created by d3js as svg file with another svg in it
问题
我为我的SVG图表创建了一个下载函数,它成功下载了SVG文件,但SVG标记中的另一个SVG图标消失了。
我尝试复制SVG图标中的路径,更改路径中的数字以获得我期望的大小,然后将其粘贴到SVG标记中进行下载。
但这会改变曲线设置(例如:A4.61,4.61,0,0,1,.59,37.34)并且会使形状失真。
有没有办法下载带有 <image>
的SVG?
英文:
I created a download function for my svg graph, it successfully download the svg file, but there is another svg icon in the svg tag disappear.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
document.querySelector(".download").addEventListener("click", () => {
const svgElement = document.querySelector("svg");
const svgString = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "my_svg_file.svg";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
});
<!-- language: lang-html -->
<div>
<svg width="300" height="200">
<rect stroke="black" x="20" y="50" width="200" height="100" fill="navy"></rect>
<rect stroke="black" x="200" y="0" width="50" height="200" fill="brown"></rect>
<clipPath id="icon-4-29">
<rect class="icon" width="26.095396561286744" height="26.09539656128674" x="0" y="0"></rect>
</clipPath>
<image xlink:href="https://expo.taiwan-healthcare.org//data/tmp/20230413/202304131zc6g3.svg" visibility="visible" clip-path="url(#icon-4-29)" width="26.095396561286744" height="26.09539656128674" x="0" y="0"></image>
</svg>
</div>
<button class="download">download</button>
<!-- end snippet -->
I tried to copy the path
from svg icon, change the number in the path to get the size I expect, paste it to svg tag then download it.
But it will change the curve setting (ex: A4.61,4.61,0,0,1,.59,37.34
) and mass the shape.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const svg1 = `
<path d="M42.39,42H5.2A4.61,4.61,0,0,1,.59,37.34V5.57A4.61,4.61,0,0,1,5.2,1H42.39A4.6,4.6,0,0,1,47,5.57V37.34A4.6,4.6,0,0,1,42.39,42"/>
<path d="M23.58,14.69c1.9-1.91,3.83-3.77,5.67-5.71a6.79,6.79,0,0,1,5.12-2.17c2.13,0,4.27,0,6.39,0a5.76,5.76,0,0,1,5.54,4.78c.38,3.07-1.41,6.06-4.22,6.64a18.62,18.62,0,0,1-4.42.12A3.32,3.32,0,0,0,35,19.47C29.43,25.06,23.83,30.6,18.26,36.18a7,7,0,0,1-5.37,2.22c-2,0-3.89,0-5.84,0a5.94,5.94,0,0,1-5.7-5.82,5.77,5.77,0,0,1,5.54-5.81c1.2,0,2.41.05,3.61,0A2.49,2.49,0,0,0,12,26.13c1.68-1.59,3.31-3.23,4.91-4.91A2,2,0,0,0,17.39,20c.05-2.22,0-4.45,0-6.67s1.71-3.68,3.74-3.18a2.73,2.73,0,0,1,2.2,2.73c0,.57,0,1.15,0,1.73l.2.13M10.29,35.43c.77,0,1.55,0,2.32,0a4.79,4.79,0,0,0,3.95-1.61C22.12,28.19,27.78,22.61,33.36,17a4.67,4.67,0,0,1,3.77-1.58c1.11.06,2.22,0,3.34,0a2.76,2.76,0,0,0,2.88-2.92,2.84,2.84,0,0,0-2.9-2.72c-2,0-4,0-6,0a4,4,0,0,0-3.12,1.31c-5.8,5.86-11.65,11.67-17.47,17.51a3.92,3.92,0,0,1-3,1.24c-1.2,0-2.41,0-3.61,0-1.82,0-2.89,1-2.89,2.65a2.86,2.86,0,0,0,2.87,3c1,0,2,0,3.06,0"/>
<path d="M20.48,3.53a3.09,3.09,0,0,1,3,3.08,3,3,0,0,1-3.11,3,3,3,0,0,1-2.91-3.09,3.12,3.12,0,0,1,3.06-3"/>
<path d="M31.58,35.13c1.86-1.86,3.52-3.51,5.16-5.16.24-.24.49-.47.72-.73a1.45,1.45,0,0,0,.08-2.13,1.41,1.41,0,0,0-2.06.17c-1.67,1.64-3.32,3.31-5,5a8.63,8.63,0,0,0-.62.81l-.32-.13c0-.9,0-1.8,0-2.69s-.45-1.43-1.29-1.45a1.22,1.22,0,0,0-1.39,1.27c0,2.1-.05,4.2,0,6.29a1.4,1.4,0,0,0,1.54,1.5c2.06,0,4.13,0,6.19,0a1.24,1.24,0,0,0,1.32-1.35,1.29,1.29,0,0,0-1.4-1.38c-.89,0-1.78,0-3,0"/>
`;
const multipliedSvg = svg1.replace(/(\d+\.\d+|\d+)/g, (match) => parseFloat(match) * 10);
<!-- language: lang-html -->
<svg>
<path d="M423.9,420H52A46.1,46.1,0,0,10,.590,373.40000000000003V55.7A46.1,46.1,0,0,10,52,10H423.9A46,46,0,0,10,470,55.7V373.40000000000003A46,46,0,0,10,423.9,420"/>
<path d="M235.79999999999998,146.9c19-19.099999999999998,38.3-37.7,56.7-57.1a67.9,67.9,0,0,10,51.2-21.7c21.299999999999997,0,42.699999999999996,0,63.9,0a57.599999999999994,57.599999999999994,0,0,10,55.4,47.800000000000004c.380,30.7-14.1,60.599999999999994-42.199999999999996,66.39999999999999a186.20000000000002,186.20000000000002,0,0,10-44.2.120A33.199999999999996,33.199999999999996,0,0,0,350,194.7C294.3,250.6,238.29999999999998,306,182.60000000000002,361.8a70,70,0,0,10-53.7,22.200000000000003c-20,0-38.9,0-58.4,0a59.400000000000006,59.400000000000006,0,0,10-57-58.2,57.699999999999996,57.699999999999996,0,0,10,55.4-58.099999999999994c12,0,24.1.50,36.1,0A24.900000000000002,24.900000000000002,0,0,0,120,261.3c16.8-15.9,33.1-32.3,49.1-49.1A20,20,0,0,0,173.9,200c.50-22.200000000000003,0-44.5,0-66.7s17.1-36.800000000000004,37.400000000000006-31.8a27.3,27.3,0,0,10,22,27.3c0,.570,0,11.5,0,17.3l.21.299999999999997M102.89999999999999,354.3c.770,0,15.5,0,23.2,0a47.9,47.9,0,0,0,39.5-16.1C221.20000000000002,281.90000000000003,277.8,226.1,333.6,170a46.7,46.7,0,0,10,37.7-15.8c11.100000000000001.60,22.200000000000003,0,33.4,0a27.599999999999998,27.599999999999998,0,0,0,28.799999999999997-29.2,28.4,28.4,0,0,0-29-27.200000000000003c-20,0-40,0-60,0a40,40,0,0,0-31.200000000000003,13.100000000000001c-58,58.6-116.5,116.7-174.7,175.10000000000002a39.2,39.2,0,0,10-30,12.4c-12,0-24.1,0-36.1,0-18.2,0-28.900000000000002,10-28.900000000000002,26.5a28.599999999999998,28.599999999999998,0,0,0,28.700000000000003,30c10,0,20,0,30.6,0"/>
<path d="M204.8,35.3a30.9,30.9,0,0,10,30,30.8,30,30,0,0,10-31.099999999999998,30,30,30,0,0,10-29.1-30.9,31.200000000000003,31.200000000000003,0,0,10,30.6-30"/>
<path d="M315.79999999999995,351.3c18.6-18.6,35.2-35.099999999999994,51.6-51.6.240-.244.89999999999998-.477.2-.730a14.5,14.5,0,0,0,.80-21.299999999999997,14.1,14.1,0,0,0-20.6.170c-16.7,16.4-33.199999999999996,33.1-50,50a86.30000000000001,86.30000000000001,0,0,0-.628.1l-.320-.130c0-.90,0-18,0-26.9s-.450-14.299999999999999-12.9-14.5a12.2,12.2,0,0,0-13.899999999999999,12.7c0,21-.50,42,0,62.9a14,14,0,0,0,15.4,15c20.6,0,41.3,0,61.900000000000006,0a12.4,12.4,0,0,0,13.200000000000001-13.5,12.9,12.9,0,0,0-14-13.799999999999999c-.890,0-17.8,0-30,0"/>
</svg>
<!-- end snippet -->
Is there any way to download the svg with <image>
in it?
答案1
得分: 1
你的缩放函数不起作用,因为你在乘以所有命令值。
A
弧命令中的第4个(长弧标志)和第5个(扫描标志)值只接受值1或0;通过设置不同的值,实际上会阻止此段被渲染。你的控制台中还会看到错误日志,类似于:
错误:
<path>
属性d:期望数字,“...3.40000000000003V55.7A46.1,46.1,…”
另一种方法
你可以选择以下两种方法之一:
- 获取外部SVG并通过dataURL嵌入它。
- 或者将SVG嵌入为
<symbol>
或嵌套的<svg>
。
获取外部SVG并创建数据URL
这种方法要求外部SVG在相同的域上或在允许跨域访问的服务器上托管,否则你将会收到CORS错误。
const btnDownload = document.querySelector(".download");
const svgElement = document.querySelector("svg");
// 嵌入外部图像
embedExternalImagesAsDataURL(svgElement);
async function embedExternalImagesAsDataURL(svg) {
let images = svg.querySelectorAll("image");
images.forEach(async (image) => {
let href = image.getAttribute("xlink:href");
let fetched = await fetch(href);
let markup = await fetched.text();
// 创建数据URL
let dataURL = `data:image/svg+xml, ` + encodeURIComponent(markup);
// 用数据URL替换外部链接
image.setAttribute("xlink:href", dataURL);
});
}
btnDownload.addEventListener("click", () => {
const svgString = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "my_svg_file.svg";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
});
<svg id="svg" width="300" height="200">
<rect stroke="black" x="20" y="50" width="200" height="100" fill="navy"></rect>
<rect stroke="black" x="200" y="0" width="50" height="200" fill="brown"></rect>
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/4/4f/SVG_Logo.svg" visibility="visible" width="26.095396561286744" height="26.09539656128674" x="0" y="0"></image>
</svg>
<button class="download">下载</button>
嵌入为<symbol>
与第一种方法类似,我们获取外部SVG。这次我们创建一个<symbol>
和一个<use>
元素,用它们替换<image>
。
重要的是复制一些图像的属性,尤其是x、y、宽度和高度,最理想的情况下还包括viewBox
属性,否则嵌入的元素将具有不同的位置和大小。
const btnDownload = document.querySelector(".download");
const svgElement = document.querySelector("svg");
// 嵌入外部图像
embedExternalImagesAsNestedSVG(svgElement);
async function embedExternalImagesAsNestedSVG(svg) {
const ns = "http://www.w3.org/2000/svg";
let images = svg.querySelectorAll("image");
images.forEach(async (image) => {
let href = image.getAttribute("xlink:href");
let fetched = await fetch(href);
let markup = await fetched.text();
// 创建数据URL
let externalSVG = new DOMParser()
.parseFromString(markup, "image/svg+xml")
.querySelector("svg");
let symbol = document.createElementNS(ns, "symbol");
symbol.setAttribute("viewBox", externalSVG.getAttribute("viewBox"));
symbol.id = "external";
symbol.innerHTML = externalSVG.innerHTML;
let use = document.createElementNS(ns, "use");
use.setAttribute("xlink:href", "#external");
use.setAttribute("href", "#external");
// 复制属性,如宽度和高度
use.setAttribute("x", image.x.baseVal.value);
use.setAttribute("y", image.y.baseVal.value);
use.setAttribute("width", image.width.baseVal.value);
use.setAttribute("height", image.height.baseVal.value);
svg.insertBefore(symbol, svg.children[0]);
image.replaceWith(use);
});
}
btnDownload.addEventListener("click", () => {
const svgString = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "my_svg_file.svg";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
});
<svg id="svg" width="300" height="200">
<rect stroke="black" x="20" y="50" width="200" height="100" fill="navy"></rect>
<rect stroke="black" x="200" y="0" width="50" height="200" fill="brown"></rect>
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/4/4f/SVG_Logo.svg" visibility="visible" width="26.095396561286744" height="26.09539656128674" x="0" y="0"></image>
</svg>
<button class="download">下载</button>
缺点:冲突的CSS样式或重复的ID
外部SVG可能会意外使用与父SVG中使用的类名或ID相同的名称。这将导致样式被覆盖、渐变填充错误或剪切错误。因此,数据URL可能是更可靠的解决方案。
英文:
Your scaling function doesn't work because you're multiplying all command values.
The 4th (long arc flag) and 5th (sweep flag) values in an A
Arc command only accept the values 1 or 0;
By setting a different value you're actually prevent this segment from being rendered. You'll also see an error log in your console like:
> Error: <path> attribute d: Expected number,
> "…3.40000000000003V55.7A46.1,46.1,…".
Alternative
You can either
- fetch the external svg and embed it via dataURL
- or embed the svg as a
<symbol>
or nested<svg>
Fetch external and create data url
This approach requires your external svg to be on same domain or hosted on a server allowing cross origin access – otherwise you'll get a CORS error
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const btnDownload = document.querySelector(".download");
const svgElement = document.querySelector("svg");
// embed external images
embedExternalImagesAsDataURL(svgElement);
async function embedExternalImagesAsDataURL(svg) {
let images = svg.querySelectorAll("image");
images.forEach(async (image) => {
let href = image.getAttribute("xlink:href");
let fetched = await fetch(href);
let markup = await fetched.text();
// create data url
let dataURL = `data:image/svg+xml, ` + encodeURIComponent(markup);
// replace external href with data url
image.setAttribute("xlink:href", dataURL);
});
}
btnDownload.addEventListener("click", () => {
const svgString = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "my_svg_file.svg";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
});
<!-- language: lang-html -->
<svg id="svg" width="300" height="200">
<rect stroke="black" x="20" y="50" width="200" height="100" fill="navy"></rect>
<rect stroke="black" x="200" y="0" width="50" height="200" fill="brown"></rect>
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/4/4f/SVG_Logo.svg" visibility="visible" width="26.095396561286744" height="26.09539656128674" x="0" y="0"></image>
</svg>
<button class="download">download</button>
<!-- end snippet -->
Embed as <symbol>
Similar to the first approach we're fetching the external svg.
This time we're creating a <symbol>
and a <use>
element replacing the <image>
.
It is important to copy some of the images attributes most importantly x,y, width and height and ideally the viewBox
attribute – otherwise the embedded element will have different placement and size.
<!-- begin snippet: js hide: false console: false babel: false -->
<!-- language: lang-js -->
const btnDownload = document.querySelector(".download");
const svgElement = document.querySelector("svg");
// embed external images
embedExternalImagesAsNestedSVG(svgElement);
async function embedExternalImagesAsNestedSVG(svg) {
const ns = "http://www.w3.org/2000/svg";
let images = svg.querySelectorAll("image");
images.forEach(async (image) => {
let href = image.getAttribute("xlink:href");
let fetched = await fetch(href);
let markup = await fetched.text();
// create data url
let externalSVG = new DOMParser()
.parseFromString(markup, "image/svg+xml")
.querySelector("svg");
let symbol = document.createElementNS(ns, "symbol");
symbol.setAttribute("viewBox", externalSVG.getAttribute("viewBox"));
symbol.id = "external";
symbol.innerHTML = externalSVG.innerHTML;
let use = document.createElementNS(ns, "use");
use.setAttribute("xlink:href", "#external");
use.setAttribute("href", "#external");
// copy attributes like width and height
use.setAttribute("x", image.x.baseVal.value);
use.setAttribute("y", image.y.baseVal.value);
use.setAttribute("width", image.width.baseVal.value);
use.setAttribute("height", image.height.baseVal.value);
svg.insertBefore(symbol, svg.children[0]);
image.replaceWith(use);
});
}
btnDownload.addEventListener("click", () => {
const svgString = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "my_svg_file.svg";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
});
<!-- language: lang-html -->
<svg id="svg" width="300" height="200">
<rect stroke="black" x="20" y="50" width="200" height="100" fill="navy"></rect>
<rect stroke="black" x="200" y="0" width="50" height="200" fill="brown"></rect>
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/4/4f/SVG_Logo.svg" visibility="visible" width="26.095396561286744" height="26.09539656128674" x="0" y="0"></image>
</svg>
<button class="download">download</button>
<!-- end snippet -->
Drawbacks: conflicting CSS styles or duplicate IDs
The external svg might accidentally use class names or Ids that are also used in the parent SVG.
This will lead to overridden styling, wrong gradient fills or incorrect masking clipping.
So a data URL is probably the more robust solution.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论