I'm trying to use gradients in an application I'm working on, but I've run into a problem. I'm specifically using userSpaceOnUse
, because I want it to be applied across my SVG evenly. However, when I nest an SVG, the gradient is no longer evenly applied. If you check the snippet I've added, you can see this in action.
I've played around, and think this might be something to do with the viewBox, but I haven't been able to fix it.
Is it possible to fix this? Using the example I've added, I'd expect the nested SVG to perfectly match the rect behind it.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" id="gradient" x1="0%" x2="0%" y1="100%" y2="0%">
<stop offset="0" stop-color="#FCAF45"></stop>
<stop offset="1" stop-color="#833AB4"></stop>
</linearGradient>
</defs>
<g fill="url(#gradient)">
<rect height="100%" width="100%" x="0" y="0"></rect>
<svg x="20" y="20" width="10" height="10"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<rect height="24" width="24" x="0" y="0"></rect>
</svg>
</g>
</svg>
CodePudding user response:
userSpaceOnUse
is defined as
the coordinate system that results from taking the current user coordinate system in place at the time when the gradient element is referenced
That means it is the local coordinate system the <rect>
is drawn in. It does not make a difference that the gradient is lexically defined for the enclosing <g>
element - CSS inheritance means it is referenced only after being inherited by the <rect>
.
As a consequence, the coordinate system for the <rect>
inside the nested <svg>
must be the same as for any other element it is applied to. <svg>
elements always establish their own viewport, and you only are able to make their coordinate system match when its equivalent transform is the identity matrix.
In practical terms, that can be achieved when
- there is no
viewBox
attribute and thex
andy
attributes both have the value 0 - or all the values of the
viewBox
attribute are equal to thex
,y
,width
andheight
attribute values.
However you do it, you have to write the child elements of the nested <svg>
as if they were children of the outer one - you could just leave it out as well, it would make no difference.¹ Especially the geometry attributes of the <rect>
have to be changed.
Also note that percentage values are in relation to the local viewport size. For the nested <svg>
, this is their width
and height
, which differs from the root <svg>
. Therefore you must use (implicit or explicit) px
units for the gradient size.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" id="gradient" x1="0" x2="0" y1="50" y2="0">
<stop offset="0" stop-color="#FCAF45"></stop>
<stop offset="1" stop-color="#833AB4"></stop>
</linearGradient>
</defs>
<g fill="url(#gradient)">
<rect height="100%" width="100%" x="0" y="0"></rect>
<svg x="20" y="20" width="10" height="10" viewBox="20 20 10 10">
<rect x="20" y="20" height="10" width="10"></rect>
</svg>
</g>
</svg>
¹ Well, almost. There is a clip path applied at the viewport borders.