3

I am using a simple SVG triangle shaped path that is filled via CSS using a reference to an SVG Gradient.

SVG:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <linearGradient id="gradient">
        <stop offset="0%" />
        <stop offset="100%" />
      </linearGradient>
    <polygon id="triangle" points="0,0 100,0 0,66" />     
</svg>

CSS:

#triangle {
  fill: url(#gradient);
}

To have a little bit more freedom with the colors used in the gradient and to get the colors out of the markup I opted to use CSS custom properties (aka CSS Variables)

CSS:

#gradient stop:first-child {
    stop-color: var(--color-stop);
}
#gradient stop {
    stop-color: var(--color-stop2);
}

Sofar so good, the issue is that I want to reuse that triangle shape and give it another color. Overwriting the CSS variables has no effect! https://codepen.io/Type-Style/pen/gNYpjL

// nothing in js this time
svg {width: 250px; height:200px;}
:root {
  --color-stop: orange;
  --color-stop2: red;
}
.second { /* has no effect */
  --color-stop: lime;
   --color-stop2: green; 
}

#gradient stop:first-child {
 stop-color: var(--color-stop);
}
#gradient stop {
 stop-color: var(--color-stop2);
}

#triangle {
  fill: url(#gradient);
}
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <linearGradient id="gradient">
        <stop offset="0%" />
        <stop offset="100%" />
      </linearGradient>
    <polygon id="triangle" points="0,0 100,0 0,66" />   
</svg>
<svg class="second" viewBox="0 0 100 100">
 <use class="use-triangle" href="#triangle" />
</svg>

Expected Result:

I wished for a second triangle colored in a different way.


Result:

But I t seems like once the gradient is used, there is no way of modifying/overwriting it. It feels like that the gradient only takes value directly assigned to it which cannot be changed after using it. It may be due to the nature of the usage of the fill referencing an ID, but I am not sure


What I have tried / credit:

I checked SVG gradient using CSS which is where I got and modified the example from. (Thx to @Maciej-Kwas)

Additionally I am aware that you can inherit fill and use currentColor to provide more colors to an element that is "used". But up to this point I could not get this to work with a gradient yet.


Unsatisfying Workaround:

Since my goal is to keep the colors outside of the markup I could move the svg to the css inline as a data-Uri much like this:

fill: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><linearGradient id='grad'><stop offset='0%' stop-color='%23ff00cc'/><stop offset='100%' stop-color='%23333399'/></linearGradient></svg>#grad")

Big credit for the article about this: https://fvsch.com/svg-gradient-fill/

But since this only works in Firefox and seems hacky.


Closing thoughts:

I wonder if there is any other solution. And would like to know exactly why it does not work as I expected, in the first place, in detail.

Type-Style
  • 1,791
  • 1
  • 16
  • 35
  • 1
    my first comment wasn't correct I guess .. the explanation is related to the fact that the linearGradient component is not duplicated each `use` but defined only once for the document – Temani Afif Jun 09 '19 at 21:53
  • 1
    Create two gradients. – Robert Longson Jun 09 '19 at 21:57
  • Yes that @TemaniAfif, that is a better definition of what I already felt like is happening. Unfortunatly there is no way of changing that, or referencing a gradient without an id!? – Type-Style Jun 09 '19 at 22:00
  • 1
    I am not expert on SVG but I don't think you can change that (with or without ID). Inspect the element and see the SVG with the `use` and you will notice that there is no gradient there. I think this is how SVG handle the `defs` element: only defined once and used multiple times and not duplicated multiple times – Temani Afif Jun 09 '19 at 22:02
  • is really only appropriate if everything is the same. If it's not, create a template and clone it then adjust the clones separately. – Robert Longson Jun 10 '19 at 09:52

2 Answers2

2

I am not sure if you are searching for a generic solution but for this particular case you can do the same effect using pure CSS.

The below trick works fine because the gradient direction is an horizontal one:

:root {
  --c1: orange;
  --c2: red;
}

.second {
  --c1: lime;
  --c2: green;
}

.triangle {
  width: 200px;
  height: 132px;
  display: inline-block;
  overflow: hidden;
  position: relative;
}

.triangle:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(to right, var(--c1), var(--c2));
  transform: skewY(-33.42deg); /* arctan(132/200)*/
  transform-origin: left;
}
<div class="triangle">

</div>

<div class="triangle second">

</div>

You can also consider clip-path where it will be easier to define any kind of gradient but you need to pay attention to browser support. You may also notice that the syntax of the polygon is almost similar to the one of the SVG so you can also consider any kind of shape.

:root {
  --c1: orange;
  --c2: red;
}

.second {
  --c1: lime;
  --c2: green;
}

.triangle {
  width: 200px;
  height: 132px;
  display: inline-block;
  background: linear-gradient(65deg, var(--c1), var(--c2));
  -webkit-clip-path:polygon(0 0,100% 0,0 100%);
  clip-path:polygon(0 0,100% 0,0 100%);
}
<div class="triangle">

</div>

<div class="triangle second">

</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
2

Two observations:

  1. You need to define 2 gradients and choose which one with a variable: fill: var(--grd);

  2. You don't fill the <polygon> you need to reuse. You fill the <use> element.

I hope it helps.

svg {width: 250px; height:200px;border:1px solid}
:root {
  --grd: url(#gradient);
}
.second{--grd: url(#gradient2);}

use {
  fill: var(--grd);
}
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> 
 <defs>
    <linearGradient id="gradient">
        <stop offset="0%" stop-color="orange"/>
        <stop offset="100%" stop-color="red" />
      </linearGradient>
   <linearGradient id="gradient2">
        <stop offset="0%" stop-color="lime"/>
        <stop offset="100%" stop-color="green" />
      </linearGradient>
    <polygon id="triangle" points="0,0 100,0 0,66" />  
 </defs>
 
 <use class="use-triangle" xlink:href="#triangle" />
</svg>
<svg class="second" viewBox="0 0 100 100">
 <use class="use-triangle" xlink:href="#triangle" />
</svg>
enxaneta
  • 31,608
  • 5
  • 29
  • 42