2

I have an SVG icon, I'm setting a fixed height for it (50px, say), but I want its width to be auto, that is, whatever it needs to be depending on the icon. (Pretty common and normal scenario, right?).

Now, the problem that I've been beating my head against the wall for, is that instead of embedding the SVG inline in the HTML, I'm intending to define it with the <symbol> tag and then reference it using the <use href="... tag, and doing so apparently requires me to set a fixed width as well as a fixed height or otherwise it will always default to about 150px, instead of just defaulting to the width of icon; this is not the case when you embed the SVG directly, you can see all of this in action in the following two snippets:

Directly-embedded SVG: (Works as expected, width is consistent with the width of the icon)

.icon {
  height: 50px;
  width: auto;

  /* Aesthetics: */
  background-color: gray;
  border-radius: 5px;
}
<svg class="icon" viewBox="0 0 22.832 27.398">
    <g transform="translate(-42.667)"> <g transform="translate(42.667)"> <path class="a" d="M62.074,9.133V7.991a7.991,7.991,0,1,0-15.982,0V9.133a3.429,3.429,0,0,0-3.425,3.425V23.973A3.429,3.429,0,0,0,46.092,27.4H62.074A3.429,3.429,0,0,0,65.5,23.973V12.557A3.429,3.429,0,0,0,62.074,9.133Zm-13.7-1.142a5.708,5.708,0,0,1,11.416,0V9.133H48.375ZM63.216,23.973a1.143,1.143,0,0,1-1.142,1.142H46.092a1.143,1.143,0,0,1-1.142-1.142V12.557a1.143,1.143,0,0,1,1.142-1.142H62.074a1.143,1.143,0,0,1,1.142,1.142Z" transform="translate(-42.667)"></path> </g> <g transform="translate(50.658 13.128)"> <path class="a" d="M195.425,245.333a3.416,3.416,0,0,0-1.142,6.639v1.922a1.142,1.142,0,1,0,2.283,0v-1.922a3.416,3.416,0,0,0-1.142-6.639Zm0,4.566a1.142,1.142,0,1,1,1.142-1.142A1.143,1.143,0,0,1,195.425,249.9Z" transform="translate(-192 -245.333)"></path> </g> </g>
</svg>

But using the <use> tag: (Width is coming from nowhere!)

.icon {
  height: 50px;
  width: auto;

  /* Aesthetics: */
  background-color: gray;
  border-radius: 5px;
}
<svg style="display: none;">
    <symbol id="lock" viewBox="0 0 22.832 27.398">
        <g transform="translate(-42.667)"> <g transform="translate(42.667)"> <path class="a" d="M62.074,9.133V7.991a7.991,7.991,0,1,0-15.982,0V9.133a3.429,3.429,0,0,0-3.425,3.425V23.973A3.429,3.429,0,0,0,46.092,27.4H62.074A3.429,3.429,0,0,0,65.5,23.973V12.557A3.429,3.429,0,0,0,62.074,9.133Zm-13.7-1.142a5.708,5.708,0,0,1,11.416,0V9.133H48.375ZM63.216,23.973a1.143,1.143,0,0,1-1.142,1.142H46.092a1.143,1.143,0,0,1-1.142-1.142V12.557a1.143,1.143,0,0,1,1.142-1.142H62.074a1.143,1.143,0,0,1,1.142,1.142Z" transform="translate(-42.667)" /> </g> <g transform="translate(50.658 13.128)"> <path class="a" d="M195.425,245.333a3.416,3.416,0,0,0-1.142,6.639v1.922a1.142,1.142,0,1,0,2.283,0v-1.922a3.416,3.416,0,0,0-1.142-6.639Zm0,4.566a1.142,1.142,0,1,1,1.142-1.142A1.143,1.143,0,0,1,195.425,249.9Z" transform="translate(-192 -245.333)" /> </g> </g>
    </symbol>
</svg>

<svg class="icon">
  <use href="#lock"></use>
</svg>

I've already checked out this question, as well as this one, and I've realized that by adding the "viewBox" of the icon to the referencing SVG, the problem would be solved, like this:

.icon {
  height: 50px;
  width: auto;

  /* Aesthetics: */
  background-color: gray;
  border-radius: 5px;
}
<svg style="display: none;">
    <symbol id="lock" viewBox="0 0 22.832 27.398">
        <g transform="translate(-42.667)"> <g transform="translate(42.667)"> <path class="a" d="M62.074,9.133V7.991a7.991,7.991,0,1,0-15.982,0V9.133a3.429,3.429,0,0,0-3.425,3.425V23.973A3.429,3.429,0,0,0,46.092,27.4H62.074A3.429,3.429,0,0,0,65.5,23.973V12.557A3.429,3.429,0,0,0,62.074,9.133Zm-13.7-1.142a5.708,5.708,0,0,1,11.416,0V9.133H48.375ZM63.216,23.973a1.143,1.143,0,0,1-1.142,1.142H46.092a1.143,1.143,0,0,1-1.142-1.142V12.557a1.143,1.143,0,0,1,1.142-1.142H62.074a1.143,1.143,0,0,1,1.142,1.142Z" transform="translate(-42.667)" /> </g> <g transform="translate(50.658 13.128)"> <path class="a" d="M195.425,245.333a3.416,3.416,0,0,0-1.142,6.639v1.922a1.142,1.142,0,1,0,2.283,0v-1.922a3.416,3.416,0,0,0-1.142-6.639Zm0,4.566a1.142,1.142,0,1,1,1.142-1.142A1.143,1.143,0,0,1,195.425,249.9Z" transform="translate(-192 -245.333)" /> </g> </g>
    </symbol>
</svg>

<svg class="icon" viewBox="0 0 22.832 27.398"> <!-- Copy-pasted the viewbox here -->
  <use href="#lock"></use>
</svg>

But obviously this is very inconvenient, repetitive, error-prone, and indeed ugly to write. So, I'd appreciate any workarounds. Isn't there a way to set the viewBox attribute to "auto" or something which means "whatever the inner SVG is", so as to avoid writing (or copy-pasting, rather) the viewBox each time you want to reference an icon?

