简体   繁体   中英

Why doesn't an SVG with a <use> tag scale like a regular SVG?

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.

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.

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM