Hitting a break wall with SVG - svg

Why a light colored rectangle background showed up on the area behind xlink every time it is touched or clicked?
Here is my SVG code:
<svg version="1.1" viewBox="0 0 147.86 258.44" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g class="bglyr" fill="none" transform="translate(-30.575,-20.926)">
<a xlink:href="https://google.com">
<path id="link1" fill="none" onmouseover="change(this)" onmouseout="unchange(this)" d="m104.43 242.44 11.742 6.1664 57.094 29.884-137.62 0.0617z" />
<a />
</g>
</svg>
JavaScript:
function change(item) {
item.style.fill = "#42d46b";
item.style.opacity = "1.0";
}
function unchange(item) {
item.style.fill = "none";
item.style.opacity = "0.0";
}

Just in case your question is actually about why your example code doesn't work, then I can explain that for you.
Mouse events won't be triggered if your <path> has nothing to hover over. When the fill is "none" or the opacity is 0. No mouse events will be triggered. To fix this, use fill="transparent" instead. This behaves the same way, but is detected as if it was any other non-transparent fill colour.
function change(item) {
item.style.fill = "#42d46b";
}
function unchange(item) {
item.style.fill = "transparent";
}
<svg version="1.1" viewBox="0 0 147.86 258.44" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g class="bglyr" fill="none" transform="translate(-30.575,-20.926)">
<a xlink:href="https://google.com">
<path id="link1" fill="transparent" onmouseover="change(this)" onmouseout="unchange(this)" d="m104.43 242.44 11.742 6.1664 57.094 29.884-137.62 0.0617z" />
</a>
</g>
</svg>

Related

Change SVG paths to be clipped according to clipPath element

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>

Alternative to <use/> to avoid repeating ids when repeating Inline 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>

Dragging wrong riot component

I'm creating a draggable riot js based SVG element.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="green" data-is="r-rect" x="30" y="230" width="256" height="64" rx="5" ry="5" draggable="true" fill="green"></g>
<g name="blue" data-is="r-rect" x="10" y="110" width="256" height="64" rx="5" ry="5" draggable="true" fill="blue"></g>
</svg>
r-rect.tag
<r-rect>
<rect ref="rect" onmousedown={hold} />
<script>
tag = this;
tag.hold = hold;
tag.x= Number.parseInt(opts.x);
tag.y= Number.parseInt(opts.y);
tag._name= opts.name;
tag.on("mount", function(e) {
tag.refs.rect.setAttribute("x", opts.x);
tag.refs.rect.setAttribute("y", opts.y);
tag.refs.rect.setAttribute("width", opts.width);
tag.refs.rect.setAttribute("height", opts.height);
opts.rx && (tag.refs.rect.setAttribute("rx", opts.rx) );
opts.ry && (tag.refs.rect.setAttribute("ry", opts.ry) );
})
function hold(e){
console.log(tag._name)
}
</script>
</r-rect>
I add 2 r-rect tags. Try to drag blue rectangle. But mousedown event of green rectangle triggers always .
http://jsfiddle.net/1k2gacy1/1/
I found the issue. It was happening because I was assigning the tag instance in global variable. So I've changed this line tag = this; to var tag = this;. And it solved the issue.

Using SVG logo as mask

