SVG USE element and :hover style

21,911

Solution 1

You can not address an element that is referenced via use. The specs say:

For user agents that support Styling with CSS, the conceptual deep cloning of the referenced element into a non-exposed DOM tree also copies any property values resulting from the CSS cascade ([CSS2], chapter 6) on the referenced element and its contents. CSS2 selectors can be applied to the original (i.e., referenced) elements because they are part of the formal document structure. CSS2 selectors cannot be applied to the (conceptually) cloned DOM tree because its contents are not part of the formal document structure.

Nevertheless, Firefox supports addressing "virtual" elements the are included via a use wormhole. All other browsers don't.

What more browser do support is changing filling or stroking color if you give the referenced element a fill/stroke value of currentColor and then you change the color property of the <use> element on hover. Like so:

<svg version="1.1" width="640" height="480" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

  <style type="text/css">
    #p0 {fill:currentColor}
    #use1:hover {color:green}
    #use2:hover {color:red}
    #use3:hover {color:blue}
  </style>

  <defs>
    <polygon id="p0" points="100,0 50,86.6 -50,86.6 -100,0 -50,-86.6 50,-86.6" class="active" />
  </defs>

  <g transform="translate(70,100)">
    <use xlink:href="#p0" transform="translate(40,0)" id="use1" />
    <use xlink:href="#p0" transform="translate(250,0)" id="use2" />
    <use xlink:href="#p0" transform="translate(460,0)" id="use3" />
  </g>
</svg>

This is supported by all major browsers (FF, Chrome, IE, Safari). Only Opera doesn't seem to like it. The disadvantage is of course, with this method you can only change one color.

So, a different method would possible be to use filters, if it's only about changing color. For example using <feColorMatrix>, you can transform one color to another using a color matrix, like this:

<svg version="1.1" width="640" height="480" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

  <style type="text/css">
    #p0 {fill: currentColor}
    #use1:hover {filter: url(#filter1)}
    #use2:hover {filter: url(#filter2)}
    #use3:hover {filter: url(#filter3)}
  </style>

  <defs>
    <g id="p0">
      <polygon points="100,0 50,86.6 -50,86.6 -100,0 -50,-86.6 50,-86.6" fill="red" />
      <rect width="50" height="70" fill="green" />
      <circle cx="-20" cy="-30" r="30" fill="blue" />
    </g>
  </defs>

  <filter id="filter1">
    <feColorMatrix type="matrix" in="SourceGraphic" values="0 1 0 0 0 
                     1 0 0 0 0 
                     0 0 1 0 0 
                     0 0 0 1 0" />
  </filter>
  <filter id="filter2">
    <feColorMatrix type="matrix" in="SourceGraphic" values="0 0 1 0 0 
                     1 0 0 0 0 
                     0 1 0 0 0 
                     0 0 0 1 0" />
  </filter>
  <filter id="filter3">
    <feColorMatrix type="matrix" in="SourceGraphic" values="0 1 0 0 0 
                     0 0 1 0 0 
                     1 0 0 0 0 
                     0 0 0 1 0" />
  </filter>

  <g transform="translate(70,100)">
    <use xlink:href="#p0" transform="translate(40,0)" id="use1" />
    <use xlink:href="#p0" transform="translate(250,0)" id="use2" />
    <use xlink:href="#p0" transform="translate(460,0)" id="use3" />
  </g>
</svg>

Still no luck with Opera, though, and this time I wasn't happy with IE9 and Safari either. But I believe it should be possible with Opera and Safari, only I did something not 100% correctly.

Solution 2

This appears to be according to spec:

For user agents that support Styling with CSS, the conceptual deep cloning of the referenced element into a non-exposed DOM tree also copies any property values resulting from the CSS cascade ([CSS2], chapter 6) on the referenced element and its contents. CSS2 selectors can be applied to the original (i.e., referenced) elements because they are part of the formal document structure. CSS2 selectors cannot be applied to the (conceptually) cloned DOM tree because its contents are not part of the formal document structure.

