Paper.js might be the perfect library for this task:
In particular it's Boolean operations – like unite() to merge path elements. The syntax looks something like this:
let unitedPath = path1.unite(path2);
The following example also employs Jarek Foksa's pathData polyfill.
Example: unite paths:
/**
* merge paths
*/
function unite(svg, decimals = 3) {
let paths = svg.querySelectorAll("path");
let path0 = paths[0];
let d0 = path0.getAttribute("d");
// create new paper.js path object
let paperPath0 = new Path(d0);
for (let i = 1; i < paths.length; i++) {
let pathI = paths[i];
let dI = pathI.getAttribute("d");
// create new paper.js path object for all children
let paperPathI = new Path(dI);
paperPath0 = paperPath0.unite(paperPathI);
pathI.remove();
}
let dUnited = paperPath0
.exportSVG({
precision: 3
})
.getAttribute("d");
path0.setAttribute("d", dUnited);
}
// init paper.js
window.addEventListener("DOMContentLoaded", (e) => {
initPaper();
});
// init paper.js and add mandatory canvas
function initPaper() {
canvas = document.createElement("canvas");
canvas.id = "canvasPaper";
canvas.setAttribute("style", "display:none");
document.body.appendChild(canvas);
paper.install(window);
paper.setup("canvasPaper");
}
svg {
display: inline-block;
width: 10em
}
svg * {
fill: none;
stroke: red;
stroke-width: 0.25%;
}
<p>
<button type="button" onclick="unite(svg, 3)">Unite Path </button>
</p>
<svg class="svgunite" id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" stroke-width="1" stroke="#000">
<path fill="none" d="M50.05 23.21l-19.83 61.51h-9.27l23.6-69.44h10.82l23.7 69.44h-9.58l-20.44-61.51h1z"/>
<rect fill="none" x="35.49" y="52.75" width="28.5" height="6.17">
</rect>
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/path-data-polyfill.min.js"></script>
<script>
/**
* convert all primitives to paths
* like <rect>, <circle> etc
*/
convertPrimitives(svg);
function convertPrimitives(svg) {
let els = svg.querySelectorAll("path, rect, circle, polygon, ellipse ");
let pathDataCombined = [];
let className = els[0].getAttribute("class") ?
els[0].getAttribute("class") :
"";
let id = els[0].id;
let fill = els[0].getAttribute("fill");
els.forEach(function(el, i) {
let pathData = el.getPathData({
normalize: true
});
// create path for conversion
let pathTmp = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
pathTmp.id = id;
pathTmp.setAttribute("class", className);
pathTmp.setAttribute("fill", fill);
pathTmp.setPathData(pathData);
el.replaceWith(pathTmp);
});
}
</script>
Optional: Path normalization (using getPathData() polyfill)
You might also need to convert svg primitives (<rect>
, <circle>
, <polygon>
) like the horizontal stroke in the capital A .
The pathData polyfill provides a method of normalizing svg elements.
This normalization will output a d
attribute (for every selected svg child element) containing only a reduced set of cubic path commands (M, C, L, Z) – all based on absolute coordinates.
Little downer:
I won't say paper.js can boast of a plethora of tutorials or detailed examples. But you might check the reference for pathItem to see all options.
See also: Subtracting SVG paths programmatically