1

Question: In the example below is there a way using CSS to target the fill color of the polygon during a hover state? I need to change it from fill:rgb(0, 0, 0) to fill:rgb(255, 255, 255).

Background: I know that I can make a second icon with a different fill color and store it in root but that seems wasteful. In addition I can include the SVG as an image inside my HTML and target the polygon. But in my case I need to use the SVG as a background image. With those restrictions is there a way to target the fill inside the polygon?

Example:

: root {
    --icon-arrowDown: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60"><polygon style="fill:rgb(0, 0, 0)" points="50 60 0 0 100 0 50 60"/></svg>');
}

.myIcon {
  width: 2rem;
  height: 2rem;
  background-image: var(--icon-arrowDown);
}

.myIcon:hover {
  background-image: var(--icon-arrowDown); // ?
}
myNewAccount
  • 578
  • 5
  • 17
  • Maybe you’ll find some interesting ideas here: https://stackoverflow.com/a/65147574/1221208 – Diego D May 08 '22 at 18:49
  • You could have `fill=currentColor` and in CSS, change the `color` property on hover? – Sheraff May 08 '22 at 18:59
  • 3
    Adding your SVG directly to the HTML is the only way you can make the page's stylesheet reach into the SVG _at all_. Embedding SVG as `img` or a background image, makes that impossible to begin with. https://css-tricks.com/using-svg/#aa-the-problem-with-both-img-and-background-image – CBroe May 09 '22 at 12:12
  • @CBroe If you make that into an answer I'll select it. I was curious if there was a selecting ability I wasn't aware of but you're right. – myNewAccount May 09 '22 at 21:51

2 Answers2

1

If you can't append/insert svg elements you might use mask-image instead (still needs browser-prefixes!).

The actual fill color is set by background-color property:

.myIcon {
  -webkit-mask-image: var(--icon-arrowDownUrl);
          mask-image: var(--icon-arrowDownUrl);
  background-color: red;
}

Example using mask-image

:root {
  --icon-arrowDownUrl: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpolygon points='50 60 0 0 100 0 50 60'/%3E%3C/svg%3E");
}

.myIcon {
  width: 2rem;
  height: 2rem;
  -webkit-mask-image: var(--icon-arrowDownUrl);
  mask-image: var(--icon-arrowDownUrl);
  -webkit-mask-size: contain;
  mask-size: contain;
  background-color: red;
  background-repeat: no-repeat;
  transition: 0.3s;
}

.myIcon:hover {
  background-color: green;
}


/* select example */

.select-wrp {
  width: 25%;
  margin-bottom: 2em;
}

.select-wrp * {
  display: block;
  width: 100%;
  font-size: inherit;
}

.select-wrp {
  width: 25%;
  margin-bottom: 2em;
  position: relative;
}

.select-wrp * {
  display: block;
  width: 100%;
  font-size: inherit;
}

.select-custom-icon {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

.select-wrp:after {
  content: "";
  display: block;
  width: 1em;
  height: 1em;
  -webkit-mask-image: var(--icon-arrowDownUrl);
  mask-image: var(--icon-arrowDownUrl);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: 100% 50%;
  mask-position: 100% 50%;
  position: absolute;
  right: 0.5em;
  top: 25%;
  z-index: 10;
  background-color: red;
  pointer-events: none;
}

.select-wrp:hover:after {
  background-color: green;
}
<div class="myIcon"></div>

<div class="select-wrp">
  <select class="select-custom-icon">
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
    <option>Option 4</option>
    <option>Option 5</option>
    <option>Option 6</option>
  </select>
</div>

Common pitfalls:
Use an additional element (e.g a pseudo element with a css content) if you need to add icons to parent tags like <select>
otherwise you'll mask the whole parent element (e.g. the select field would be invisible). See example snippet.

Inlined svg – bloated code?
As @CBroe already mentioned: appending your icons as reusable assets is certainly the most versatile and powerful approach when it comes to css styling and dynamic content/shape manipulations (e.g via js).

But there are also 'semi-stylable' approaches:
Most notably, you can also add icons to your html via <use> elements referenced from an external file like so:

<svg class="myIcon" viewBox='0 0 100 100'>
  <use href="icons.svg#arrowDown" />
</svg>

Granted your svg source is stored on the same domain!

Admittedly, this concept is not at all on par with inlined svg i.e. externally referenced <use> elements are lacking some styling abilities (or at least not supported by a vast majority of browsers) like:

  • filters
  • clip-paths/masks
  • patterns
  • ... and probably other features

Example: external <use> href (emulated):

<!--emulate external svg file content -->
const pseudoExtSvgs = document.querySelectorAll('.pseudoExternal');
showPseudoExternal(pseudoExtSvgs);

function showPseudoExternal(els){
    els.forEach(function(item, i){  
        let type = item.nodeName.toLowerCase();
        let svgEl = type=='img' ? item : item.querySelector('use');
        let svgSrc = type=='img' ? item.getAttribute('src') : item.querySelector('use').getAttribute('href') ;
        let svgId = svgSrc.split('.svg')[0];
        let svgTarget =  svgSrc.indexOf('#')!==-1 ? '#'+svgSrc.split('#').pop() : '';
        let svg = document.querySelector('#'+svgId);
        
        if(svg){
            if(type=='img'){
                let dataUrl = svg2DataUrl(svg)+svgTarget;
                svgEl.src = dataUrl;
            }
            if(type=='svg'){
                svgEl.setAttribute('href', svgTarget);
            }
        }
    });
}
.myIcon {
  width: 2rem;
  height: 2rem;
  transition: 0.3s;
  color: red;
}

.myIcon:hover,
.select-wrp:hover .myIcon {
  color: green;
}


/* select example */
.select-wrp {
  width: 25%;
  position: relative;
}

select {
  width: 100%;
  display: block;
  appearance: none;
}

.select-wrp .myIcon {
  display: block;
  position: absolute;
  width: 0.75em;
  height: 0.75em;
  position: absolute;
  right: 0.5em;
  top: 25%;
  z-index: 10;
  pointer-events: none;
}
<div class="myIcon">
    <svg viewBox='0 0 100 100' class="pseudoExternal">
      <use href="icons.svg#arrowDown" />
    </svg>
</div>

<div class="select-wrp">
  <select class="select-custom-icon">
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
    <option>Option 4</option>
    <option>Option 5</option>
    <option>Option 6</option>
  </select>
    <svg class="myIcon pseudoExternal" viewBox='0 0 100 100'>
      <use href="icons.svg#arrowDown" />
    </svg>
</div>

<!--emulated icons.svg file content -->
<svg class="svgAsset" id="icons" style="width:0; height:0; position:absolute;visibility:hidden;" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
  <symbol id="arrowDown">
    <polygon fill="currentColor" points='50 60 0 0 100 0 50 60' />
  </symbol>
</svg>

(Js part is only for emulating an external file source – test it on your local server, webspace etc)

However, the ability to avoid redundant svg code in your svg html body is certainly handy for simple reusable assets like icons.
In this case, changing some more basic properties like fill, color stroke-width etc. might be sufficient.

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
1

Adding your SVG directly to the HTML is the only way you can make the page's stylesheet reach into the SVG at all.
Embedding SVG as img or a background image, makes that impossible to begin with.

This article has some more info on the topic: https://css-tricks.com/using-svg/#aa-the-problem-with-both-img-and-background-image

CBroe
  • 91,630
  • 14
  • 92
  • 150