I tried a few workarounds with use:hover and/or the [] attribute selector syntax and had little luck but there may be a solution there.

Solution 3

Maybe this can help: https://codepen.io/AmeliaBR/post/customizable-svg-icons-css-variables

    <svg  xmlns="http://www.w3.org/2000/svg" 
     viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"
     class="raw">
  <g id="palette">
  <path d="M5,90
           C5,45 40,-5 75,10 
           C110,25 80,95 60,95
           C50,95 50,75 30,75
           C20,75 17,95 12,95
           C10,95 5,95 5,90 Z"/>
    <g  style="fill:currentColor;">
  <path d="M30,50 
           c-10,0 -20,20 -5,15
           s15,-15 5,-15z
           "/>
  <path  d="M45,30 
           c-10,0 -15,15 -5,15
           s10,-15 5,-15z
           "/>
  <path  d="M70,20 
           c-10,0 -20,15 -5,15
           s10,-15 5,-15z
           "/>
  <path  d="M75,45 
           c-10,0 -20,15 -5,15
           s15,-15 5,-15z
           "/>
  <path  d="M65,65 
           c-10,0 -15,25 -5,20
           s10,-20 5,-20z"/>
    </g>
  </g>
</svg>
<svg class="icon-style-A">
    <use xlink:href="#palette"/>
</svg>

<svg class="icon-style-B">
    <use xlink:href="#palette"/>
</svg>

<svg class="icon-style-C">
    <use xlink:href="#palette"/>
</svg>

    svg {
  display:inline-block;
  height:100px; 
  width:100px;
  margin:10px;
  border:1px solid;
  background:#eee;
}
svg.raw {
/* Default styles for the initial SVG.
 * Because they are defined on the <svg>,
 * not the individual graphics elements, 
 * they will NOT be inherited by the <use> references.
 */
  fill:rgba(255,250,220,0.4);
  stroke: rgba(0,0,0,0.7);
  stroke-width:2;
}
svg.icon-style-A {
/* Set the fill, stroke, and color properties to be 
   inherited by the <use> element:
 */
  fill:burlywood;
  color:blueviolet;
  stroke:#222; 
  stroke-width:0.5px;
}
svg.icon-style-B {
/* Set the color properties:
 */
  fill:blanchedalmond;
  color:lavender;
  stroke:white;
  stroke-width:1px;
/* set some icon styles on the <svg> itself: */
  background:aliceblue;
  border-radius:20%;
  border:none;
  box-shadow:royalblue 0 0 2px;
}
svg.icon-style-C {
/* Set the color properties:
 */
  fill:beige;
  color:green;
  stroke:#aaa; 
  stroke-width:1.5px;
/* icon styles for the <svg> itself: */
  background:#222;
  border-radius:10%;
  border:solid gray;
}

Not very flexible but it worked for my project.

Share:
21,911

Related videos on Youtube

SasQ
Author by

SasQ

