CSS3 - Select SVG group sibling - css

In a React application, I'm returning an SVG tag from a component. The SVG consists of a ring, and groups of elements positioned around it. Each group contains a shape (circle or rect) and a path depicting a number overlaying and identifying the shape.
For a minimum working example:
https://codepen.io/notthelewis/pen/gOGRZQQ
Each group can be either active or inactive. In the inactive state, I want the background colour of the shape to change on hover , but I want the path's fill to stay the same colour. Current behaviour of the codepen works when hovering over the shape itself, but reverts when hovering over the path (number) element, which is inside the shape; meaning that the fill of the shape returns to default rather than the chosen hover colour.
In looking around, I tried a few solutions, but could not get the selectors to perform quite how I want.
To surmise:
.inactive path:hover should somehow target: is(rect, circle) contained in the same group
.inactive path's stroke property should remain unaffected.
I've tried the following (to no avail!):
.inactive ~ path:hover {
fill: red;
}
.inactive path:hover ~ {
fill: red;
}
.inactive path:hover+rect {
fill: red;
}
.inactive :is(rect, circle)+path:hover {
fill: red;
}
I did find the has selector, but it doesn't appear to be adopted anywhere meaningful...
https://caniuse.com/css-has
And the has selector is also mentioned alongside the general successor selector answer provided in this SO question:
Is there a "previous sibling" selector?

