svg grouped elements jointly hover - css

Is it possible for hover on an svg element cause other elements with the same class to hover too without jQuery? Or do I have to next the two into an outer group?
I have inside an inline svg the following groups:
<g class="class1">
<path....>
<path....>
</g>
<g class="class1">
<path....>
<path....>
</g>
I then have in my CSS:
class1 {
...
}
class1:hover {
...
}

I guess you can't do it directly, but you can achieve it by adding an id to the parent element, no classes needed, like that:
#circles:hover circle{
fill: Wheat;
}
<svg id="circles" width="100" height="260" >
<circle cx="50" cy="50" r="40" stroke="Tomato" stroke-width="4" fill="Tomato" />
<circle cx="50" cy="140" r="40" stroke="Tomato" stroke-width="4" fill="Aquamarine"/>
</svg>

Related

Apply :hover to all children of an svg group

How do I get all the elements of an svg group to change their fill color on hover?
The example below does not work at all. If I use .sgroup circle:hover only the circle under the pointer works not both.
.sgroup:hover {
fill: green;
}
<div>
<svg width="200" height="200">
<g class="sgroup">
<circle cx="50" cy="50" fill="pink" r="10" ></circle>
<circle cx="150" cy="150" fill="purple" r="10" ></circle>
</g>
</svg>
</div>
Please see below. I assume this is what you're looking for?
.sgroup:hover circle {
fill: green;
}
<div>
<svg width="200" height="200">
<g class="sgroup">
<circle cx="50" cy="50" fill="pink" r="10" ></circle>
<circle cx="150" cy="150" fill="purple" r="10" ></circle>
</g>
</svg>
</div>

How to add a CSS transform or filter to an existing inline SVG transform or filter?

Without knowing what the inline SVG transform or filter is, how can I apply extra transforms or filters to the same element using CSS?
Whatever I try, my CSS transform or filter overwrites the inline SVG transform/filter, instead of adding to it.
For example I have in my SVG:
<circle r="50" transform="translate(100,100)">
And then I try to scale this circle using a CSS transform, without removing its initial translate transform:
circle {
transform: initial scale(0.5);
}
However, the original translate is replaced by the scale transform, they are not added.
The same problem applies to filters.
As #ccprog says. Wrap the element in a parent <g> element, and apply your transform or filter to that.
<g>
<circle r="50" transform="translate(100,100)">
</g>
g {
transform: scale(0.5);
}
However, be aware that may give a different result to what you are expecting.
The following two SVGs are not equivalent.
<g transform="scale(0.5)">
<circle r="50" transform="translate(100,100)">
</g>
<g>
<circle r="50" transform="translate(100,100) scale(0.5)">
</g>
You would need to modify the transform you apply to the group to get an equivalent result.
The equivalent of
<g>
<circle r="50" transform="translate(100,100) scale(0.5)">
</g>
is either
<g transform="scale(0.5) translate(100,100)">
<circle r="50" transform="translate(100,100)">
</g>
or
<g transform="translate(50,50) scale(0.5)">
<circle r="50" transform="translate(100,100)" fill="blue"/>
</g>

SVG radius or position with CSS variables

Is it possible to use CSS Variables in SVG to manipulate values like radius or position in the "attribute styles" of an element?
For Example, in the below code, I have added a CSS color variable - --dark-text-clr and a radius variable --radius. When I use the color CSS variable in fill it renders fine - 1st circle, but using the radius variable doesn't render the element - 2nd circle.
:root{
--dark-text-clr: blue;
--radius: 12;
}
<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="100px" viewBox="0 0 300 100">
<circle cx="9" cy="9" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r=9 ></circle>
<circle cx="36" cy="20" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r="var(--radius)" ></circle>
</svg>
Yes, but CSS must have units for non-zero values.
:root{
--dark-text-clr: blue;
--radius: 12px;
}
<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="100px" viewBox="0 0 300 100">
<circle cx="9" cy="9" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r=9 ></circle>
<circle cx="36" cy="20" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r="var(--radius)" ></circle>
</svg>
According to the MDN Docs "Starting with SVG2, r is a Geometry Property meaning this attribute can also be used as a CSS property for circles."
There are three ways to set the radius value
as attribute
<circle ... r=10>
via class and stylesheet
circle {
r: 10px;
}
inline in 'style' attribute
<circle... style="r: 10px;" ></circle>
The last way has the greates presedence. Take a look at this example where all circle elements have r set as attribute, which is overridden by the stylesheet (2nd circle), which is overridden again by the inline style attribute (3rd circle)
(These three ways don't have to be used together, but are only combined to demontrate which one has a higher presedence and overwrites the already set values)
:root {
--dark-text-clr: purple;
--radius: 20px;
}
.circle {
r: 10px;
}
<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="300px" viewBox="0 0 300 300">
<circle cx="10" cy="10" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r=5></circle>
<circle cx="30" cy="30" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r=5 class="circle"></circle>
<circle cx="60" cy="60" fill="var(--dark-text-clr)" mask="url(#moon-mask)" r=5 class="circle" style="r: var(--radius);" ></circle>
</svg>
Setting r with the variable on the attribute seems to be working in firefox, but not in chrome/edge
<circle ... r="var(--radius);" ></circle>
so better set it on the style attribute
<circle ... style="r: var(--radius);" ></circle>

CSS: change opacity of svg path on hover

I would like to change opacity of an inline SVG path from CSS. Here is the code :
canvas { background-color:#777;}
svg { position:absolute; top: 0px;left:0px;}
#circle:hover {opacity: 0.9;}
#square:hover {opacity: 0.9;}
<canvas width="300px" height="300px"></canvas>
<svg height="300" width="300" pointer-events="none" >
<circle id="circle" cx="100" cy="100" r="40" stroke="black" stroke-width="3" fill="red" pointer-events="all" opacity="0.3"/>
<path id="square" d="M150 150 H 250 V 250 H 150 L 150 150" opacity="0.3"/>
</svg>
As you'll notice, it works with the circle, but not with the path. My questions are:
Why isn't it working with the path?
What should I do to apply the hover opacity to the path?
The path inherits pointer-events="none" from its parent.
The circle doesn't because it overrides that parent pointer-events value.
If you want something to respond to mouse events, don't have it set it as pointer-events none.
Remove pointer-events="none" then it will work fine.
Pointing none The element does not react to pointer events. Hence it doesn't work.
See details
canvas { background-color:#777;}
svg { position:absolute; top: 0px;left:0px;}
#circle:hover {opacity: 0.9;}
#square:hover {opacity: 0.9;}
<canvas width="300px" height="300px"></canvas>
<svg height="300" width="300" >
<circle id="circle" cx="100" cy="100" r="40" stroke="black" stroke-width="3" fill="red" pointer-events="all" opacity="0.3"/>
<path id="square" d="M150 150 H 250 V 250 H 150 L 150 150" opacity="0.3"/>
</svg>

Portions of SVG that were offscreen while zoomed in disappear when zooming back out

I'm using css transitions to animate zooming into an SVG. The only problem is that when zooming out, portions of the SVG are missing until the animation is complete and then it all pops in.
(Only tested in Chrome on a Mac so far)
I'm not changing the SVG at all, just zooming in and then back out by setting the a scale transform on a group in the SVG.
How can I make the browser re-render these offscreen elements so that don't pop in like this?
const root = document.getElementById('root')
setTimeout(function() {
root.setAttribute('transform', 'scale(10,10)')
}, 1)
setTimeout(function() {
root.setAttribute('transform', 'scale(1,1)')
}, 4200)
#root {
transition: 4s transform;
}
circle {
stroke: white;
stroke-width: 3px;
}
<svg viewbox="0 0 300 100">
<g id="root">
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
</svg>
As said in comments, this is probably because of some optimizations in the CSS renderer.
This is a Chrome bug (one of the many they have with their CSS paintings optimizations...), and you should let them know about it.
For the time being, have you considered using SMIL instead?
Since you used javascript in your code, I will assume you run this in a browser from some place where script execution is allowed (i/e not in <img> tag), and hence where you will be able to use a polyfill like FakeSmile.
So this will actually offer you a better browser support than through CSS transitions (IIRC IE<11 didn't support CSS transform transitions on svg elements), and moreover than the still experimental SVG2 only mix-up CSS transition of SVGTransformAttribute.
Indeed, only Chrome does support it for now (probably because while some attributes were already CSS transitionable in SVG1.1, transform having a different syntax than its CSS equivalent, the algo should be differents).
Here is what your example would look like in SMIL:
// and if you need JS control
document.onclick = e => {
document.getElementById('zoomin').beginElement();
};
circle {
stroke: white;
stroke-width: 3px;
transform: translateZ(1);
}
<svg viewbox="0 0 300 100">
<g id="root">
<animateTransform attributeName="transform" type="scale" id="zoomin"
from="1 1" to="10 10" dur="4s" begin="1s"/>
<animateTransform attributeName="transform" type="scale" id="zoomout"
from="10 10" to="1 1" dur="4s" begin="zoomin.end"/>
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
<!-- for IE -->
<script xlink:href="https://cdn.rawgit.com/FakeSmile/FakeSmile/master/smil.user.js"></script>
</svg>
How about, transition every circle,
is this ok for you?
I actually dont have an explanation about why it works this way
const circles = document.getElementsByTagName('circle')
setTimeout(function() {
circles[0].setAttribute('transform', 'scale(10,10)');
circles[1].setAttribute('transform', 'scale(10,10)');
circles[2].setAttribute('transform', 'scale(10,10)');
circles[3].setAttribute('transform', 'scale(10,10)');
circles[4].setAttribute('transform', 'scale(10,10)');
}, 1)
setTimeout(function() {
circles[0].setAttribute('transform', 'scale(1,1)');
circles[1].setAttribute('transform', 'scale(1,1)');
circles[2].setAttribute('transform', 'scale(1,1)');
circles[3].setAttribute('transform', 'scale(1,1)');
circles[4].setAttribute('transform', 'scale(1,1)');
}, 4200)
#root {
transition: 4s transform;
}
circle {
stroke: white;
stroke-width: 3px;
transition: 4s transform;
}
<svg viewbox="0 0 300 100">
<g id="root">
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
</svg>
Why not transition the entire svg element?
const root = document.getElementById('root')
setTimeout(function() {
root.setAttribute('transform', 'scale(10,10)')
}, 1)
setTimeout(function() {
root.setAttribute('transform', 'scale(1,1)')
}, 4200)
#root {
transition: 4s transform;
transform-origin: top left;
}
circle {
stroke: white;
stroke-width: 3px;
}
<svg id="root" viewbox="0 0 300 100">
<g>
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
</svg>
The setAttribute need the style attribue. Try this script:
setTimeout(function() {
root.setAttribute('style', 'transform: scale(10,10)')
}, 1)
setTimeout(function() {
root.setAttribute('style', 'transform: scale(1,1)')
}, 4200)

Resources