I used to post some good answers on this site, answers that earn upvotes to this day. But the way Stack Overflow works these days no longer seems to be about sharing knowledge, getting answers, and welcoming community. Now your answers must be "politically correct" and complying to the highest nazi standards of those who didn't even ask for them, and who claim to know better than the original poster what's good for them. Often they even edited my answers and corrupted them from their original intended meaning, because of what they thought is a better answer. (Hey, if you think of a better answer, why don't you write your own? :q ) Same goes with questions: most of them are claimed as "dumb", "off topic", "duplicate", or whatever lame excuse they can come up with to close/delete it. Especially when they can't answer the question themselves immediately with a canned response copy-pasted from Wikipedia. I don't like it that Stack Overflow has become just one huge middle-fingered RTFM / SEO link farm for Wikipedia, and I don't want to participate in it anymore. That's not the type of "community" I want to be part of. I won't delete my old answers (and most likely they wouldn't allow me to do it anyway – they would revert them), but don't expect any more answers from me. I'm done with this site.

Updated on September 21, 2020

Comments

  • SasQ
    SasQ over 3 years

    I'm trying to use CSS :hover pseudoclass to style SVG elements embeded from <defs> by <use> tag, but it doesn't seem to work :-/ Here's my code:

    <!DOCTYPE HTML>
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
    <head>
     <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8"/>
     <style type="text/css" media="screen">
            .active { fill: #0BE; }
            .active:hover { opacity: 0.8; stroke: #F0F; stroke-width: 4px; }
            .active2 #p2 { fill: #0BE; }
            .active2:hover #p2 { opacity: 0.8; stroke: #F0F; stroke-width: 4px; }
            #p2:hover { opacity: 0.8; stroke: #F0F; stroke-width: 4px; }
        </style>
    </head>
    <body>
    
    
    <svg version="1.1" width="640" height="480"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
    
     <defs>
      <polygon id="p0" points="100,0 50,86.6 -50,86.6 -100,0 -50,-86.6 50,-86.6" class="active"/>
      <g id="gr1">
          <polygon id="p1" points="130,0 50,86.6 -50,86.6 -100,0 -50,-86.6 50,-86.6"/>
          <polygon id="p2" points="100,0 50,86.6 -50,86.6 -100,0 -50,-86.6 50,-86.6" class="active"/>
      </g>
     </defs>
    
     <g transform="translate(70,100)">
        <use xlink:href="#p0" transform="translate(40,0)"/>
        <use xlink:href="#p0" transform="translate(250,0)"/>
        <use xlink:href="#p0" transform="translate(460,0)" class="active" />
     </g>
     <g transform="translate(100,300)">
        <polygon id="style" points="110,0 50,86.6 -50,86.6 -100,0 -50,-86.6 50,-86.6" class="foo"/>
        <use xlink:href="#gr1" transform="translate( 350,2)" class="active2"/>
     </g>
    
    </svg>
    
    </body>
    </html>
    

    I want it to work in a way that when user places the mouse pointer over the embedded element, its inner element with class "active" will change its style. It works when I embed one shape from the <defs> directly and apply the CSS class to the <use> which embeds it. But it doesn't work for any classes or ID's inside the group embedded through <use>.

    How to fix it?

    Or maybe there's a better way to do it?

    I need to change only this particular part inside the embedded object when the user hovers it, not the whole group. It's because different parts of this group would have different styles applied, and they need to change differently when hovered by the mouse.

    Edit: What I want to get

    What I want to get is a way to embed one "library object" from <defs> into many different places in my SVG document. Some parts of this object need to be styled with custom colors from the CSS, because I need easy customization of those colors without changing the library object's code.

    And then I need to signal the user when the mouse pointer is above such "active" object by styling its parts differently: some bright outlines here and there to show the shape of the clickable areas when the mouse pointer is over them.

    Unfortunately, I cannot apply the style to the <use> element's subelements, because they're not subelements of <use> in the DOM (as others already mentioned). I can apply some styles to the elements inside the <defs> section, because they're in the DOM and they're addressable with CSS selectors, but they cannot be hovered, because they're invisible, so applying :hover pseudoclass to them doesn't work. And it doesn't work either if this class is applied to the <use>, because then I cannot sub-select the proper sub elements (they're not sub-elements of <use>). So I don't have any hook to apply those :hover pseudoclass to.

    Maybe there's some other solution to my problem?

  • Thomas W
    Thomas W over 11 years
    Sorry, didn't recognize your answer while writing my answer. Quoted the same specs passage...
  • SasQ
    SasQ over 11 years
    You're right. It doesn't work in Opera. I've updated my question a little bit to describe further what I want to get.
  • svachalek
    svachalek over 11 years
    NP. I didn't think of currentColor which is a good partial solution.
  • AmeliaBR
    AmeliaBR over 9 years
    For completeness (since I've just redirected a duplicate here): In addition to using currentColor, you can also use inherit to force fill or stroke values to use the equivalent value from the <use> element. This can give you greater flexibility: you can modify a main fill color, a stroke color, and a contrast color (using currentColor). Once CSS variables are widely implemented, they will enable complete customization.