3

If I have several SVG elements in a HTML body, where each SVG references a CSS class name, and this class name is defined in one of the SVG elements like below, then all the SVG elements are affected by this style. I am trying to find out whether this is by design, and how to make sure that the CSS class name is local to the SVG that defines it.

<svg xmlns="http://www.w3.org/2000/svg" width=100 viewBox="0 0 15 10">
    <path class="iconMain" d="M7.5,9,1,4.78789l.46713-.91645L7.5,7.85543l6.03287-3.984L14,4.78789ZM14,1.91646,13.53287,1,7.5,4.98349,1.46713,1,1,1.91646l6.5,4.2121Z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width=100 viewBox="0 0 15 10">
    <defs>
        <style>
            .iconMain {
                fill: #9cbacf;
            }
        </style>
    </defs>
    <path class="iconMain"
        d="M7.5,9,1,4.78789l.46713-.91645L7.5,7.85543l6.03287-3.984L14,4.78789ZM14,1.91646,13.53287,1,7.5,4.98349,1.46713,1,1,1.91646l6.5,4.2121Z" />
</svg>

https://codepen.io/napsta32/pen/ExOPGae

akonsu
  • 28,824
  • 33
  • 119
  • 194

3 Answers3

1

You are misunderstanding the usage of defs they are used to define graphical elements to be used later on with the tag use If you put something inside that is not a graphical element it will go straight into the document, a style or script for example. And even the defs are global, if you define one element inside an svg you can use it in another one.

<svg xmlns="http://www.w3.org/2000/svg" width=100 viewBox="0 0 15 10">
  <defs>
  <circle id="circle_defined_in_first_svg" cx="1" cy="2" r="6" />
 </defs>
  
    <path class="iconMain" d="M7.5,9,1,4.78789l.46713-.91645L7.5,7.85543l6.03287-3.984L14,4.78789ZM14,1.91646,13.53287,1,7.5,4.98349,1.46713,1,1,1.91646l6.5,4.2121Z" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg" width=100 viewBox="0 0 15 10">
    <defs>
        <style>
            .iconMain {
                fill: #9cbacf;
                background-color:pink;
            }
        </style>       
      <script>
        var variableInsideDef = 7987;
      </script>      
    </defs>
    <path class="iconMain"
        d="M7.5,9,1,4.78789l.46713-.91645L7.5,7.85543l6.03287-3.984L14,4.78789ZM14,1.91646,13.53287,1,7.5,4.98349,1.46713,1,1,1.91646l6.5,4.2121Z" />
  
  <use x="5" y="5" href="#circle_defined_in_first_svg"/>
  
</svg>

<p class="iconMain"> P ELEMENT IN ROOT AFFECTED BY THE STYLE INSIDE DEF</p>

<script>
  alert("Variable declared inside def: " + variableInsideDef)
</script>

The only way I can think for you to have styles aplying only to a specific svg is to have a different class for each one...

Paulo Fernando
  • 3,148
  • 3
  • 5
  • 21
  • Thank you. I did not know about `defs`. But if I remove the `defs` and just define `style` without it, the problem still persists. – akonsu Jun 24 '23 at 02:54
  • Can you use different classes for each svg, so that each one gets it's own styles? – Paulo Fernando Jun 24 '23 at 03:20
  • Yes, I can. Webpack is useful for that. It can generate unique CSS class names if I use scss modules. I was just trying to understand what is going on and whether there is a way to create a "scope" for each `style` in an SVG. – akonsu Jun 24 '23 at 03:22
  • I see, yeah, there is no way I know... – Paulo Fernando Jun 24 '23 at 11:33
0

Inlined SVGs containing <style> elements will affect the global document styles just as any other appended stylesheet.

As already pointed out by Paulo Fernando

See this example also affecting HTML elements:

<svg xmlns="http://www.w3.org/2000/svg" width=100 viewBox="0 0 15 10">
    <path class="iconMain" d="M7.5 9l-6.5-4.2l0.5-0.9l6 4l6-4l0.5 0.9zm6.5-7.1l-0.5-0.9l-6 4l-6-4l-0.5 0.9l6.5 4.2z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width=100 viewBox="0 0 15 10">
    <defs>
        <style>
            .iconMain {
                fill: #9cbacf;
            }
          p{
            color: red
          }
          body{
            background: #ccc
          }
          h1{
            color:green
          }
        </style>
    </defs>
    <path class="iconMain"
        d="M7.5 9l-6.5-4.2l0.5-0.9l6 4l6-4l0.5 0.9zm6.5-7.1l-0.5-0.9l-6 4l-6-4l-0.5 0.9l6.5 4.2z" />
</svg>

<h1>New Heading</h1>
<p>New paragraph</p>

Use case 1: icons

Common practice: keep your icon graphics as neutral as possible to facilitate styling like changing fill/stroke colors for each instance.

You can take inspiration from popular icon libraries like fontAwesome, feather icons, Material Icons etc.

The main concept: organize your icons as <symbol> elements and create instances via <use> elements.

