Changing CSS with SVG <symbols> on hover - css

I'm working on a handful of SVG icons as <symbol> that I want to be able to alter on :hover, :focus, and :active. Specifically, I need to be able to change the color of specific paths within an SVG. Normally, with any other element, I can do something like this
.parent .child {
fill: blue;
}
.parent:hover .child {
fill: red;
}
However, when applying this to my SVG symbols, it just doesn't work. Not many discussions from what I can find in the last couple of years on problems changing css with shadow-DOM stuff like SVG symbols, but the just using CurrentColor won't work for me because I need to do it with two colors.
I have a CodePen up with my scenario here
It seems like this should be straightforward but I've been tied up in knots over it for awhile. Anybody have any thoughts that might help?
Thanks in advance!

A similar question was asked here: SVG USE element and :hover style
The problem is that elements that are referenced by use aren't really part of the DOM, so you can't access them with CSS. However, some properties that are set on the use element itself (like fill or color) will trickle down to the referenced elements.
So, first of all, our CSS can only change the use element. Then we need to use some of the tricks in the article #Matheus mentioned to handle the different colors. This means changing the SVG code for the symbols a little. If the icons only need two colors per state, we can use fill + color/currentColor:
<g id="icon_alarm">
<g class="transflip">
<path class="transflip" d="M8 1.3c-3.6 ....." />
<path class="transflip" d="M1 5.1C1.7 3....." />
</g>
<g class="fillflip">
<path class="fillflip" fill="currentColor" d="M11.7 8......" />
</g>
...
.icon use {
fill: transparent;
color: #007fa3;
}
.icon:hover use {
fill: #007fa3;
color: transparent;
}
If the icons need more colors per state, we can use CSS variables and add style="fill: var(--my-special-color)" styles in the SVG (also a trick from the mentioned article), but that doesn't work in some browsers, e.g. IE/Edge.
Updated pen using both techniques: https://codepen.io/Sphinxxxx/pen/GMjgxJ?editors=1100

html:hover .transflip {
fill: red;
}
This kind of hover works. I noticed you used same class for g and path. I'd try not to.
I'd also try to wrap each svg inside a div with width and height, and then use :focus etc. on that div since svg elements seem not to recognize pseudoselectors on your pen.

Related

Why does SVG allow me to use CSS var without closing bracket?

Please see this example on codepen
I am using a CSS var as the fill prop for the SVG, but I've purposefully left off the closing parenthesis. The colour still displays as intended. Why would this be the case?
NB: When using the var() in the stylesheet, it's not as forgiving (see the example)
<body>
<style>
:root {
--main-color: #06c;
}
/* If you miss the closing parenthesis from var() the styling breaks, which makes sense*/
h1 {
color: var(--main-color);
}
</style>
<h1>Here is my coloured heading</h1>
<!-- the closing parenthesis from var() is missing, but the colour is still applied. That's good for when you make a mistake, but I dont get why -->
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="var(--main-color" />
Sorry, your browser does not support inline SVG.
</svg>
</body>
It does also work in CSS but you need to remove the last } and have nothing after it:
:root {
--main-color: #06c;
}
h1 {
color: var(--main-color
<h1>Here is my coloured heading</h1>
Sometimes the browser is clever enough "to understand" some broken stuff but you should never rely on such behavior. People use such trick to shorten the code when doing some online challenges for fun or they simply do it by mistake.

Reset svg fill in css

I want to have all my svgs to have the same plain color. So I use
svg *{
fill: #ccc;
}
But I want to get default fills on :hover. How can I disable the fills and get defaults back?
You can do this using :not() and effectively style "not hovered".
svg *:not(:hover){
fill: #ccc;
}
The above might work, here's a quick CodePen that you can play with: http://codepen.io/anon/pen/rrqyAx
You can learn more on the Mozilla Dveloper Network entry for :not()
Alternatively (I was curious) you could use fill:inherit - which is just as valid. In this case, the color used will be inherited from the fill value of the parent svg, which can be set in css also.
svg *:hover{
fill : inherit;
}
I've added an svg styled in this manner to the CodePen.

How to use SVG symbols in CSS?

Using symbols in SVG formats seems like a good idea to me, so you can only load a single SVG file, and use it as a sort of spritemap.
It feels wrong to me though, to directly include the <svg></svg> tags in my html for icons, since they are presentation only and should be added in my CSS.
Is there a way to add a symbol from an svg in an :after pseudo element in my CSS?
You can refer to fragments in CSS this way (fragments must have an ID to identified):
.element {
background-image: url('vector.svg#fragment');
}
You can also show a specific area by clipping it with viewBox:
.element {
background-image: url('vector.svg#fragment(viewBox(0,0,25,25))');
}
Here you can read more about this methods:
http://www.broken-links.com/2012/08/14/better-svg-sprites-with-fragment-identifiers/
Another method could be to create all your symbols in a icon font.
As Blazemonger mentioned in comment you can give it inside the content of an afterelement.
But if you are setting it as a background of an after you would have more control over it regarding its size,position and any other properties a background image can have
html
<div id = "mydiv"><div>
css
#mydiv{
width:500px;
height:100px;
background:tomato;
}
#mydiv:after{
content:"After of #mydiv";
width:500px;
height:100px;
background:url('http://xn--dahlstrm-t4a.net/tmp/sharp-icons/svg-icon.svg') no-repeat;
position:absolute;
top:200px;
border:solid 1px tomato;
}
You can save the svg as a separate file and give it as url for background
DEMO
Read more on ways of adding SVG to page here

Can I have multiple :before pseudo-elements for the same element?

Is it possible to have multiple :before pseudos for the same element?
.circle:before {
content: "\25CF";
font-size: 19px;
}
.now:before{
content: "Now";
font-size: 19px;
color: black;
}
I am trying to apply the above styles to the same element using jQuery, but only the most recent one is applied, never both of them.
In CSS2.1, an element can only have at most one of any kind of pseudo-element at any time. (This means an element can have both a :before and an :after pseudo-element — it just cannot have more than one of each kind.)
As a result, when you have multiple :before rules matching the same element, they will all cascade and apply to a single :before pseudo-element, as with a normal element. In your example, the end result looks like this:
.circle.now:before {
content: "Now";
font-size: 19px;
color: black;
}
As you can see, only the content declaration that has highest precedence (as mentioned, the one that comes last) will take effect — the rest of the declarations are discarded, as is the case with any other CSS property.
This behavior is described in the Selectors section of CSS2.1:
Pseudo-elements behave just like real elements in CSS with the exceptions described below and elsewhere.
This implies that selectors with pseudo-elements work just like selectors for normal elements. It also means the cascade should work the same way. Strangely, CSS2.1 appears to be the only reference; neither css3-selectors nor css3-cascade mention this at all, and it remains to be seen whether it will be clarified in a future specification.
If an element can match more than one selector with the same pseudo-element, and you want all of them to apply somehow, you will need to create additional CSS rules with combined selectors so that you can specify exactly what the browser should do in those cases. I can't provide a complete example including the content property here, since it's not clear for instance whether the symbol or the text should come first. But the selector you need for this combined rule is either .circle.now:before or .now.circle:before — whichever selector you choose is personal preference as both selectors are equivalent, it's only the value of the content property that you will need to define yourself.
If you still need a concrete example, see my answer to this similar question.
The legacy css3-content specification contains a section on inserting multiple ::before and ::after pseudo-elements using a notation that's compatible with the CSS2.1 cascade, but note that that particular document is obsolete — it hasn't been updated since 2003, and no one has implemented that feature in the past decade. The good news is that the abandoned document is actively undergoing a rewrite in the guise of css-content-3 and css-pseudo-4. The bad news is that the multiple pseudo-elements feature is nowhere to be found in either specification, presumably owing, again, to lack of implementer interest.
If your main element has some child elements or text, you could make use of it.
Position your main element relative (or absolute/fixed) and use both :before and :after positioned absolute (in my situation it had to be absolute, don't know about your's).
Now if you want one more pseudo-element, attach an absolute :before to one of the main element's children (if you have only text, put it in a span, now you have an element), which is not relative/absolute/fixed.
This element will start acting like his owner is your main element.
HTML
<div class="circle">
<span>Some text</span>
</div>
CSS
.circle {
position: relative; /* or absolute/fixed */
}
.circle:before {
position: absolute;
content: "";
/* more styles: width, height, etc */
}
.circle:after {
position: absolute;
content: "";
/* more styles: width, height, etc */
}
.circle span {
/* not relative/absolute/fixed */
}
.circle span:before {
position: absolute;
content: "";
/* more styles: width, height, etc */
}
I've resolved this using:
.element:before {
font-family: "Font Awesome 5 Free" , "CircularStd";
content: "\f017" " Date";
}
Using the font family "font awesome 5 free" for the icon, and after, We have to specify the font that we are using again because if we doesn't do this, navigator will use the default font (times new roman or something like this).
You can also use an image/icon plus text in the content field
e.g.
p.album-title::after {
content: url('https://...camera-icon-blue.png') ' View >';
display: block;
...;
}
In ::after css set content:'any text' and add backgroun-image with svg text from external svg file url(anySvgText.svg) or inline svg code url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="30" width="200"><text x="0" y="15" fill="black" style="font-family: tahoma;">any second text</text></svg>')
Also you can use only svg instead content value. but you must set empty string (content: '') to display a ::after style
.circle:before {
content: "\25CF";
font-size: 19px;
color: red;
width: 200px;
display: block;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="30" width="200"><text x="0" y="15" fill="black" style="font-family: tahoma;">Now</text></svg>');
background-position-x: 15px;
}
<div class="circle"></div>

