SVG USE element and :hover style

SasQ picture SasQ · Dec 2, 2012 · Viewed 20.1k times · Source

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?

Answer

Thomas W picture Thomas W · Dec 2, 2012

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.