Home > Back-end >  Inconsistent behaviour with nested SVG's and gradients
Inconsistent behaviour with nested SVG's and gradients

Time:03-05

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 the x and y attributes both have the value 0
  • or all the values of the viewBox attribute are equal to the x, y, width and height 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.

  •  Tags:  
  • svg
  • Related