Put the :hover on the <g>, not a descendant of <g>:
g.inactive:hover > rect,
g.inactive:hover > circle {
fill: grey;
}
Also, replace the huge <path> elements that are being used to represent text with <text> elements:
Before:
<svg width="468" height="304" viewBox="0 0 468 304" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Sensors">
<circle id="Sensor Ring" cx="228" cy="154" r="149" stroke="black" strokeWidth="2"/>
<g id="6" class='inactive'>
<rect id="6_sensor" x="80" y="203" width="20" height="20" fill="white" stroke="black" strokeWidth="2"/>
<path id="6_tag" d="M86.9941 213.561C86.9941 211.866 87.3516 210.604 88.0664 209.776C88.7852 208.948 89.8594 208.534 91.2891 208.534C91.7773 208.534 92.1602 208.563 92.4375 208.622V210.069C92.0898 209.991 91.7461 209.952 91.4062 209.952C90.7852 209.952 90.2773 210.046 89.8828 210.233C89.4922 210.421 89.1992 210.698 89.0039 211.065C88.8086 211.432 88.6934 211.954 88.6582 212.63H88.7344C89.1211 211.966 89.7402 211.634 90.5918 211.634C91.3574 211.634 91.957 211.874 92.3906 212.354C92.8242 212.835 93.041 213.499 93.041 214.346C93.041 215.261 92.7832 215.985 92.2676 216.52C91.752 217.052 91.0371 217.317 90.123 217.317C89.4902 217.317 88.9375 217.171 88.4648 216.878C87.9961 216.585 87.6328 216.157 87.375 215.595C87.1211 215.032 86.9941 214.354 86.9941 213.561ZM90.0879 215.87C90.4746 215.87 90.7715 215.741 90.9785 215.483C91.1855 215.221 91.2891 214.85 91.2891 214.37C91.2891 213.952 91.1914 213.624 90.9961 213.386C90.8047 213.143 90.5137 213.022 90.123 213.022C89.7559 213.022 89.4414 213.141 89.1797 213.38C88.9219 213.618 88.793 213.895 88.793 214.212C88.793 214.677 88.9141 215.069 89.1562 215.389C89.4023 215.71 89.7129 215.87 90.0879 215.87Z" fill="black"/>
</g>
After:
<svg width="468" height="304" viewBox="0 0 468 304" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Sensors">
<circle id="Sensor Ring" cx="228" cy="154" r="149" stroke="black" strokeWidth="2"/>
<g id="6" class='inactive'>
<rect id="6_sensor" x="80" y="203" width="20" height="20" fill="white" stroke="black" strokeWidth="2"/>
<text>6</text>
</g>
(You'll need to set font-size though).

Related

SVG with marker having an issue on mouse over

I have a button with svg inside like:
<button>
Checkout
<ArrowSvg />
</button>
And ArrowSvg looks like this (line has class: svg-line):
<svg fill="none" stroke="#000">
<defs>
<marker id="m" overflow="visible">
<path d="M-4,-4L0,0 -4,4" />
</marker>
</defs>
<line x1="0" y1="50%" x2="100%" y2="50%"marker-end="url(#m)" class="svg-line" />
</svg>
When button is hovered, I change the stroke color:
btn:hover > .svg-line {
stroke: blue;
}
It's working well - when I hover the button, the arrow (line and arrow head) turns blue.
But when I display the multiple buttons (more than 1), hovering 1 button affects arrows in other buttons. The arrow head part (which is marker/path in svg?) of all the buttons get affected when hovering the first button.
I am also working with the arrow width, so I need line. I can't make everything with path because I won't be able to expand the arrow width.
Am I missing anything? Why am I seeing this issue?
Wrap it in a native JS Web Component, supported in all modern browsers, to create the <svg>
because every SVG requires a unique marker ID
note: the SVG is created for every instance, no need for a marker at all, use the path by itself
customElements.define("svg-button",class extends HTMLElement{
connectedCallback(){
let id = "id" + (Math.floor(Math.random() * 1e10));
let stroke = this.getAttribute("stroke") || "#000";
this.innerHTML = `
<button>
Checkout
<svg fill="none" stroke="${stroke}" viewBox="0 0 10 10">
<defs>
<marker id="${id}" overflow="visible">
<path d="M-4,-4L0,0 -4,4"/>
</marker>
</defs>
<line x1="0" y1="5" x2="9" y2="5" marker-end="url(#${id})"/>
</svg>
</button>`
}
});
<style>
button:hover svg {
stroke:gold;
}
</style>
<svg-button></svg-button>
<svg-button stroke="red"></svg-button>
<svg-button stroke="green"></svg-button>
<svg-button stroke="blue"></svg-button>
What you are writing looks like .JSX syntax, but the question is lacking a react tag. I will assume it for this answer anyway, but other frameworks using the format will probably work comparably.
All you need is a unique id for the <marker> element. This is easily done if you spell out the <ArrowSvg> as a function. Then, wrap it in a factory function to form a closure over a running number:
const ArrowSvg = (() => {
let id = 0;
return function (props) {
return (
const ref = 'arrowMarker' + ++id;
<svg fill="none" stroke="#000">
<defs>
<marker id=(ref) overflow="visible">
<path d="M-4,-4L0,0 -4,4" />
</marker>
</defs>
<line x1="0" y1="50%" x2="100%" y2="50%"
marker-end=(`url(#${ref})`) class="svg-line" />
</svg>
);
}
})();

Animating SVG Path using CSS instead of <animateMotion>

I am trying to place an arrow on the midpoint of the bezier curve. The solution using <animateMotion> in the question How properly shift arrow head on cubic bezier in SVG to its center , which moves a <path> which is the arrow and freezes it at the middle of the bezier curve, works only in Firefox. As the curve's points keep changing frequently in my case, I didn't want to use marker-mid as it is costly for me to calculate the midpoint of the bezier curve everytime.
<svg viewBox="0 0 500 500">
<g>
<path id="path1" d="M291.698 268.340 C321.698 268.340, 411.904 93.133 441.904 93.133"></path>
<path class="path_arrow" d="M0,0 L6,6 L0,12" transform="translate(-3,-6)">
<animateMotion dur="0s" rotate="auto" fill="freeze" keyTimes="0;1" keyPoints="0.5;0.5">
<mpath xlink:href="#path1"></mpath>
</animateMotion>
</path>
</g>
<g transform="translate(166.698,243.340)">
<circle r="5" class="p1"></circle>
</g>
<g transform="translate(441.904,68.133)" >
<circle r="5" class="p2"></circle>
</g>
</svg>
Is there any way to do this using CSS Animations so as to avoid using <animateMotion> ?
EDIT 1:
The endpoints of the curve here is draggable and so the points of the curve tend to change frequently. The animation is to move the arrow to the center of the curve without calculating the midpoints.
EDIT 2:
Thanks to Kaiido's comment, I added calcMode="linear" and the arrow is now placed on the path as expected. But When I reposition the end point by dragging, the arrow stays in its initial position(as shown) in Chrome but it is expected to move along the parent path. In Firefox this is working fine as before.
You could achieve the same with CSS offset-path, offset-distance and offset-rotate properties:
#path1 {
fill: none;
stroke: black;
}
.path_arrow {
transform: translate( -3px, -6px );
offset-path: path("M220 104C220 144,400 180,400 224");
offset-rotate: auto;
offset-distance: 50%;
}
body { background: white; }
<svg width="500" height="500" >
<path id="path1" d="M 220 104 C 220 144 400 180 400 224"
fill="none" stroke-width="2" stroke="black" />
<path class="path_arrow" d="M0,0 L0,12 L12,6 z" />
</svg>
But their browser support is far lower than the one of SMIL, so I wouldn't recommend it.
Note that I did fix the answer there where they were missing a calcMode="linear" attribute to make Blink browsers happy.
If you need IE support, you may want to try this js implementation which seems to support <animateMotion> and rotate, keeping in mind I didn't test it myself.
Regarding the question's "EDIT 2":
Chrome indeed seems to need an explicit call to update the <mpath>. This can be done by calling the beginElement() method of the <animationMotion> element after each update:
document.querySelector('svg').onmousemove = function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
path1.setAttribute( 'd', `M ${x} ${y} C 220 144 400 180 400 224` );
// Chrome requires an explicit update
document.querySelector('animateMotion').beginElement();
}
<pre style="position: absolute;pointer-events:none">move your mouse to change the path</pre>
<svg width="500" height="500" >
<path id="path1" d="M 220 104 C 220 144 400 180 400 224"
fill="none" stroke-width="2" stroke="black" />
<path d="M0,0 L0,12 L12,6 z" transform="translate(-3,-6)">
<animateMotion dur="0s" rotate="auto" fill="freeze"
keyTimes="0;1" keyPoints="0.5;0.5" calcMode="linear" >
<mpath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path1"/>
</animateMotion>
</path>
</svg>
You have to use webkit animation inside the css. Also i would recommend https://codepen.io/collection/yivpx/. Is an open source project for SVG optimization. I also encourage you to name all your classes within your SVG in order to do cool CSS stuff with animate, like:
animation: kaboom 5s ease alternate infinite;