How do I make a "title" (tooltip, pop-up, alt-text) in a CSS to be used with SVG, without JavaScript

It is pretty easy to create a pop-up with the "title" attribute in SVG. But how do you achieve the same effect with CSS.
This is for an illustration I'm making for Wikimedia and I strive to have most things other wikipedians would like to correct within an embedded stylesheet, with style properties grouped by real-life subject. Most wikipedia editors are computer illiterates, you can't expected them to hunt through a lot of code to make their modifications and if they have to change anything but simple CSS-values within the code, then you probably get a nasty mess out of it. If I make this to hard for their poor brains, then sooner or later someone will make the file unmaintainable by using Inkscape, Illustrator or some other equally horrible vector editor.
I could use Javascript, but that would make the illustration less accessible and make the code even more confusing for people who need to edit it. Also, some SVG-files using Javascript have been removed from Wikimedia because of that, but I'm not sure if that is an absolute rule.
I just discovered when I validated my file that the title-attribute in SVG is not allowed where I have been using it, but it works with most browsers. According to the standard you should use the title-element, but that doesn't seem to work with any browsers. Also, according to the standard, I could give a title-attribute to a style-element, but that doesn't seem to work with any browser and it would require me to use 180 style-elements instead of just one (the code is generated by a script, so it isn't a problem for me to create them, it is just that it would make the code much larger and harder to understand).
The two "answers" I've gotten thus far have not been for the question I asked, and is not even remotely useful.
I don't care if the image is editable in Inkscape. Inkscape is a good tool for creating bitmap images (even if you have to run them through some other program to get better compression afterwards). Inkscape sucks at creating SVG files (or any vector based image files intended for an audience, the vector based image files Inkscape creates is only really usefull with Inkscape), Inkscape is a really, really, really poor tool to choose for creating SVG images intended as an end product; Sodipodi was a good tool for that, but Inkscape is not Sodipodi, only based on Sodipodi (unfortunatly, the old C-version of Sodipodi don't run well on modern computer systems and the C++ version was never finished). I want to make my image easy to edit with a text editor, so that no Wikipedian is tempted to use Inkscape and make the file unmaintanable (and in the process increase the size from slightly less then 2 MB to more then 60 MB, if you save it in Inkscape as a "standard" SVG image (which not always give a standard complient file), Inkscapes own svg-based file format makes even larger files).
I don't think wikipedia allow javascript within SVG-images, so Javascript is out of the question.
I already know how to create tooltips with javascript and two different methods to create them in SVG (but not in CSS embedded in SVG). The reason, in addition to the one already mentioned in the original question, that I don't want to use SVG code for the tooltips is that no web-browser support the standard, but SVG-viewers do (but not the non-standard tooltips that work with browsers), so someone who would manually make corrections to a tooltip in the file (with a text editor), would have to change the text in two different places, most likely, with time, resulting in different tooltips when viewed in different browsers/viewers.
I often resort to using the (invalid) title="" attribute on SVG elements as it's so simple and seems to work in most browsers. I understand your requirement is that it is standards compliant and that people can edit the graphic in InkScape transparently and without breaking the tooltips - for this you will have to use some JavaScript. Here's a quick example I hope you'll find helpful, it will attach the mouseover event to each <title> element's immediate parent and load the text content in a tooltip displayed next to the cursor. This while retaining InkScape compatibility and the ability to use InkScape's "Object Properties" dialog box to set the tooltip text.
Given the following example SVG:
<svg xmlns="http://www.w3.org/2000/svg">
<title>Main Title</title>
<g id="group1">
<title>Title One</title>
<circle cx="120" cy="60" r="40" fill="#993333" />
</g>
<g id="group2">
<title>Title Two</title>
<circle cx="60" cy="60" r="40" fill="#339933" />
<g id="group3">
<circle cx="180" cy="60" r="40" fill="#333399" />
<title>Title Three</title>
</g>
</g>
</svg>
First add a dummy tooltip element:
<g id="toolTip" transform="translate(0,0)">
<rect width="150" height="25" />
<text x="5" y="18"> </text>
</g>
Give it some styling:
<style>
g#toolTip {
visibility: hidden;
}
g#toolTip rect {
fill: #FFCC00;
}
g#toolTip text {
font-size: 16px;
}
</style>
Then insert a sprinkling of JS:
<script>
<![CDATA[
var toolTip = document.getElementById('toolTip');
var titles = document.getElementsByTagName('title');
for (var i = 0; i < titles.length; i++) {
titles.item(i).parentNode.addEventListener('mouseover', function(e) {
showTip(this,xy(e));
}, true);
titles.item(i).parentNode.addEventListener('mouseout', function() {
hideTip();
}, true);
}
function showTip(element,pos) {
var text = element.getElementsByTagName('title')[0].childNodes[0].nodeValue;
toolTip.getElementsByTagName('text')[0].childNodes[0].nodeValue = text;
toolTip.setAttribute('transform', 'translate(' + pos + ')');
toolTip.style.visibility = 'visible';
}
function hideTip() {
toolTip.style.visibility = 'hidden';
}
function xy(e) {
if (!e) var e = window.event;
if (e.pageX || e.pageY) {
return [e.pageX,e.pageY]
} else if (e.clientX || e.clientY) {
return [e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,e.clientY + document.body.scrollTop + document.documentElement.scrollTop];
}
return [0,0]
}
]]>
</script>
Caveats: The JS block needs to go at the bottom of the SVG file - and if you use the XML editor in InkScape (rare) it will mess up the script (this may be solved by moving to an external JS file and adding some DOM ready detection).
The approach also expects that a) all <title> elements should be rendered as tooltips and b) that there is only ever one <title> element per parent (oddly, the SVG spec allows multiple) - here I have hard coded it to simply use the first <title> child element encountered, ignoring any subsequent ones.
Note: the xy(e) function is only needed to get round browser inconsistencies when it comes to reporting the cursor position - you may not need it, or you may prefer to roll your own.
Bonus: This could easily be extended to make use of the <desc> element as well, should you need more elaborate tooltips with longer bits of text.
As far as i can tell you can't.
The SVG element seems to behave oddly.
I thought you could apply a similar concept as this using CSS3:
<style type="text/css">
acronym {
position: relative;
}
acronym:after {
content: attr(title);
position: absolute;
position: absolute;
left: 100%;
margin-left: -10px;
top: -0.5em;
z-index: 2;
padding: 0.25em;
width: 200px;
background-color: #f1f1f1;
text-align: center;
display: none;
}
acronym:hover:after {
display: block;
}
</style>
<acronym title="Keep It Simple Stupid">KISS</acronym>
But the :after Selector doesn't seem to work on the SVG element
I think your best bet is a jQuery script which runs on $(document).ready() and does all the heavy lifting for you.
This article: http://codepen.io/recursiev/pen/zpJxs
demonstrates several types of tooltip with SVG.
Including what you asked for : SVG+CSS.
Basically:
<g class="tooltip css" transform="translate(200,50)">
<rect x="-3em" y="-45" width="6em" height="1.25em"/>
<text y="-45" dy="1em" text-anchor="middle" fill="LightSeaGreen">
SVG/CSS Tip</text>
</g>
With some appropriate CSS as well:
.tooltip {
pointer-events:none; /*let mouse events pass through*/
opacity:0;
transition: opacity 0.3s;
text-shadow:1px 1px 0px gray;
}
g.tooltip:not(.css) {
fill:currentColor;
}
g.tooltip rect {
fill: lightblue;
stroke: gray;
}
This article: http://www.scientificpsychic.com/etc/css-mouseover.html
demonstrates CSS only tooltips.
basically:
<span class="dropt" title="Title for the pop-up">Hot Zone Text
<span style="width:500px;">Pop-up text</span>
</span>
with this CSS:
span.dropt {border-bottom: thin dotted; background: #ffeedd;}
span.dropt:hover {text-decoration: none; background: #ffffff; z-index: 6; }
span.dropt span {position: absolute; left: -9999px;
margin: 20px 0 0 0px; padding: 3px 3px 3px 3px;
border-style:solid; border-color:black; border-width:1px; z-index: 6;}
span.dropt:hover span {left: 2%; background: #ffffff;}
span.dropt span {position: absolute; left: -9999px;
margin: 4px 0 0 0px; padding: 3px 3px 3px 3px;
border-style:solid; border-color:black; border-width:1px;}
span.dropt:hover span {margin: 20px 0 0 170px; background: #ffffff; z-index:6;}
Between these, you should be able to get exactly what you want.
UPDATE: This works fine for me in Chrome v44, but not in Internet Explorer v9.
IE just gives me the popup's title, not the popup itself.

Resources