SVG gradient using CSS - css

I'm trying to get a gradient applied to an SVG rect element.
Currently, I'm using the fill attribute. In my CSS file:
rect {
cursor: pointer;
shape-rendering: crispEdges;
fill: #a71a2e;
}
And the rect element has the correct fill color when viewed in the browser.
However, I'd like to know if I can apply a linear gradient to this element?

Just use in the CSS whatever you would use in a fill attribute.
Of course, this requires that you have defined the linear gradient somewhere in your SVG.
Here is a complete example:
rect {
cursor: pointer;
shape-rendering: crispEdges;
fill: url(#MyGradient);
}
<svg width="100" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
rect{fill:url(#MyGradient)}
</style>
<defs>
<linearGradient id="MyGradient">
<stop offset="5%" stop-color="#F60" />
<stop offset="95%" stop-color="#FF6" />
</linearGradient>
</defs>
<rect width="100" height="50"/>
</svg>

2019 Answer
With brand new css properties you can have even more flexibility with variables aka custom properties
.shape {
width:500px;
height:200px;
}
.shape .gradient-bg {
fill: url(#header-shape-gradient) #fff;
}
#header-shape-gradient {
--color-stop: #f12c06;
--color-bot: #faed34;
}
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" class="shape">
<defs>
<linearGradient id="header-shape-gradient" x2="0.35" y2="1">
<stop offset="0%" stop-color="var(--color-stop)" />
<stop offset="30%" stop-color="var(--color-stop)" />
<stop offset="100%" stop-color="var(--color-bot)" />
</linearGradient>
</defs>
<g>
<polygon class="gradient-bg" points="0,0 100,0 0,66" />
</g>
</svg>
Just set a named variable for each stop in gradient and then customize as you like in css. You can even change their values dynamically with javascript, like:
document.querySelector('#header-shape-gradient').style.setProperty('--color-stop', "#f5f7f9");

Building on top of what Finesse wrote, here is a simpler way to target the svg and change it's gradient.
This is what you need to do:
Assign classes to each color stop defined in the gradient element.
Target the css and change the stop-color for each of those stops using plain classes.
Win!
Some benefits of using classes instead of :nth-child is that it'll not be affected if you reorder your stops. Also, it makes the intent of each class clear - you'll be left wondering whether you needed a blue color on the first child or the second one.
I've tested it on all Chrome, Firefox and IE11:
.main-stop {
stop-color: red;
}
.alt-stop {
stop-color: green;
}
<svg class="green" width="100" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg">
<linearGradient id="gradient">
<stop class="main-stop" offset="0%" />
<stop class="alt-stop" offset="100%" />
</linearGradient>
<rect width="100" height="50" fill="url(#gradient)" />
</svg>
See an editable example here:
https://jsbin.com/gabuvisuhe/edit?html,css,output

Here is a solution where you can add a gradient and change its colours using only CSS:
// JS is not required for the solution. It's used only for the interactive demo.
const svg = document.querySelector('svg');
document.querySelector('#greenButton').addEventListener('click', () => svg.setAttribute('class', 'green'));
document.querySelector('#redButton').addEventListener('click', () => svg.setAttribute('class', 'red'));
svg.green stop:nth-child(1) {
stop-color: #60c50b;
}
svg.green stop:nth-child(2) {
stop-color: #139a26;
}
svg.red stop:nth-child(1) {
stop-color: #c84f31;
}
svg.red stop:nth-child(2) {
stop-color: #dA3448;
}
<svg class="green" width="100" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg">
<linearGradient id="gradient">
<stop offset="0%" />
<stop offset="100%" />
</linearGradient>
<rect width="100" height="50" fill="url(#gradient)" />
</svg>
<br/>
<button id="greenButton">Green</button>
<button id="redButton">Red</button>

Thank you everyone,
for all your precise replys.
Using the svg in a shadow dom, I add the 3 linear gradients I need within the svg, inside a .
I place the css fill rule on the web component and the inheritance od fill does the job.
<svg viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path
d="m258 0c-45 0-83 38-83 83 0 45 37 83 83 83 45 0 83-39 83-84 0-45-38-82-83-82zm-85 204c-13 0-24 10-24 23v48c0 13 11 23 24 23h23v119h-23c-13 0-24 11-24 24l-0 47c0 13 11 24 24 24h168c13 0 24-11 24-24l0-47c0-13-11-24-24-24h-21v-190c0-13-11-23-24-23h-123z"></path>
</svg>
<svg height="0" width="0">
<defs>
<linearGradient id="lgrad-p" gradientTransform="rotate(75)"><stop offset="45%" stop-color="#4169e1"></stop><stop offset="99%" stop-color="#c44764"></stop></linearGradient>
<linearGradient id="lgrad-s" gradientTransform="rotate(75)"><stop offset="45%" stop-color="#ef3c3a"></stop><stop offset="99%" stop-color="#6d5eb7"></stop></linearGradient>
<linearGradient id="lgrad-g" gradientTransform="rotate(75)"><stop offset="45%" stop-color="#585f74"></stop><stop offset="99%" stop-color="#b6bbc8"></stop></linearGradient>
</defs>
</svg>
<div></div>
<style>
:first-child {
height:150px;
width:150px;
fill:url(#lgrad-p) blue;
}
div{
position:relative;
width:150px;
height:150px;
fill:url(#lgrad-s) red;
}
</style>
<script>
const shadow = document.querySelector('div').attachShadow({mode: 'open'});
shadow.innerHTML="<svg viewbox=\"0 0 512 512\">\
<path d=\"m258 0c-45 0-83 38-83 83 0 45 37 83 83 83 45 0 83-39 83-84 0-45-38-82-83-82zm-85 204c-13 0-24 10-24 23v48c0 13 11 23 24 23h23v119h-23c-13 0-24 11-24 24l-0 47c0 13 11 24 24 24h168c13 0 24-11 24-24l0-47c0-13-11-24-24-24h-21v-190c0-13-11-23-24-23h-123z\"></path>\
</svg>\
<svg height=\"0\">\
<defs>\
<linearGradient id=\"lgrad-s\" gradientTransform=\"rotate(75)\"><stop offset=\"45%\" stop-color=\"#ef3c3a\"></stop><stop offset=\"99%\" stop-color=\"#6d5eb7\"></stop></linearGradient>\
<linearGradient id=\"lgrad-g\" gradientTransform=\"rotate(75)\"><stop offset=\"45%\" stop-color=\"#585f74\"></stop><stop offset=\"99%\" stop-color=\"#b6bbc8\"></stop></linearGradient>\
</defs>\
</svg>\
";
</script>
The first one is normal SVG,
the second one is inside a shadow dom.

Here is how to set a linearGradient on a target element:
<style type="text/css">
path{fill:url('#MyGradient')}
</style>
<defs>
<linearGradient id="MyGradient">
<stop offset="0%" stop-color="#e4e4e3" ></stop>
<stop offset="80%" stop-color="#fff" ></stop>
</linearGradient>
</defs>

Related

SVG linear-gradient fill works on desktop view but not on mobile. How can I fix this?

So I have a logo for a website I'm making with React and Express. The SVG path is returned from a function which is called in both Desktop and Mobile components. Here's my default.css (which stands for default style, applied to both mobile and desktop, I'm only changing color stuff here.
svg {
fill: url(#logo-gradient);
}
#logo-gradient {
--color-stop-1: rgba(242, 68, 132, 1);
--color-stop-2: rgba(237, 136, 173, 1);
--color-stop-3: #fbbed4;
}
Here's how it works well on Desktop view:
Not working on Mobile:
However, if I use fill: red; it shows on mobile as expected, of course, in red:
I have no idea what could be wrong, It should just work. Any help would be appreciated!
Edit: Responding to "somethinghere", I thought it would be irrelevant to include svg code since it's returned by a function and the same function is called on both mobile and desktop components... But here it is:
getLogo = () => {
return (
<svg
viewBox="0 0 250 50"
width="250"
height="50"
version="1.1"
id="svg3785">
<defs>
<linearGradient id="logo-gradient">
<stop offset="0%" stop-color="var(--color-stop-1)" />
<stop offset="77%" stop-color="var(--color-stop-2)" />
<stop offset="100%" stop-color="var(--color-stop-3)" />
</linearGradient>
</defs>
<path d="M 17.699219 8.7597656 ... really long path"/>
</svg>
)
}
And in component (including only parent divs):
<div className="logo-container">
{this.getLogo()}
</div>
And for the Mobile:
<div className="nav-resp-logo-container">
{this.getLogo()}
</div>
I have tried to simplify your code to something testable, and it all seems to be working fine across devices on my end:
:root {
--g1: red;
--g2: green;
--g3: blue;
}
#definitions {
position: fixed;
top: 0;
left: 0;
transform: translate(-100%,-100%);
opacity: 0;
pointer-events: none;
}
#logo text {
fill: url(#logo-gradient);
}
<svg
viewBox="0 0 250 50"
width="250"
height="50"
id="definitions"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="logo-gradient">
<stop offset="0%" stop-color="var(--g1)" />
<stop offset="77%" stop-color="var(--g2)" />
<stop offset="100%" stop-color="var(--g3)" />
</linearGradient>
</defs>
</svg>
<svg
id="logo"
viewBox="0 0 250 50"
width="250"
height="50"
xmlns="http://www.w3.org/2000/svg">
<text x="0" y="40" style="font-size: 40px; font-weight: bold;">Hello World</text>
</svg>
Which means your problem might not be in the code you are showing us. Maybe the logo is accidentally hidden on mobile? What browsers are you testing this on? Which devices?
The only advice I could give your is to ensure that you have a defined XMLNS, or XML namespace. If you do not, funky things can happen on certain browsers - mostly older ones. So make sure your <svg> tags at least contain this:
xmlns="http://www.w3.org/2000/svg"

use var to set offset position in SVG gradient

I am using a gradient to fill an svg path.
like this:
<svg viewBox="0 0 40 40">
<defs>
<linearGradient id="progress-gradient" x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="var(--offset-empty)" stopColor="var(--color-empty)" />
<stop offset="var(--offset-full)" stopColor="var(--color-full)" />
</linearGradient>
</defs>
<path d="..removed" fill="url(#progress-gradient)" />
</svg>
and I am styling it with css:
#progress-gradient {
--color-empty: #ffffff;
--color-full: #ff0000;
--offset-empty: 50%;
--offset-full: 100%;
}
I am trying to set the offsets dynamically to create a kind of progress within the SVG icon.
But this isn't working. Colors are working, but not offset.
Is there any other way I can dynamically set the offset positions with CSS?
many thanks

How to set the size of the handle in a QSlider?

I'm trying to add some style to a QSlider I want to use a custom image as the handle that the user drags back and forth. While I've figured out how to use style sheets to have a custom icon drawn where the handle should be, the image is being drawn much too small and I cannot figure out how to make it larger.
Setting width and height seem to do nothing. I've tried using image, border-image and background-image, but none give me the ability to set the size of the handle image. Does anyone know how to do this?
This is the style sheet that I've been adding to my QSlider in QtDesigner:
QSlider::handle:vertical {
image: url(:/data/icons/mixer-slider-handle.svg);
width:64px;
height:64px;
}
This is the SVG:
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="30"
height="45"
viewBox="0 0 7.9374997 11.90625"
version="1.1"
id="svg8"
>
<defs
id="defs2">
<linearGradient
id="linearGradient844"
inkscape:collect="always">
<stop
id="stop840"
offset="0"
style="stop-color:#cecece;stop-opacity:1;" />
<stop
id="stop842"
offset="1"
style="stop-color:#ffffff;stop-opacity:1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient824">
<stop
style="stop-color:#cecece;stop-opacity:1;"
offset="0"
id="stop820" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop822" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient824"
id="linearGradient826"
x1="-3.9103179"
y1="297.24557"
x2="-3.8304768"
y2="285.38882"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8683302,0,0,0.96503255,7.3827223,9.9179025)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient844"
id="linearGradient838"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.86322913,0,0,0.81935486,7.5301966,52.317886)"
x1="-3.8119318"
y1="285.99686"
x2="-3.7885454"
y2="296.82458" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-285.09375)">
<rect
style="fill:url(#linearGradient826);fill-opacity:1;stroke:#0e0e0e;stroke-width:0.1373108;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect818"
width="5.157938"
height="11.397007"
x="1.453124"
y="285.36124"
ry="2.866178" />
<rect
style="opacity:1;fill:url(#linearGradient838);fill-opacity:1;stroke:none;stroke-width:0.12615089;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect828"
width="4.6027613"
height="8.9100981"
x="1.7161824"
y="286.60291"
ry="2.184411" />
<path
style="fill:none;stroke:#000000;stroke-width:0.24780074px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.8804469,291.0695 4.3266789,0.047"
id="path846"
inkscape:connector-curvature="0" />
</g>
</svg>
Looks like the answer is to set the margin attribute for both the groove and the handle to reflect the size of the handle you are using.
This worked for me:
.QSlider::groove:vertical {
border: 1px solid #111;
background-color: #333;
width: 6px;
margin: 24px 12px;
}
.QSlider::handle:vertical {
image: url(:/data/icons/mixer-slider-handle.svg);
margin: -24px -12px;
height: -30px;
}

How to use less #variable for SVG inline style "stop-color"

I have two variables for colors in color.less
#color-example-1: red;
#color-example-2: yellow;
and an svg in example.html that looks something like this:
<svg viewBox="0 0 48 48">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0" y1="24" x2="48" y2="24">
<stop offset="0" style="stop-color:#FFF33B"/>
<stop offset="1" style="stop-color:#E93E3A"/>
</linearGradient>
<circle class="st0" cx="24" cy="24" r="24"/>
</svg>
Is it possible to either replace the stop-color value with a #variable or (even better) define the whole linearGradient in the css file?
A desired result would be something like this:
css
.example-gradient {
background: linear-gradient(135deg, #color-example-1 0%,#color-example-2 100%);
}
html
<svg viewBox="0 0 48 48">
<circle class="example-gradient" cx="24" cy="24" r="24"/>
</svg>
You can't define the whole gradient in CSS. CSS gradients don't currently work on SVG elements. They might one day in the future. If they did, you would use something like the following:
circle {
fill: linear-gradient(to right, yellow, orange)
}
However all is not lost. You definitely can restyle the <stop> elements in the SVG gradient definition.
Note that the style="stop-color: ..." in the SVG gradient will override any CSS you define. So the first thing you need to do is remove it, or change it to a presentation attribute (stop-color="#abcdef").
.stop1 {
stop-color: blue;
}
.stop2 {
stop-color: yellow;
}
<svg viewBox="0 0 48 48">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0" y1="24" x2="48" y2="24">
<stop offset="0" class="stop1" stop-color="#FFF33B"/>
<stop offset="1" class="stop2" stop-color="#E93E3A"/>
</linearGradient>
<circle class="st0" cx="24" cy="24" r="24"/>
</svg>
Note that I obviously haven't used LESS here, but it should work fine as long as your SVG is inlined in your HTML.

How can i make an SVG-sprite tile properly in a CSS background?

I'm trying to use part of an SVG as a tiling background. However I'm having great problems making it work properly.
I think a picture will illustrate the problem best:
demonstration picture
This is my CSS:
body {
background: url("tiletest.svg#svgView(viewBox(120 32 150 64))");
background-size: 30px 32px;
}
And here is the code of the SVG I'm using for the demonstration:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg height="100" width="360" version="1.1" id="Lager_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 360 100" style="enable-background:new 0 0 360 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#13FF00;}
.st2{fill:#2732FF;}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="9.094947e-13" y1="9.094947e-13" x2="100" y2="100">
<stop offset="0" style="stop-color:#E7FF00"/>
<stop offset="1" style="stop-color:#FF0000"/>
</linearGradient>
<rect class="st0" width="100" height="100"/>
</g>
<polygon class="st1" points="174.68,0 190.92,32.92 227.25,38.2 200.96,63.82 207.17,100 174.68,82.92 142.19,100 148.39,63.82
122.11,38.2 158.43,32.92 "/>
<circle class="st2" cx="310" cy="50" r="50"/>
</svg>
As you can see the full viewbox in the SVG is 0 0 360 100 and when I call the SVG in the CSS I'm giving it a new viewbox of 120 32 150 64, as well as changing the background size accordingly (though I'm pretty sure this shouldn't matter anyway as the svg defined by the viewbox is supposed to expand to fill the container no matter size,right?).
I've tried fiddling with the viewbox in the SVG, with the width and height, with the preserveAspectRatio attribute, and so far nothing has worked. What am I doing wrong?
<update>
While what I wrote below is useful it did not fully fix your problem. With the proviso that Safari and iOs support is flakey, I got your code working by removing the height and width on the SVG as below:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 100">
<linearGradient id="grad" x2="100" y2="100" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#E7FF00"/>
<stop offset="1" stop-color="#F00"/>
</linearGradient>
<path d="M0 0h100v100H0z" fill="url(#grad)"/>
<path d="M174.68 0l16.24 32.92 36.33 5.28-26.29 25.62 6.21 36.18-32.49-17.08L142.19 100l6.2-36.18-26.28-25.62 36.32-5.28z" fill="#13FF00"/>
<circle cx="310" cy="50" r="50" fill="#2732FF"/>
</svg>
Using following html worked in Chrome, Firefox and IE11:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SVG Fragment</title>
<style type="text/css">
body {
background: url("tiletest.svg#svgView(viewBox(120,32,30,32))");
background-size: 30px 32px;
}
</style>
</head>
<body>
<h1>SVG Fragment</h1>
</body>
</html>
</update>
A couple of things, support for inline viewbox is not there in all browsers yet ... and often has quirks (see links at bottom) ... the other is that the viewbox is x-min y-min width height ... you appear to have thought it is x1 y1 x2 y2.
You should have used background: url("tiletest.svg#svgView(viewBox(120 32 30 32))"); for it to work in Chrome ... though you may need to use a view element to get it working in Firefox.
I've shown another way to implement what you want below which will work in all modern browsers (except Opera Mini and a few others). Hope it gives you some ideas.
.svg-background {
height: 200px;
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='120 32 30 32'%3E%3ClinearGradient id='grad' x2='100' y2='100' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23E7FF00'/%3E%3Cstop offset='1' stop-color='%23F00'/%3E%3C/linearGradient%3E%3Cpath d='M0 0h100v100H0z' fill='url(%23grad)'/%3E%3Cpath d='M174.68 0l16.24 32.92 36.33 5.28-26.29 25.62 6.21 36.18-32.49-17.08L142.19 100l6.2-36.18-26.28-25.62 36.32-5.28z' fill='%2313FF00'/%3E%3Ccircle cx='310' cy='50' r='50' fill='%232732FF'/%3E%3C/svg%3E");
}
<svg xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 360 100">
<linearGradient id="grad" x2="100" y2="100" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#E7FF00"/>
<stop offset="1" stop-color="#F00"/>
</linearGradient>
<path d="M0 0h100v100H0z" fill="url(#grad)"/>
<path d="M174.68 0l16.24 32.92 36.33 5.28-26.29 25.62 6.21 36.18-32.49-17.08L142.19 100l6.2-36.18-26.28-25.62 36.32-5.28z" fill="#13FF00"/>
<circle cx="310" cy="50" r="50" fill="#2732FF"/>
</svg>
<div class="svg-background">
</div>
Further Reading:
Mozilla Developer Network (MDN) Viewbox page
CanIUse.com svg fragment identifiers
Also check out the SVG view element which I have not looked into yet.
Definitely check out the page on CSSTricks about fragment identifers
and the fragment identifier testbed codepen

Resources