Using an svg image with inline data uri

I'm pretty sure this isn't possible, but before giving up I wanted to throw it out there.
Here's a fiddle, http://jsfiddle.net/sqszuzep/6/
<svg version="1.1" viewBox="0 0 2 1" width="100%">
<image xlink:href="http://fillmurray.com/600/300" x="0" y="0" height="100%" width="100%"/>
</svg>
<div id="bg" class="bg"></div>
<div id="bg2" class="bg"></div>
<h2>IE10/11</h2>
<div id="bg3" class="bg"></div>
<div id="bg4" class="bg"></div>
js:
// Chrome/Firefox/Safari
var x = encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><circle fill="#000000" stroke="#000000" stroke-width="20" cx="105" cy="105" r="90"></circle> <circle fill="none" stroke="#0000FF" stroke-width="10" cx="300" cy="105" r="90"></circle></svg>');
var y = encodeURIComponent('<svg version="1.1" viewBox="0 0 2 1" width="100%"><image xlink:href="http://fillmurray.com/600/300" x="0" y="0" height="100%" width="100%"/></svg>');
$(function () {
$('#bg').css('background-image', 'url("data:image/svg+xml;utf8,'+ x +'")');
$('#bg2').css('background-image', 'url("data:image/svg+xml;utf8,'+ y +'")');
});
// IE 10/11
// Chrome/Firefox/Safari
var x = btoa('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><circle fill="#000000" stroke="#000000" stroke-width="20" cx="105" cy="105" r="90"></circle> <circle fill="none" stroke="#0000FF" stroke-width="10" cx="300" cy="105" r="90"></circle></svg>');
var y = btoa('<svg version="1.1" viewBox="0 0 2 1" width="100%"><image xlink:href="http://fillmurray.com/600/300" x="0" y="0" height="100%" width="100%"/></svg>');
$(function () {
$('#bg3').css('background-image', 'url("data:image/svg+xml;base64,'+ x +'")');
$('#bg4').css('background-image', 'url("data:image/svg+xml;base64,'+ y +'")');
});
Essentially I was attempting to use a svg image, encode the svg element and use it in css on an element. This works for vectors, but not for images loaded via svg.
I believe this pertains, https://bugs.webkit.org/show_bug.cgi?id=63548 saying that "Images are not allowed to load further resources".
What I see is that no browser supports this feature, or I'm doing something wrong.
It is possible to base64 encode image data and use that, as demonstrated in this fiddle I wrote: http://jsfiddle.net/N2n27/3/. On browsers that support svg filters (non-IE) this is a handy way to do blurring and other filter effects.
Am I missing something here?
There are two issues with the y version.
a) the svg element has no namespaces (it does in the x version) but in the y version it also needs the xlink namespace as the image href attribute is in the xlink namespace.
b) the image in the SVG is external.
A css background is an image itself so any SVG it references cannot load external resources. You therefore need to URI encode the inner image data and then uri encode the outer SVG (thereby doubly encoding the inner image data)
So you need something like this...
var y = encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 2 1" width="100%"><image xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAA....
Which I've completed here for the Firefox/Chrome case

