88

I have a SVG graphic put like this:

a::before { content: url(filename.svg); }

When I hover over the tag, I really want the SVG to change the fill color, without loading a new SVG file, as I have now:

a:hover::before { content: url(filename_white.svg); }

Is this possible to achieve using JavaScript, jQuery or just pure CSS that I am not aware of?

Thanks.

mnsth
  • 2,169
  • 1
  • 18
  • 22

4 Answers4

157

The accepted answer is incorrect, this is actually possible by applying a workaround with an SVG mask and background-color:

p:after {
  width: 48px;
  height: 48px;
  display: inline-block;
  content: '';
  -webkit-mask: url(https://gh.max.ax/heart.svg) no-repeat 50% 50%;
  mask: url(https://gh.max.ax/heart.svg) no-repeat 50% 50%;
  -webkit-mask-size: cover;
  mask-size: cover;
}

.red:after {
  background-color: red;
}

.green:after {
  background-color: green;
}

.blue:after {
  background-color: blue;
}
<p class="red">red heart</p>
<p class="green">green heart</p>
<p class="blue">blue heart</p>

You're not actually modifying the SVG DOM itself, you're just changing the background color. That way, you could even use images or gradients as background.


Update

As MisterJ mentioned, this feature is sadly not widely supported.

After six years, the support for prefixed use has risen to 97%.

lmaooooo
  • 3,364
  • 4
  • 19
  • 24
  • 1
    I think you should update with the support for this method (http://caniuse.com/#feat=css-masks) You need to be ok with dropping some browsers along the way if you want to go for this one. – MisterJ Mar 17 '17 at 12:21
  • It's actually even lower than 88%. The partial support in browsers doesn't support the feature you're using. Firefox for instance doesn't support it. – Sirisian Mar 29 '17 at 15:14
  • Works great and you can fallback to a Unicode symbol in the content attribute (or use fontawesome) – nodws Feb 22 '18 at 23:11
  • 1
    content:'' does not seem to be necessary. – bart Oct 31 '18 at 21:20
  • @coops glad I could help someone :) – lmaooooo Apr 17 '19 at 14:24
  • Thanks! It helped me as well. The accepted answer might not be incorrect as it is answered in 2014. – Akshay Nov 24 '20 at 16:52
  • This worked for me, thank you! Just point out that SVG doesn't show up if it does not have *fill* property set in the code. – Pablo Dec 03 '20 at 14:51
  • 2
    This is brilliant. Content fallback works beautifully. Nice little trick is to set the background-color to currentColor, if you need to match the text :D – Andron May 06 '21 at 22:36
25

Using the content property generates (non-exposed) markup functionally equvialent to an svg in an <img> element.

You cannot apply style to elements inside the svg document because:

  1. styles are not allowed to cascade across documents
  2. when using <img> (or content, or any css image property that references svg) the svg document is not exposed by the browsers due to security concerns

Similar questions, but for background-image here and here.

So, to do what you want you must get around the two points above somehow. There are various options for doing that, e.g using inline svg, using filters (applied to the <img>) or generating different svg files (or data URIs), as in your question.

Community
  • 1
  • 1
Erik Dahlström
  • 59,452
  • 12
  • 120
  • 139
7

A technique similar to @lmaooooo's answer without setting display: inline-block. This is useful in phrasing content when you want to preserve display: inline to ensure the :after content doesn't wrap to a new line independent of the preceding text content.

Also uses clip-path to prevent the background-color from leaking in Safari (the usefulness of this depends on the image/line-height/etc).

a[target="_blank"]:after {
  background-color: currentColor;
  content: "";
  padding: 0 0.5em;
  margin: 0 0.125rem;
  -webkit-mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2048 2048'%3E%3Cpath d='M1792 256v640h-128V475l-851 850-90-90 850-851h-421V256h640zm-512 1007h128v529H256V640h529v128H384v896h896v-401z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2048 2048'%3E%3Cpath d='M1792 256v640h-128V475l-851 850-90-90 850-851h-421V256h640zm-512 1007h128v529H256V640h529v128H384v896h896v-401z'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  clip-path: padding-box inset(0.28125em 0);
}
Lorem ipsum sumit dolar <a href="#" target="_blank">hello world</a>
Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
6

There is another way to do it. You can make it happen by playing with filters.

Simply add svg url in content and play with hue-rotate to change the color.

According to CanIUse It is supported by all popular broswers.

p:after {
  width: 48px;
  height: 48px;
  content: url(https://gh.max.ax/heart.svg);
}

.red:after {
  filter: hue-rotate(14deg);
}

.green:after {
  filter: hue-rotate(120deg);
}

.blue:after {
  filter: hue-rotate(210deg);
}

.black:after {
  filter: grayscale(100%);
}
<p class="red">red heart</p>
<p class="green">green heart</p>
<p class="blue">blue heart</p>
<p class="black">black heart</p>
molikh
  • 1,274
  • 13
  • 24