I'm basing my exercise on the accepted answer in: Creating transparent text to show gradient color of underlying div
Here's my rendition in jsfiddle: http://jsfiddle.net/skrln/zSjgL/
The svg code of my logo:
<svg width="190" height="121">
<mask id="cutouttext">
<rect width="190" height="121" x="0" y="0" fill="white" />
<path id="number-two" d="M75.3,56.1c7.3-3,14.2-12.5,14.2-24c0-17.7-15.1-32.1-36.8-32.1H0v121.5h52.4c30,0,43.4-16.5,43.4-36.8
C95.8,72.3,87,59.8,75.3,56.1z M66.5,94.6h-49V79.7h0.1l27-22.1c3.5-2.8,5.3-6.1,5.3-9c0-4-3.2-7.6-8.4-7.6c-6.4,0-9.1,5.7-10.2,9
l-14.6-3.9c2.9-10.8,11.8-19.1,25.2-19.1c14.4,0,24.5,9.4,24.5,21.5c0,12.4-9,18.1-17.1,23.8l-10.4,7.3h27.6V94.6z" />
<polygon id="filler" points="190,33.9 190,0 101.6,0 101.6,121.5 190,121.5 190,87.6 141.4,87.6 141.4,74.7 177.1,74.7 177.1,46.6
141.4,46.6 141.4,33.9 " />
</mask>
<rect width="190" height="121" x="0" y="0" fill="white" mask="url(#cutouttext)" />
</svg>
The result so far:
Issue:
The mask isn't behaving the way I want to; I want the inner parts of the "B" and "E" to mask out the gray underlying div so you can see the background image like the image below:
I'm having trouble knowing what part of the logo is the and which one is the . Also I can't seem to figure out the logic behind the <mask> in the SVG.
There's nothing wrong with your SVG. You placed it on a grey background, so the bits that are masked out are grey.
What you want to do is remove the grey background from below the SVG image. There may be neater ways of doing this, but one approach is to use a table layout with the logo in one cell and the grey background in another.
Here's a JSFiddle link
HTML
<div class="gray">
<svg width="190" height="121">
<mask id="cutouttext">
<rect width="190" height="121" x="0" y="0" fill="white" />
<path d="M75.3,56.1c7.3-3,14.2-12.5,14.2-24c0-17.7-15.1-32.1-36.8-32.1H0v121.5h52.4c30,0,43.4-16.5,43.4-36.8
C95.8,72.3,87,59.8,75.3,56.1z M66.5,94.6h-49V79.7h0.1l27-22.1c3.5-2.8,5.3-6.1,5.3-9c0-4-3.2-7.6-8.4-7.6c-6.4,0-9.1,5.7-10.2,9
l-14.6-3.9c2.9-10.8,11.8-19.1,25.2-19.1c14.4,0,24.5,9.4,24.5,21.5c0,12.4-9,18.1-17.1,23.8l-10.4,7.3h27.6V94.6z" />
<polygon points="190,33.9 190,0 101.6,0 101.6,121.5 190,121.5 190,87.6 141.4,87.6 141.4,74.7 177.1,74.7 177.1,46.6
141.4,46.6 141.4,33.9 " />
</mask>
<rect width="190" height="121" x="0" y="0" fill="white" mask="url(#cutouttext)" />
</svg>
<div></div>
</div>
CSS
.body {
background: #550000 url('http://sciencelakes.com/data_images/out/7/8788677-red-background-wallpaper.jpg');
display: block;
height: 500px;
margin: 0;
}
.gray {
display:table-row;
width:100%;
height:121px;
}
.gray div, .gray svg {
display:table-cell;
}
.gray div {
background:gray;
width:100%;
}

Consistently change background colour upon hovering over an SVG link

I decided to play around re-making a nav menu in SVG. It looks quite a bit like this site's nav actually, so nothing much to imagine.
I'm drawing boxes with SVG and then placing text over them, enclosing them both in a link. By attaching a css class to the box, I can set a :hover attritbute, so I can change the background colour when the user hovers over it. The problem is, when the user hovers over the text the color change is reversed, even though the link still works.
How can I make the box change colour as well?
The code looks like this:
<svg width="640px" height="40px"
xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform="translate(60 20) ">
<a xlink:href="http://www.apple.com">
<rect width="100" height="40" x="-50" y="-20" rx="5" class="svg_nav" />
<text class= "nav_text" text-anchor="middle" dominant-baseline="mathematical">Home</text>
</a>
</g>
</svg>
What do your style rules look like?
Something like the following should work fine:
<svg width="640px" height="40px"
xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform="translate(60 20) ">
<a xlink:href="http://www.apple.com">
<rect width="100" height="40" x="-50" y="-20" rx="5" class="svg_nav" />
<text class= "nav_text" text-anchor="middle" dominant-baseline="mathematical">Home</text>
</a>
</g>
<g transform="translate(166 20) ">
<a xlink:href="http://www.apple.com">
<rect width="100" height="40" x="-50" y="-20" rx="5" class="svg_nav" />
<text class= "nav_text" text-anchor="middle" dominant-baseline="mathematical">Home</text>
</a>
</g>
<style>
g:hover .svg_nav { fill: blue }
g:hover .nav_text { fill: red }
.svg_nav { fill: yellow }
.nav_text { fill: black }
</style>
</svg>

Resources