'punch out' transparent path on svg background

I have an svg defining a path drawn on a transparent background -- a pretty basic svg.
I need to convert this svg so that the background is white and the text is transparent and cutout of the white background. I have been playing around with clip-path but to no avail. How is the right way to make this modification?
<svg width="138" xmlns="http://www.w3.org/2000/svg" height="40" viewBox="0 0 138 40">
<g transform="translate(2 30)">
<path d="m6.12,0 5.88,0 0-2.46-.6,0-2.535-9.21-5.46,0-2.535,9.21-.6,0 0,2.46 5.13,0 0-2.46-.9,0 .24-1.2 2.025,0 .255,1.2-.9,0 0,2.46m-.435-8.235 .15,0 .615,2.985-1.38,0 .615-2.985"/>
<path d="m12.3696-9.21 .66,0 0,6.75-.66,0 0,2.46 4.77,0 0-2.46-.66,0 0-9.21-4.11,0 0,2.46"/>
<path d="m17.5552-5.88 .66,0 0,3.42-.66,0 0,2.46 5.28,0 0-2.46-1.17,0 0-3.42 1.17,0 0-2.46-1.17,0c0-.63 .255-.87 1.035-.87 .735,0 1.47,.075 1.47,.075v-2.355c0,0-.78-.39-2.43-.39-2.205,0-3.495,1.035-3.495,3.54h-.69v2.46"/>
<path d="m28.518,0 4.11,0 0-2.46-.66,0 0-2.46c0-2.415-.945-3.63-4.155-3.63-2.385,0-4.155,.525-4.155,.525v2.58c.99-.225 2.295-.435 3.48-.435 1.125,0 1.38,.3 1.38,.87-3.735,0-5.4,1.02-5.4,2.88 0,1.515 1.065,2.34 2.775,2.34 1.44,0 2.25-.75 2.625-1.23v1.02m0-3.42c0,.645-.33,.96-.915,.96-.57,0-.825-.135-.825-.48 0-.48 .675-.57 1.74-.57v.09"/>
<path d="m41.9899-2.685c-.75,0-.93-.285-.93-.885h-3.3v2.97c0,0 1.815,.81 4.695,.81 3.21,0 4.95-1.29 4.95-3.855 0-2.58-1.935-3.375-4.26-4.125-.615-.195-.93-.3-.93-.645 0-.42 .27-.57 .93-.57 .465,0 .825,.18 .825,.75h3.15v-2.685c0,0-1.485-.96-4.38-.96-3.84,0-5.115,1.38-5.115,3.615 0,2.34 1.44,3.135 4.41,4.245 .705,.255 .87,.465 .87,.735 0,.39-.255,.6-.915,.6"/>
<path d="m47.8481-9.21 .66,0 0,6.75-.66,0 0,2.46 4.77,0 0-2.46-.66,0 0-9.21-4.11,0 0,2.46"/>
<path d="m58.2837,0 4.11,0 0-2.46-.66,0 0-2.46c0-2.415-.945-3.63-4.155-3.63-2.385,0-4.155,.525-4.155,.525v2.58c.99-.225 2.295-.435 3.48-.435 1.125,0 1.38,.3 1.38,.87-3.735,0-5.4,1.02-5.4,2.88 0,1.515 1.065,2.34 2.775,2.34 1.44,0 2.25-.75 2.625-1.23v1.02m0-3.42c0,.645-.33,.96-.915,.96-.57,0-.825-.135-.825-.48 0-.48 .675-.57 1.74-.57v.09"/>
<path d="m66.9288-11.67-4.11,0 0,2.46 .66,0 0,6.75-.66,0 0,2.46 4.11,0 0-1.02c.375,.48 1.185,1.23 2.325,1.23 2.07,0 3.075-1.62 3.075-4.38 0-2.76-1.005-4.38-3.075-4.38-1.14,0-1.95,.75-2.325,1.23v-4.35m1.74,8.25c0,.675-.3,.96-.87,.96-.57,0-.87-.285-.87-.96v-1.5c0-.675 .3-.96 .87-.96 .57,0 .87,.285 .87,.96v1.5"/>
<path d="m83.0515-2.46c-.675,0-1.02-.33-1.02-1.11v-4.53c0-.78 .345-1.11 1.02-1.11 .675,0 1.02,.33 1.02,1.11v4.53c0,.78-.345,1.11-1.02,1.11m5.73-3.375c0-4.38-2.235-6.045-5.73-6.045-3.495,0-5.73,1.665-5.73,6.045 0,4.38 2.235,6.045 5.73,6.045 3.495,0 5.73-1.665 5.73-6.045"/>
<path d="m94.4339,0 4.77,0 0-2.46-.66,0 0-2.46c0-2.505-1.02-3.63-2.655-3.63-1.35,0-2.055,.75-2.535,1.23v-1.02h-4.11v2.46h.66v3.42h-.66v2.46h4.77v-2.46h-.66v-2.46c0-.675 .3-.96 .87-.96 .57,0 .87,.285 .87,.96v2.46h-.66v2.46"/>
<path d="m103.732-8.55c-2.775,0-4.53,1.455-4.53,4.38 0,2.925 1.755,4.38 4.53,4.38 2.385,0 3.735-.75 3.735-.75v-2.4c-.72,.24-1.86,.48-3.18,.48-1.05,0-1.425-.36-1.425-1.08v-.09h4.86v-1.08c0-1.89-.915-3.84-3.99-3.84m0,2.67c.57,0 .87,.285 .87,.96v.09h-1.74v-.09c0-.675 .3-.96 .87-.96"/>
</g>
</svg>
Try using a mask instead of a clip path.
<mask id="m"> ... </mask>
Inside the mask, create a white rectangle, followed by (under) your black paths. The brightness of different areas of the mask determines the opacity of the corresponding areas of the element the mask is applied to, with white areas being fully opaque and black areas being fully transparent.
<rect width="100%" height="100%" fill="white" />
<g transform="translate(2 30)"> ... </g>
After the mask, add another rectangle and set its mask attribute to your mask. This rectangle is the white background you wanted, so give it a white fill.
<rect width="100%" height="100%" fill="white" mask="url(#m)" />
This should give you the effect you want. Check out this fiddle: http://jsfiddle.net/9tT6T/