svg{
  width:1em;
  border:1px solid #ccc;
}
<svg viewBox="0 0 15 10">
  <use href="#angle-double-down" fill="red"/>
</svg>

<svg viewBox="0 0 15 10">
  <use href="#angle-double-down" fill="green"/>
</svg>

<svg viewBox="0 0 15 10">
  <use href="#angle-double-down" fill="purple"/>
</svg>


<!-- hidden svg icon asset -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 10" style="position:absolute; height:0; width:0;">
  <symbol id="angle-double-down">
    <path d="M7.5 9l-6.5-4.2l0.5-0.9l6 4l6-4l0.5 0.9zm6.5-7.1l-0.5-0.9l-6 4l-6-4l-0.5 0.9l6.5 4.2z" />
  </symbol>
</svg>

To some extent you can also use external <use> references like

<svg viewBox="0 0 15 10">
  <use href="icons.svg#angle-double-down" fill="green"/>
</svg>

However there are some limitations or issues concerning gradients, masks clip paths etc. (Related "SVG ignores Gradient styling").

Use case 2: complex SVG graphics with predefined colors

It is not always necessary to manipulate the SVG styles, for example, when you display a complex vector illustration.

In this case – just use an <img> element referencing a SVG file.
This file can also contain style rules but it won't override any global HTML element styles.

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' viewBox='0 0 15 10'%3E%3Cdefs%3E%3Cstyle%3E .iconMain %7B fill: %239cbacf; %7D body %7B background: red; %7D %3C/style%3E%3C/defs%3E%3Cpath class='iconMain' d='M7.5,9,1,4.78789l.46713-.91645L7.5,7.85543l6.03287-3.984L14,4.78789ZM14,1.91646,13.53287,1,7.5,4.98349,1.46713,1,1,1.91646l6.5,4.2121Z' /%3E%3C/svg%3E" />

Otherwise, using a native web component might be a good alternative as explained here: "How can I change the color of an 'svg' element?"

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
0

Yes, SVG content is global content.

  • id attributes become duplicates, following SVGs will <use> the first element with that id

  • And <style> becomes global style.

Easiest, modern, way around this is to:

create a native JavaScript Web Component (JSWC)

Wrap the innerHTML in shadowDOM, creating the "scope" you are after,
so styles don't leak in (except for CSS properties, parts and inheritable styles)
... or leak out

Symantic HTML:

    <svg-chevron></svg-chevron>
    <svg-chevron color="green"></svg-chevron>
    <svg-chevron color="blue"></svg-chevron>
    <svg-chevron color="gold"></svg-chevron>

then creates:

<svg-chevron></svg-chevron>
<svg-chevron color="green"></svg-chevron>
<svg-chevron color="blue"></svg-chevron>
<svg-chevron color="gold"></svg-chevron>

<style>
 [color] { --iconhovercolor:beige }
 path { fill:red!important } /* never applied to shadowDOM */
</style>

<script>
  customElements.define('svg-chevron', class extends HTMLElement {
    connectedCallback(){
      Object.assign( this.style , {
        display : "inline-block",
        width   : "100px",
        cursor  : "pointer"
      });
      let color = this.getAttribute("color") || "black";
      let d = "m75 90-65-42 5-9 60 40 60-40 5 9zm65-71-5-9-60 40-60-40-5 9 65 42z";
      this.attachShadow({mode:"open"})
          .innerHTML = `<style>
                          path{fill:${color}}
                          svg:hover{background:var(--iconhovercolor,red)}
                        </style>
                        <svg viewBox="0 0 150 100">
                         <path d="${d}" stroke="${color}"/>
                        </svg>`;
      this.onclick = (evt) => {
           console.log("You clicked me!", color);
      }
    }
  });
</script>

Refactor the Web Component to <svg-icon> and you have your own icon design system:

  <svg-icon is="chevron"></svg-icon>
  <svg-icon is="menu"></svg-icon>
  <svg-icon is="settings"></svg-icon>

fish and ice Icons from another StackOverflow question:

<style>
  svg-icon {  --iconhovercolor:beige;  width: 120px  }
</style>

<svg-icon></svg-icon>
<svg-icon is="fish" color="green"></svg-icon>
<svg-icon is="fish" color="blue"></svg-icon>
<svg-icon color="teal"></svg-icon>

