I have a SVG that looks like this:
<svg id="Layer_1" data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 576 576">
<defs>
<style>...</style>
<clipPath id="clip-path">
<rect class="cls-1" x="0.02" width="575.04" height="576"/>
</clipPath>
</defs>
<g class="cls-2">
<path class="cls-3" d="M137.91-147.28c-4-1.67-8.25-3.46-12.37-3.86-5.43-.53-9.26,1.73-12.55,5a18.75,18.75,0,0,0-4.69-9.42,19.23,19.23,0,0,0-6.45-...
<path class="cls-4" d="M.08,502.59c-.79-5.67-6.22-4.3-5.81-.22a17.15,17.15,0,0,1,0,2.95c-.22,2.82-1.46,7.6-5,7.61-1.35,0-2.61-1-3.12...
...
I want to change it such that:
there is no grouping (this is easy)
change the path, rect, etc. elements such that they are clipped according to the what's in the clipPath element. The clipPath should no longer be present in the SVG (because it isn't needed anymore)
I've tried this:
inkscape --actions \
"select-all:groups; SelectionUnGroup; ObjectUnSetClipPath; export-filename: output.svg; export-plain-svg; export-do;" \
Decorations.svg
This removes the grouping, but the path elements are not clipped. The clipPath is still present.
ObjectUnSetClipPath will remove the clipping attribute clip-path="url(#clip-path)" – not the <def> clipping path itself.
But you can't clip any element, if your clipPath definition is stripped.
function stripClip(el){
let clipPaths = document.querySelector(el).querySelectorAll('clipPath');
if(clipPaths.length){
clipPaths.forEach(function(item, i){
item.remove();
}
)
}
}
svg{
width: 20vw;
}
.clipped{
clip-path: url(#clip-path2);
}
<p><button onclick="stripClip('#svg1')" >remove ClipPath</button></p>
<svg id="svg1" data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
<defs>
<style>...</style>
<clipPath id="clip-path">
<rect class="cls-1" x="10" width="36" height="18"/>
</clipPath>
</defs>
<g class="cls-2">
<path clip-path="url(#clip-path)" d="M18 2.0845
a 15.9155 15.916 0 0 1 0 31.83
a 15.916 15.916 0 0 1 0 -31.83z" fill="red" />
<rect clip-path="url(#clip-path)" x="0" y="0" width="10" height="25" />
</g>
</svg>
<svg id="svg2" data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
<defs>
<style>...</style>
<clipPath id="clip-path2">
<rect class="cls-1" x="10" width="36" height="18"/>
</clipPath>
</defs>
<g class="cls-2">
<path class="clipped" d="M18 2.0845
a 15.9155 15.916 0 0 1 0 31.83
a 15.916 15.916 0 0 1 0 -31.83z" fill="red" />
<rect class="clipped" x="0" y="0" width="10" height="25" />
</g>
</svg>
**Edit:** Get svg intersection paths using paper.js
Admittedly, not the most convenient approach but it is possible:
In addition to paper.js, we also need some script/helper to convert svg shapes (circles, rects etc.) to <path> elements (I'm using 'pathThatSvg'
, since paper.js can only calculate new intersection paths based on 2 path elements like so:
var intersectionPath = path1.intersect(clipPath);
Based on this answer
var svg = document.querySelector("#svgIntersect");
// set auto ids for processing
function setAutoIDs(svg) {
var svgtEls = svg.querySelectorAll(
"path, polygon, rect, circle, line, text, g"
);
svgtEls.forEach(function (el, i) {
if (!el.getAttribute("id")) {
el.id = el.nodeName + "-" + i;
}
});
}
setAutoIDs(svg);
// convert shapes to paths
function shapesToPath(svg) {
pathThatSvg(svg.outerHTML).then((converted) => {
var tmp = document.createElement("div");
tmp.innerHTML = converted;
svg.innerHTML = tmp.querySelector("svg").innerHTML;
});
}
shapesToPath(svg);
function intersectPath(svg, decimals=2) {
// init paper.js and add canvas
canvas = document.createElement('canvas');
canvas.id = "canvasPaper";
canvas.setAttribute('style','display:none')
document.body.appendChild(canvas);
paper.setup("canvasPaper");
// process clipped elements
var all = paper.project.importSVG(svg, function (item, i) {
item.position = new paper.Point(
item.bounds.width / 2,
item.bounds.height / 2
);
//item.scale(0.5, new Point(0, 0) )
var items = item.getItems();
var ids = item._namedChildren;
var groups = item.children;
groups.forEach(function (gr, i) {
var group = gr["_namedChildren"];
if (group) {
for (key in group) {
//get clip path
var clip = group["clipPath"][0];
if (key !== "clipPath") {
var el = group[key][0];
//get intersection path and generate d commands
var elClipped = el.intersect(clip);
var elClippedD = elClipped
.exportSVG({ precision: decimals })
.getAttribute("d");
// select path by id and overwrite d attribute
var newEl = svg.querySelector("#" + key);
newEl.setAttribute("d", elClippedD);
}
}
}
});
// remove clip defs and attributes
var clippedEls = svg.querySelectorAll("[clip-path]");
clippedEls.forEach(function (clippedEl, e) {
clippedEl.removeAttribute("clip-path");
});
svg.querySelector("defs").remove();
svg.classList.add("svg-intersect");
console.log(svg.outerHTML)
});
}
svg{
border: 1px solid #ccc;
display:inline-block;
width:200px;
}
.svg-intersect path{
stroke:red;
stroke-width: 0.25;
}
<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/path-that-svg#1.2.4/dist/pathThatSvg.umd.min.js"></script>
<p>
<button type="button" onclick="intersectPath(svg)">get Path Intersect</button>
</p>
<svg id="svgIntersect" viewBox="0 0 100 100">
<defs>
<clipPath id="clipPath">
<circle cx="25" cy="25" r="25"/>
</clipPath>
</defs>
<g id="clipGroup" clip-path="url(#clipPath)">
<circle fill="#999" data-id="circle" cx="25" cy="25" r="25" />
<rect fill="#555" id="rect" x="25" y="25" width="50" height="50" />
</g>
<g clip-path="url(#clipPath)">
<circle fill="#444" id="circle2" cx="66" cy="25" r="25" />
<rect fill="#22" id="rect2" x="15" y="12.5" width="20" height="75" />
</g>
</svg>
Plunkr example.
So lets say I have svg that I am including inline in HTML
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" class="col-sm-3">
<symbol id="circle" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="white" stroke="black" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".3em">___THIS_IS_A_VARIABLE__</text>
</symbol>
<use xlink:href="#circle" width="85" height="85"/>
</svg>
I have a slightly more complex use care where the inside of the <symbol/> is a complex path of a different scale than the svg root viewbox so I need to use the <symbol/> to scale it to be full size.
I need to include a bunch of these in my html which I do via a for loop. Everything works find, but I am afraid I might introduce bugs in the future because in my html document I have multiple elements with the same id.
My questions:
Is there a different way to use <use/> like with a css selector?
Am I correct that repeating ids in inline SVG inside a html document is as bad as repeating ids in a html document itself?
Is there a better way to nest viewbox attributes to avoid <use/>?
The best way I can think of is to make the id dynamic. So in ther case of using angular it would be:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" class="col-sm-3">
<symbol [id]="circleId" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="white" stroke="black" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".3em">___THIS_IS_A_VARIABLE__</text>
</symbol>
<use [attr.xlink:href]="'#' + circleId" width="85" height="85"/>
</svg>
No need for a Framework, native W3C Web Components can do the same:
If you don't/can't specify an id, generate one: let id = "id" + new Date()/1;
svg {
display: inline-block;
background: grey;
}
<svg-shape id=1 text="red"></svg-shape>
<svg-shape id=2></svg-shape>
<svg-shape id=3 text="blue"></svg-shape>
<script>
customElements.define('svg-shape', class extends HTMLElement {
connectedCallback() {
let id = this.getAttribute("id");
let text = this.hasAttribute("text") ? this.getAttribute("text") : "";
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" class="col-sm-3">
<symbol id="id${id}" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="white" stroke="${text}" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".3em">${text}</text>
</symbol>
<use href="#id${id}" width="85" height="85"/>
</svg>`;
this.onclick = (evt) => console.log('clicked', id);
}
});
</script>
Forgive my English
I am using svg with filters, but faced the following problem.
This is the base for svg. The result is expected:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<g>
<circle id="2" cx="50" cy="50" r="50"/>
<g id="1">
<rect x="0" y="0" width="50" height="50" fill="#ccc"/>
<rect x="50" y="50" width="50" height="50" fill="#ccc"/>
</g>
</g>
</svg>
But with the filter feComposite:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<defs>
<filter id="myFilter1">
<feImage href="#1" result="1"/>
<feImage href="#2" result="2"/>
<feComposite in="1" in2="2" operator="xor"/>
</filter>
</defs>
<g filter="url(#myFilter1)">
<circle id="2" cx="50" cy="50" r="50"/>
<g id="1">
<rect x="0" y="0" width="50" height="50" fill="#ccc"/>
<rect x="50" y="50" width="50" height="50" fill="#ccc"/>
</g>
</g>
</svg>
As you can see, the image is shifted. If you inspect the code, the blocks will not match the visible image:
Here with the addition of interactivity:
const value = (max = 100000000, min = 0) => Math.round(Math.random() * (max - min)) + min;
const createCircle = (size) => {
const r = value(10, 3);
const cx = value(size - r - 10, r + 10);
const cy = value(size - r - 10, r + 10);
return {
r,
cx,
cy
}
};
const createCircles = (counts, size) => Array(counts).fill().map(() => createCircle(size));
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
position: {
x: 0,
y: 0,
}
};
this.size = 300;
this.circlesData = createCircles(100, this.size);
const getCoords = (c, i) => c + (this.state.position.x * 0.002 * c * (i % 2 ? 1 : -1));
this.circles = () => this.circlesData.map((item, i) => <circle key = {`circles_12dew1_${i}`} cx={getCoords(item.cx, i)} cy={getCoords(item.cy, i)} r={item.r}/>);
}
onMouseMove = e => {
const position = {
x: e.pageX,
y: e.pageY,
};
this.setState({position});
}
render() {
return (
<div className = "App" >
<svg onMouseMove={this.onMouseMove} ref = {elem => this.svg = elem} xmlns = "http://www.w3.org/2000/svg" width = {this.size} height = {this.size} viewBox={`0 0 ${this.size} ${this.size}`}>
<defs>
<filter id="myFilter1">
<feImage href="#1" result="1"/>
<feImage href="#2" result="2"/>
<feComposite in ="1" in2="2" operator="xor"/>
</filter>
</defs>
<g id = "3" filter = "url(#myFilter1)" >
<circle id = "2" cx={this.size / 2 + 100} cy={this.size / 2 + 100} r={this.size / 3}/>
<g id="1"> {this.circles()} </g>
</g>
</svg>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
As you can see, a big circle is moving, but it shouldn't be doing that.
How to fix?
Or how to make the exception of figures as in the interactive example without svg? For example using canvas, thank you!
The image placement behaviour you are seeing is because <feImage> elements are positioned using the filter region, or filter primitive region.
https://www.w3.org/TR/SVG11/single-page.html#filters-feImageElement
By default the filter region is an area that is 10% bigger than the original object on all sides.
x="-10%" y="-10%" width="120%" height="120%"
This is done to cater for filter primitives, such as <feGuassianBlur>, which extend outside the original size and would otherwise get clipped.
To get the images to position how you want, change the filter region, or filter primitive region, to be the same size as the original object. For example:
<feImage href="#1" x="0" y="0" width="100%" height="100%" result="1"/>
Updated demo:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<defs>
<filter id="myFilter1">
<feImage href="#1" x="0" y="0" width="100%" height="100%" result="1"/>
<feImage href="#2" x="0" y="0" width="100%" height="100%" result="2"/>
<feComposite in="1" in2="2" operator="xor"/>
</filter>
</defs>
<g filter="url(#myFilter1)">
<circle id="2" cx="50" cy="50" r="50"/>
<g id="1">
<rect x="0" y="0" width="50" height="50" fill="#ccc"/>
<rect x="50" y="50" width="50" height="50" fill="#ccc"/>
</g>
</g>
</svg>
This is my code:
<?xml version="1.0">
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 30" version="1.1">
<text id="t1" x="50%" y="50%" text-anchor="middle">Hello World</text>
<g transform="translate(0,0) rotate(0)">
<rect x="0" y="0" width="10" height="10" fill="blue" />
</g>
</svg>
This gives me hello world and a rectangle. I would like to know how to position my rectangle relative to my text. I thought this would do the trick, but according to my code above the rectangle should sit on top of the text but it does not.
Edit: I tried this but it didn't change anything:
<?xml version="1.0">
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 30" version="1.1">
<g transform="translate(0,0) rotate(0)">
<rect x="0" y="0" width="10" height="10" fill="blue" />
</g>
<text id="t1" x="50%" y="50%" text-anchor="middle">Hello World</text>
</svg>
Is this close to what you want?
<svg width="100%" height="100%" viewBox="0 0 100 30" version="1.1">
<rect x="0" y="0" width="100%" height="100%" fill="blue" />
<text id="t1" x="50%" y="50%" text-anchor="middle" dy="0.3em">Hello World</text>
</svg>
The correct value to use for dy, to get the text vertically centred, is font specific. You may have to tweak that value to match whichever font you choose. In my opinion, it is a more reliable alternative to other solutions like alignment-baseline etc.
Another Approach you can try! Maybe It will fit for you ...
p {
font-size: 42px;
}
p>svg {
width: 60px;
height: 60px;
display: inline-block;
position: relative;
top: 28px;
}
<div style="display:none">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol viewBox="0 0 20 20" id="box">
<g transform="translate(0,0) rotate(0)">
<rect x="0" y="0" width="12" height="12" fill="blue" />
</g>
</symbol>
</svg>
</div>
<p>
<svg>
<use xlink:href="#box"></use>
</svg> Hello World
</p>
I have the following SVG code.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="500" preserveAspectRatio="none">
<svg x="10">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A2B2B1" d="M78.447-0.167c46.51-0.516,69.627,42,81.004,77.004
c12.313,37.886-7.802,73.92-35.002,82.504C82.63,172.54,33.583,136.621,14.944,115.339c-7.92-9.042-19.932-22.157-14.001-41.502
c9.402-30.666,31.449-50.303,56.003-66.003C64.113,5.167,71.281,2.5,78.447-0.167z"
onmouseover="this.style.stroke = '#000000'; this.style['stroke-width'] = 1;"
onmouseout="this.style.stroke = '#000000'; this.style['stroke-width'] = 0;"/>
<animateMotion
path="M14.088,6.647c5.426,0,13.935,0.083,16.278,4.281
c2.343,4.199,8.151,8.273,2.726,12.678c-5.426,4.405-2.753-6.984-10.337-8.479c-7.584-1.496-5.103,5.973-10.528,5.973
S0.557,10.939,6.293,6.647C10.917,3.188,8.663,6.647,14.088,6.647z"
begin="0s" dur="5s" repeatCount="indefinite"
/>
</g>
</svg>
<svg x="200">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#D8D3DE" d="M78.447-0.167c46.51-0.516,69.627,42,81.004,77.004
c12.313,37.886-7.802,73.92-35.002,82.504C82.63,172.54,33.583,136.621,14.944,115.339c-7.92-9.042-19.932-22.157-14.001-41.502
c9.402-30.666,31.449-50.303,56.003-66.003C64.113,5.167,71.281,2.5,78.447-0.167z"
onmouseover="this.style.stroke = '#000000'; this.style['stroke-width'] = 1;"
onmouseout="this.style.stroke = '#000000'; this.style['stroke-width'] = 0;"/>
<animateMotion
path="M38.69,22.497c-7.039,3.198-18.123,8.114-23.417,4.53
C9.979,23.443,0.254,22.048,4.927,13.642c4.673-8.406,7.322,6.636,17.965,3.935S26.304,7.508,33.343,4.311
s20.598,5.138,15.461,13.592C44.664,24.718,45.729,19.3,38.69,22.497z"
begin="0s" dur="5s" repeatCount="indefinite"
/>
</g>
</svg>
</svg>
is it possible to pause the animateMotion on mouse over and then restart it on mouse out.
Hosted here if you wanted to see it working - http://cdpn.io/cIfCv
It's possible to pause and unpause all declarative animations in an svg, like this:
<svg id="root" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="500" preserveAspectRatio="none">
<svg x="10">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A2B2B1" d="M78.447-0.167c46.51-0.516,69.627,42,81.004,77.004
c12.313,37.886-7.802,73.92-35.002,82.504C82.63,172.54,33.583,136.621,14.944,115.339c-7.92-9.042-19.932-22.157-14.001-41.502
c9.402-30.666,31.449-50.303,56.003-66.003C64.113,5.167,71.281,2.5,78.447-0.167z" stroke-width="1" stroke="none" onmouseover="pause(); this.style.stroke='#000000';" onmouseout="unpause(); this.style.stroke='none';" />
<animateMotion path="M14.088,6.647c5.426,0,13.935,0.083,16.278,4.281
c2.343,4.199,8.151,8.273,2.726,12.678c-5.426,4.405-2.753-6.984-10.337-8.479c-7.584-1.496-5.103,5.973-10.528,5.973
S0.557,10.939,6.293,6.647C10.917,3.188,8.663,6.647,14.088,6.647z" begin="0s" dur="5s" repeatCount="indefinite" />
</g>
</svg>
<svg x="200">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#D8D3DE" d="M78.447-0.167c46.51-0.516,69.627,42,81.004,77.004
c12.313,37.886-7.802,73.92-35.002,82.504C82.63,172.54,33.583,136.621,14.944,115.339c-7.92-9.042-19.932-22.157-14.001-41.502
c9.402-30.666,31.449-50.303,56.003-66.003C64.113,5.167,71.281,2.5,78.447-0.167z" stroke-width="1" onmouseover="pause(); this.style.stroke = '#000000';" onmouseout="unpause(); this.style.stroke = 'none';" />
<animateMotion path="M38.69,22.497c-7.039,3.198-18.123,8.114-23.417,4.53
C9.979,23.443,0.254,22.048,4.927,13.642c4.673-8.406,7.322,6.636,17.965,3.935S26.304,7.508,33.343,4.311
s20.598,5.138,15.461,13.592C44.664,24.718,45.729,19.3,38.69,22.497z" begin="0s" dur="5s" repeatCount="indefinite" />
</g>
</svg>
<script>
var svg = document.getElementById("root");
function pause() {
svg.pauseAnimations();
}
function unpause() {
svg.unpauseAnimations();
}
</script>
</svg>
See fiddle.