Why is this background image not working for this SVG?

I want to create this triangular/polygonal shape using SVGs and assign it a background image.
<svg class="svg-graphic" width="100%" height="100%" class="svg-graphic" viewBox="0 0 100 100" >
<defs>
<pattern id="image" x="0" y="0" patternUnits="userSpaceOnUse" height="1" width="1">
<image x="0" y="0" xlink:href="http://25.media.tumblr.com/fe6e34ef00254f2bd05451f525b02324/tumblr_mw8osqc77F1qdrz3yo3_500.jpg"></image>
</pattern>
<polygon points="0, 0, 100, 0, 50, 50" fill="url('http://25.media.tumblr.com/fe6e34ef00254f2bd05451f525b02324/tumblr_mw8osqc77F1qdrz3yo3_500.jpg')"/>
</defs>
</svg>
Very similar to this question here:
Happy Chanukkah
The problem is the fill="#url"
you have to give the polygon/path a class and then from that class, assign the background image (that's already defined in the defs):
.imageFill {
fill: url(#image);
}
The fill attribute references the pattern, in your case it's url(#image) (like you'd do in CSS). Repeating the image's URL is pointless there. See the accepted answer in the question you linked to.
Apart from that you must make sure, that the view box spanned by the <pattern> actually matches the shape it shall be applied to. See this updated fiddle: http://jsfiddle.net/boldewyn/k7Rk9/12/

Resources