I am working on combining SVG images into one combined SVG image using ReactJS. Sorry you may think that this is duplicate question. But I have checked all the suggested questions but none of them solved my issue. I have done lot of research in fact spent so many days in figuring out solution for this problem.
I have checked NodeJS sharp library as well to solve this issue. I know you must be wondering why I am checking NodeJS because I am looking for solution related to ReactJS. I was checking that because my frontend is in ReactJS but my backend is in NodeJS so I thought If I couldn't achieve this in ReactJS then I should try to do same in NodeJS. But I found that sharp library can take input as SVG but not providing output in SVG.
Actually I am using NPM package merge-images to create combined image. In this I am passing multiple SVG's and getting output in PNG format as combined image. I am giving my code for reference.
import mergeImages from 'merge-images';
const Avatar = (props) => {
const [src, setSrc] = useState('');
useEffect(() => {
let filteredArray = [
{ "name": "skintone","src": "../images/skintone.svg"},
{ "name": "face","src": "../images/face.svg"},
{ "name": "hair","src": "../images/hair.svg"}
];
mergeImages(filteredArray)
.then(src => {
//this src is the base64 encoded data of image in PNG format
console.log('base64 string', src);
setSrc(src);
})
.catch(err => {
console.log('Error', err)
});
});
return (
{ src ? <div><image src={src} /></div> : <div><p>Image Loading....</p></div> }
)
}
Above code giving image in PNG format.But I wanted output image in SVG format. So I tried passing MIME format as a parameter like as follows
mergeImages(filteredArray, {format: 'image/svg xml'})
.then(src => {
//this src is the base64 encoded data of image in PNG format
console.log('base64 string', src);
setSrc(src);
})
.catch(err => {
console.log('Error', err)
});
But above code also giving output image in PNG format.
Please help me in getting output image in SVG format. If possible suggest me any other library through which I can convert combined image in SVG format.
Thanks in advance.
CodePudding user response:
you can just render them one after another, and pass position: absolute
to each svg, that will make them render on the same place
<svg style={{ position: 'absolute' }}>...</svg>
<svg style={{ position: 'absolute' }}>...</svg>
<svg style={{ position: 'absolute' }}>...</svg>
CodePudding user response:
I assume your ultimate goal is to create a avatar generator?
Unfortunately, I can't provide a proper react/node.js example, but the following js examples might help you to get the gist.
JS example 1: merge external svgs into nested image
Since svg is XML based you can concatenate the raw text content of each file and wrap it into the output avatar svg.
So you don't need a dedicated image processing library.
The resulting file would be a nested svg like this:
<svg id="avatar">
<svg id="part1"></svg>
<svg id="part2"></svg>
</svg>
// file src
let svgSrcs = [
"https://cdn.jsdelivr.net/npm/[email protected]/icons/chainlink.svg",
"https://cdn.jsdelivr.net/npm/[email protected]/icons/chai.svg"
];
compileSVG(svgSrcs);
async function fetchSvgs(src) {
const response = await fetch(src);
const svg = await response.text();
return svg;
}
async function compileSVG(svgSrcs) {
let newSVG = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">`;
for (let i = 0; i < svgSrcs.length; i ) {
let src = svgSrcs[i];
newSVG = await fetchSvgs(src);
}
newSVG = "</svg>";
// encode base 64
let base64 = btoa(unescape(encodeURIComponent(newSVG)));
// render image
let img = document.createElement("img");
document.body.appendChild(img);
img.src = "data:image/svg xml;base64," base64;
document.body.insertAdjacentHTML('beforeend', newSVG);
return newSVG;
}
body{
font-family:sans-serif
}
img,
svg{
width:10em;
}
<h3>Nested svg</h3>
This approach will work well in modern browsers, but won't produce the best standalone svg files.
Some editors or img previewers have issues rendering nested svgs.
JS example 2: parse external svgs and copy child nodes to merged file
// create parent svg
let ns = "http://www.w3.org/2000/svg";
let xlink = "http://www.w3.org/1999/xlink";
let svgMerged = document.createElementNS(ns, "svg");
svgMerged.setAttribute("xmlns", ns);
svgMerged.setAttribute("xmlns:xlink", xlink);
svgMerged.setAttribute("viewBox", "0 0 24 24");
// file src
let svgSrcs = [
"https://cdn.jsdelivr.net/npm/[email protected]/icons/chainlink.svg",
"https://cdn.jsdelivr.net/npm/[email protected]/icons/chai.svg"
];
compileSVG(svgSrcs);
// fetch async
async function fetchSvgs(src) {
const response = await fetch(src);
const svg = await response.text();
return svg;
}
// get each svg's content
async function compileSVG(svgSrcs) {
for (let i = 0; i < svgSrcs.length; i ) {
let src = svgSrcs[i];
let svgXml = await fetchSvgs(src);
const parser = new DOMParser();
let svg = parser.parseFromString(svgXml, "application/xml").querySelector('svg');
let svgEls = [...svg.children];
// copy all svg elements to parent svg
svgEls.forEach(el => {
let clonedEl = el.cloneNode(true);
svgMerged.appendChild(clonedEl)
});
}
// serialize to svg xml string
const serializer = new XMLSerializer();
const svgXml = serializer.serializeToString(svgMerged);
let base64 = btoa(unescape(encodeURIComponent(svgXml)));
let img = document.createElement('img');
img.src = 'data:image/svg xml;base64,' base64;
// render image
document.body.appendChild(img);
document.body.appendChild(svgMerged)
}
body {
font-family: sans-serif;
}
img,
svg {
width: 10em;
}
<p>Single svgs</p>
<img src="https://cdn.jsdelivr.net/npm/[email protected]/icons/chainlink.svg" alt="">
<img src="https://cdn.jsdelivr.net/npm/[email protected]/icons/chai.svg" alt="">
<p>Merged svg</p>
The output file would contain child nodes of each external svg:
<svg id="avatar">
<path id="part1" />
<path id="part2" />
</svg>
Make sure, all svg asset elements have unique IDs (or classes) otherwise styles, clip paths, gradients etc. might be broken when concatenating your svgs.
This approach will result in more robust standalone svgs (preview, editing, conversion to pixel image).
Adapting these concept to react.js should't be too difficult. See also:
How to read text file in react
Parse and Render external HTML in React component