<script>
  customElements.define('svg-icon', class extends HTMLElement {
        connectedCallback() {
          Object.assign(this.style, {
            display: "inline-block",
            cursor: "pointer"
          });
          let is = this.getAttribute("is") || "ice";
          let color = this.getAttribute("color") || "black";
          let d = {
            "fish": "m64 46s-.7 2.7-2.6 5.3c-1.9 2.5-4 3.4-4 3.4s11.53 0 21.23 2.2c9.7 2 17.5 10.12 17.5 10.12s-.8-1.2-1.8-3.02c-1-1.8.8-4.4.8-4.4s-2.7.5-5.8-.2c-3-.6-3.9-5.2-3.9-5.2s-2.8.5-6 .5-3.6-4.5-3.6-4.5-4.5 1.2-7.7-.2c-3.13-1.4-4.03-4-4.03-4zm-2.5 10.7c-24.1 0-39.4 22.62-39.4 22.62l9 3.1h-6.8s8.2 16.7 27.9 20c.7 1.4 1.8 3.5 3.4 4.8 2.6 2 7.6 3.7 7.6 3.7s-3.3-2.9-3.7-5c-.2-.9-.3-2-.3-2.9 1.6 0 3.3-.1 5.1-.3 28.33-2.7 32.43-17.7 41.13-17.7 8.8 0 21 10.6 21 10.6l-11-16.9 11-14.66c-1.6 1.66-8.1 8.76-19.1 8.76-12.5 0-21.9-15.76-45.98-15.76zm5.93 5c2.1.8 3.6 3.1 3.6 5.22 0 1.9-1.7 3.7-3.9 4.5 1.7-1.1 2.5-2.7 2.5-4.5 0-2.02-.6-4.12-2.2-5.22zm-18.03 1.3c5.2 2.62 7.6 10.12 7.6 16.82s-3 12.4-8.1 15.1c4-3.6 5.9-9 5.9-15.1 0-6.2-1.4-13.3-5.4-16.82zm-7.4 3.82c1.9 0 3.5 1.6 3.5 3.5s-1.6 3.5-3.5 3.5c-2 0-3.6-1.6-3.6-3.5s1.6-3.5 3.6-3.5zm34.03 1.2c2 .8 3.7 3.1 3.7 5.2 0 1.9-1.8 3.7-4 4.5 1.6-1.1 2.5-2.7 2.5-4.5 0-2-.5-4.1-2.2-5.2zm7.2 5.9c2.3.8 3.8 3.1 3.8 5.2 0 1.9-1.8 3.7-4 4.5 1.7-1.1 2.5-2.7 2.5-4.5 0-2-.5-4.1-2.3-5.2zm-16 1.1c2.1.8 3.6 3.1 3.6 5.2 0 1.9-1.7 3.7-3.9 4.5 1.7-1.1 2.5-2.7 2.5-4.5 0-2-.6-4.1-2.2-5.2zm7.9 6.2c2.1.8 3.7 3.1 3.7 5.2 0 1.9-1.8 3.7-3.9 4.5 1.6-1.1 2.4-2.7 2.4-4.5 0-2-.6-4.1-2.2-5.2zm-7.9 6.8c2.2.8 3.8 3.1 3.7 5.2.1 1.9-1.7 3.7-3.9 4.5 1.7-1.1 2.5-2.7 2.5-4.5 0-2-.6-4.1-2.3-5.2z",
            "ice": "m15.5 39h-1.6c6.9 6.64 6.6 11.74 10.1 19.64 2.9 6.4 5.8 11.6 10.5 15.1h-12.2c-1.5 0-2.9 1.3-2.9 2.9v.1c0 1.6 1.4 2.9 2.9 2.9h94.5c1.6 0 3-1.3 3-2.9v-.1c0-1.5-1.4-2.9-3-2.9h-26.4c.1-.7.2-1.4.2-2.1 0-8.9-7.4-16.1-16.5-16.1s-16.5 7.2-16.5 16.1c-1.6.7-3.3 1.3-5 1.5-4 .4-7.5-.9-10.5-2.8-5.4-3.3-9.1-8.6-11.5-14.2-1.6-3.8-3.8-7.3-6-10.8 1 1.1 2 2.3 2.9 3.5 2.8 3.2 4.6 7.1 6.8 10.7 2.1 3.7 5.2 6.8 8.9 9.1 3 1.8 6.8 2.2 10.2 2 1.1-.1 2.3 0 3.3-.4 2.2-3 .9-7.3-.4-10.1-.5-1.1-1-2.4-1.8-3.4-1.4.7-2.9 1.4-4.3 1.9 1-2.2 3.1-4.5 1.7-5.7-1.8-1.7-3.3-3.6-5.4-5-.9 1.5-1.7 3.1-2.7 4.4-.1-2.1-.1-4.3-.3-6.5-2.6-1.6-6.1-3.34-9.5-4.34-.6.2-.2 4.04-.5 5.74-.9-2.2-1.7-4.44-2.5-6.54-3.5-.80-11.1-1.7-15.5-1.7zm5.9 41.14c0 8.2 8.7 15.4 21.9 19.6h52.5c13.2-4.2 21.9-11.4 22-19.6h-96.4z"
          }[is];
          this.attachShadow({mode:"open"})
              .innerHTML = `<style>
                              path{fill:${color}}
                              svg{vertical-align:top}
                              svg:hover{background:var(--iconhovercolor,red)}
                            </style>
                            <svg viewBox="0 0 140 140"><path d="${d}" stroke="${color}"/></svg>`;
          this.onclick = (evt) => {
               console.log("You clicked me!", is, color);
          }
        }});
</script>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49