4

I have been breaking my head for hours now. It doens't make any sense.

I reduced the problem I am experiencing to this codepen: https://codepen.io/Octopous/pen/OJORpJQ

HTML:

*the two inline SVG's* (too many characters), see the CodePen

CSS:

svg.one {
  display: none;
}

svg.two {
  display: block;
}

There's two inline SVG elements on a page. They are identical. When the first SVG is set to "display: none", the second SVG gets scrambled/displays differently. In this case all elements that used to be clipped by a clipping path now ignore the clipping path. When the first SVG is not set to "display: none", the second SVG display correctly, just like the first one.

As the CSS declarations are as simple as can be and couldn't be any more specific, I just can't wrap my head around why the second SVG is even affected by any of this. In all my years as a webdeveloper and working with SVG's I have never come across a problem even close to this.

Things I have tried:

  • renaming all the ID's within the second SVG (even though this doesn't make sense)
  • Exporting an addition SVG with different dimensions and such so that the second SVG has different properties all together without making it visually different.

Any help is greatly appreciated. It is paramount for this project that both SVG's are indeed the same SVG. As long as this problem exists I simply cannot continue the project.

EDIT: Safari seems to be doing fine, behaviour as expected. Firefox and Chrome both screw it up.

Grabbels
  • 123
  • 1
  • 9
  • 7
    Make sure you use unique id attributes, otherwise `url(#_clip...)` property values might point to the wrong SVG. – ccprog Feb 04 '22 at 11:07
  • Thank you, that seems to have worked! I thought I tried this already but I failed to also change the corresponding clip-path attributes to the actual clipPath elements. It still feels weird that a css declaration on an SVG as a whole screws with a different SVG element. That's not how CSS is supposed to work when classes don't match up. – Grabbels Feb 04 '22 at 11:57
  • It is precisely how CSS is supposed to work. [All ID values must be unique in a document](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) – Robert Longson Feb 04 '22 at 12:57
  • Wrap those SVGs in a shadowRoot so all IDs/styles/defs are unique **_within_ a shadowDOM**. With external SVGs you can use the [`` Web Component``](https://dev.to/dannyengelman/load-file-web-component-add-external-content-to-the-dom-1nd) – Danny '365CSI' Engelman Feb 04 '22 at 13:28
  • 1
    Also watch for SVGs that share `class` names. For example, Illustrator can produce files that all reuse class names of the form `cls-1`, `cls-2`, etc – Paul LeBeau Feb 04 '22 at 13:48
  • The spec has always been that an ID is a unique identifier re: https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#the-id-attribute Voting to close as a typo – Mark Schultheiss Jun 02 '23 at 11:45

2 Answers2

2

Problem - Entangled SVGs (Demo in CodePen)

As others have pointed out in the comments, the issue is that you shouldn't include the same SVG twice on a page if that SVG has elements that have an [id] attribute.

At best, your markup is invalid, because IDs must be unique throughout the entire document

At worst, as we see here, having multiple IDs creates confusion for the browser as to which to use when referenced by other elements. By default, browsers will typically use the first id within the document flow. If the first one happens to be hidden via display none, nothing automatically re-routes to the next applicable usage.

Solutions

Option A - Create Unique IDs

Depending on how you're injecting SVGs into the page, you can swap out the old ID and replace with a random guid. That way every SVG works and acts entirely independently and doesn't interfere with any others.

However, this takes slightly more processing and slightly more space over the wire.

Option B - Use External SVG

Instead of inlining the <svg> element, if you instead refer to an external reference file, like <img src="/circle.svg" />, then each file gets its own document scope and can be acted on independently.

However, requires an additional (but hopefully cached) network request. And externally loaded SVGs have some other limitations like not being able to inherit color.

Option C - Conceal, Don't Hide

Instead of using display:none, try using width:0px;height:0px; position;absolute;right:-100px;. Which allows the styles to still be used, but moves the element off screen.

However, this can be challenging if the thing being hidden is higher up in the DOM and just happens to include the SVG

Option D - Use CSS instead of defs

Depending on what properties you're referencing, it may be possible to modify the SVG to use CSS instead of referencing SVG elements by ID.

However, it can range from challenging to impossible find alternate ways to phrase SVGs in CSS.

Option E - Split Defs from Usage

Create two SVGs - Icon_Base.svg, Icon_Image.svg. Add any common <defs> to the base and include it toward the top of your DOM, and then include the actual image in your SVGs anywhere and include anywhere within the page. Since the base is guaranteed to be visible, you can update individual images as you see fit without fear of interference.

However, creates a rather unituitive DX that the *_Base variant needs to be included somewhere else on the page and included if you use the icon anywhere else.

Option F - Inline B64 SVG

You can serialize your entire SVG as a base64 string and then refer to the ID of that URL like this fill="url(data:image/svg+xml;base64,<encoded full svg>#mygradient)

However, only works on FF

Further Reading

KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • Migrated from [SVG filter loses colour when the SVG is hidden or external](https://stackoverflow.com/a/76385177/1366033) to better directly address OP's question – KyleMit Jun 02 '23 at 11:37
0

In React you can put your svg into component and generate a random id for every svg:

export const PlayCircleIcon: FC<SvgIconProps> = () => {
  const id = useRef(Math.floor(Math.random() * 10e4)).current;

  return (
    <svg>
      <path
        // ...
        fill={`url(#paint0_linear_6316_${id})`}
      />
      <defs>
        <linearGradient
          id={`paint0_linear_6316_${id}`}
          // ...
        >
          <stop stopColor="#6345ED" />
          <stop offset="1" stopColor="#E039FD" />
        </linearGradient>
      </defs>
    </svg>
  );
};

nezort11
  • 326
  • 1
  • 4
  • 11