To be honest, I think everyone would intuitively kind of expect this to work like regular embedded SVGs since the viewBox is already set once on the symbol that is being referenced.

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Arad Alvand
  • 8,607
  • 10
  • 51
  • 71
  • Is there a reason adding `width: 50px` in the CSS is not a valid solution for you? It solves the problem for me – Ruben Helsloot Oct 31 '20 at 23:41
  • @RubenHelsloot Obviously yes. Since not all SVGs with a class of `.icon`. have a 1:1 aspect ratio, like the very same one I included in this question. The whole point of setting `width` or `height` to `auto` is to have the browser calculate the right number for you – Arad Alvand Oct 31 '20 at 23:47
  • Clear, just wanted to make sure there wasn't a simpler solution that was being overlooked before diving deeper – Ruben Helsloot Oct 31 '20 at 23:49
  • @RubenHelsloot BTW, thanks for the edit but I had intentionally put the SVGs in one line since they are NOT the focus of the problem. I didn't want them to make the code look cluttered. – Arad Alvand Oct 31 '20 at 23:51
  • I went for a Custom Element creating all SVG and no more ``use`` usage in [IconMeister.github.io](https://iconmeister.github.io) Full control over SVG, and allows for different viewBox per icon, so you can cherry pick icons from any Icon Set (I prepped over 7000+ icons now) – Danny '365CSI' Engelman Nov 01 '20 at 10:05

2 Answers2

5

I'd highly recommend:

  • Make all your icons have a consistent viewBox. For example, "0 0 24 24" is a common one that many icon libraries use. That way you don't have to find and copy the correct viewBox where you need it. It's always "0 0 24 24".

  • Add a class to the referencing <svg> that you can use for setting the icon width.

<svg class="icon lock" viewBox="0 0 24 24">
  <use href="#lock"></use>
</svg>

Then in your CSS:

.icon {
  // as above
}

.icon.lock {
  width: 45px;
  height: 50px;
}

.icon.something-else {
  width: 35px;
  height: 50px;
}

As long as your icon is horizontally centred in its viewBox, everything will work.

Default size (square) icons need no extra CSS. You only need to add a CSS rule for the non-square ones.

Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181
  • Thanks. But I'm struggling to understand how this solves the problem. I wanted to have an automatic `width` (or `height`) in my CSS since I didn't want to set a fixed aspect ratio, now with this approach, every single icon will be assumed to have a 1:1 aspect ratio, and in that case, **why do I even need to set a `viewBox` AND a fixed `width: 50px;` and `height: 50px;` in the CSS? I can just have the fixed width and height in the CSS and omit the `viewBox` altogether; the result would be the same, wouldn't it?** Please tell me if I'm misunderstanding you here, I'd love you to clarify a bit. – Arad Alvand Nov 01 '20 at 12:59
  • There is no way to have an automatic width or height using the `` method. I was suggesting a compromise. The advantages of my suggestion are (a) all the viewboxes are the same, and (b) you only need to add special CSS for the non-square icons. – Paul LeBeau Nov 02 '20 at 03:15
2

If you're not opposed to using some JavaScript, the following should work. Load it once, no matter how many icons you have on your page.

document.querySelectorAll(".icon").forEach(node => {
  const href = node.querySelector("use").href.baseVal;
  const icon = document.querySelector(href);
  const vb = icon.viewBox.baseVal;
  node.setAttribute("viewBox", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);
});
.icon {
  height: 50px;
  width: auto;
  /* Aesthetics: */
  background-color: gray;
  border-radius: 5px;
}
<svg style="display: none;">
  <symbol id="lock" viewBox="0 0 22.832 27.398">
    <g transform="translate(-42.667)">
      <g transform="translate(42.667)">
        <path
          class="a"
          d="M62.074,9.133V7.991a7.991,7.991,0,1,0-15.982,0V9.133 
             a3.429,3.429,0,0,0-3.425,3.425V23.973A3.429,3.429,0,0,0,46.092,27.4
             H62.074A3.429,3.429,0,0,0,65.5,23.973V12.557
             A3.429,3.429,0,0,0,62.074,9.133Z
             m-13.7-1.142a5.708,5.708,0,0,1,11.416,0V9.133H48.375Z
             M63.216,23.973a1.143,1.143,0,0,1-1.142,1.142H46.092
             a1.143,1.143,0,0,1-1.142-1.142V12.557a1.143,1.143,0,0,1,1.142-1.142
             H62.074a1.143,1.143,0,0,1,1.142,1.142Z"
          transform="translate(-42.667)">
        </path>
      </g>
      <g transform="translate(50.658 13.128)">
        <path
          class="a" 
           d="M195.425,245.333a3.416,3.416,0,0,0-1.142,6.639v1.922
              a1.142,1.142,0,1,0,2.283,0v-1.922a3.416,3.416,0,0,0-1.142-6.639Z
              m0,4.566a1.142,1.142,0,1,1,1.142-1.142
              A1.143,1.143,0,0,1,195.425,249.9Z"
           transform="translate(-192 -245.333)">
        </path>
      </g>
    </g>
  </symbol>
</svg>

<svg class="icon">
  <use href="#lock"></use>
</svg>

If you want to add icons dynamically - after page load, the following would also be an improvement over adding a manual viewBox:

function iconLoaded(event) {
  const node = event.target;
  const href = node.querySelector("use").href.baseVal;
  const icon = document.querySelector(href);
  const vb = icon.viewBox.baseVal;
  node.setAttribute("viewBox", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);
}
.icon {
  height: 50px;
  width: auto;
  /* Aesthetics: */
  background-color: gray;
  border-radius: 5px;
}
<svg style="display: none;">
  <symbol id="lock" viewBox="0 0 22.832 27.398">
    <g transform="translate(-42.667)">
      <g transform="translate(42.667)">
        <path
          class="a"
          d="M62.074,9.133V7.991a7.991,7.991,0,1,0-15.982,0V9.133 
             a3.429,3.429,0,0,0-3.425,3.425V23.973A3.429,3.429,0,0,0,46.092,27.4
             H62.074A3.429,3.429,0,0,0,65.5,23.973V12.557
             A3.429,3.429,0,0,0,62.074,9.133Z
             m-13.7-1.142a5.708,5.708,0,0,1,11.416,0V9.133H48.375Z
             M63.216,23.973a1.143,1.143,0,0,1-1.142,1.142H46.092
             a1.143,1.143,0,0,1-1.142-1.142V12.557a1.143,1.143,0,0,1,1.142-1.142
             H62.074a1.143,1.143,0,0,1,1.142,1.142Z"
          transform="translate(-42.667)">
        </path>
      </g>
      <g transform="translate(50.658 13.128)">
        <path
          class="a" 
           d="M195.425,245.333a3.416,3.416,0,0,0-1.142,6.639v1.922
              a1.142,1.142,0,1,0,2.283,0v-1.922a3.416,3.416,0,0,0-1.142-6.639Z
              m0,4.566a1.142,1.142,0,1,1,1.142-1.142
              A1.143,1.143,0,0,1,195.425,249.9Z"
           transform="translate(-192 -245.333)">
        </path>
      </g>
    </g>
  </symbol>
</svg>

<svg class="icon" onload="iconLoaded(event)">
  <use href="#lock"></use>
</svg>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
  • +1 Thanks for this. But it doesn't work if the `href` attribute on the `` tag is pointing to an external SVG file, which is in fact what I need, I made it point to an element on the same document in my question just for simplicity sake. – Arad Alvand Nov 01 '20 at 00:12
  • Aah, but in that case, why not simply use ` – Ruben Helsloot Nov 01 '20 at 00:13
  • Because I'm using an SVG sprite, which means I have one SVG file containing ALL the icons needed each being its own `` with a unique `id`, which I then reference in the HTML document with the `` tag. You can't use a sprite with an `` (as far as I know), you'd have to use CSS `background-image`s with specific `background-position`s for each icon and stuff, which would be a whole 'nother topic. – Arad Alvand Nov 01 '20 at 00:17