I have two implementations of consuming the same SVG content: one through a <use>
element and one through an <img>
. Despite the same content, viewbox, and sizing on both, the <use>
element comes up shorter — whereas the <img>
is 160x160
, <use>
is 150x122
. For the life of me I cannot seem to find an answer as to why this is.
The problem I’m looking to solve is to merge 10 or so individual SVG files, consumed via an <img>
, into a singular sprite sheet to avoid unnecessary network requests (they’re always consumed together, making this a perfect use for sprites). However, the sizing doesn’t match up when switching to the sprite sheet.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
div {
display: flex;
flex-direction: column;
max-width: 10rem;
}
img, svg {
width: 100%;
}
</style>
</head>
<body>
<div>
<svg>
<use href="./icon-sprite.svg#icon" />
</svg>
<img src="./icon.svg" />
</div>
</body>
</html>
icon-sprite.svg
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="icon" viewBox="0 0 54 54">
<path d="M52 5H2C.9 5 0 5.9 0 7v40c0 1.1.9 2 2 2h50c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zM1 7c0-.55.45-1 1-1h50c.55 0 1 .45 1 1v9H1zm52 40c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V17h52z" fill="#37474f" />
<path d="M6 8.5a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5zm0 4c-.83 0-1.5-.67-1.5-1.5S5.17 9.5 6 9.5s1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm6-4a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5zm0 4c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm6-4a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5zm0 4c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM48 10c0-.55-.45-1-1-1H35c-.55 0-1 .45-1 1v2c0 .55.45 1 1 1h12c.55 0 1-.45 1-1zm-1 2H35v-2h12zM29.8 29.58l3.2 2.28V44c0 1.65 1.35 3 3 3s3-1.35 3-3V31.86l3.2-2.28c.44-.32.8-1.03.8-1.58v-5c0-.55-.32-1.32-.7-1.7L39 18v6l-3 2-3-2v-6l-3.3 3.3c-.38.38-.7 1.15-.7 1.7v5c0 .55.36 1.26.8 1.58zM30 23c0-.28.2-.8.4-1l1.6-1.6v4.15l.45.3 3 2 .55.36.56-.37 3-2 .44-.3V20.2l1.6 1.7c.2.2.4.82.4 1.1v5c0 .23-.2.63-.4.77l-3.18 2.27-.42.3V44c0 1.1-.9 2-2 2s-2-.9-2-2V31.34l-.42-.3-3.18-2.27c-.2-.14-.4-.54-.4-.77z" fill="#37474f" />
<path stroke-miterlimit="10" d="M36 44V33" fill="#b2ff59" stroke="#37474f" stroke-linecap="round" />
<path d="M27 36h-4V19h-8v17h-4l8 11zm-12 1h1V20h6v17h3.04L19 45.3 12.96 37z" fill="#37474f" />
</symbol>
</svg>
icon.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54">
<path d="M52 5H2C.9 5 0 5.9 0 7v40c0 1.1.9 2 2 2h50c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zM1 7c0-.55.45-1 1-1h50c.55 0 1 .45 1 1v9H1zm52 40c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V17h52z" fill="#37474f" />
<path d="M6 8.5a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5zm0 4c-.83 0-1.5-.67-1.5-1.5S5.17 9.5 6 9.5s1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm6-4a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5zm0 4c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm6-4a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5zm0 4c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM48 10c0-.55-.45-1-1-1H35c-.55 0-1 .45-1 1v2c0 .55.45 1 1 1h12c.55 0 1-.45 1-1zm-1 2H35v-2h12zM29.8 29.58l3.2 2.28V44c0 1.65 1.35 3 3 3s3-1.35 3-3V31.86l3.2-2.28c.44-.32.8-1.03.8-1.58v-5c0-.55-.32-1.32-.7-1.7L39 18v6l-3 2-3-2v-6l-3.3 3.3c-.38.38-.7 1.15-.7 1.7v5c0 .55.36 1.26.8 1.58zM30 23c0-.28.2-.8.4-1l1.6-1.6v4.15l.45.3 3 2 .55.36.56-.37 3-2 .44-.3V20.2l1.6 1.7c.2.2.4.82.4 1.1v5c0 .23-.2.63-.4.77l-3.18 2.27-.42.3V44c0 1.1-.9 2-2 2s-2-.9-2-2V31.34l-.42-.3-3.18-2.27c-.2-.14-.4-.54-.4-.77z" fill="#37474f" />
<path stroke-miterlimit="10" d="M36 44V33" fill="#b2ff59" stroke="#37474f" stroke-linecap="round" />
<path d="M27 36h-4V19h-8v17h-4l8 11zm-12 1h1V20h6v17h3.04L19 45.3 12.96 37z" fill="#37474f" />
</svg>
2
Answers
To build this into a snippet I had to use an inline SVG for the sprite, to avoid cross-domain security issues. But all I had to do to get the sprite the same size as the image was to add an
aspect-ratio: 1/1
.Note that neither your
icon.svg
nor youricon-sprite.svg
have an intrinsic size, so if you do not specify a size, you are relying on the browser default behaviour which may vary from browser to browser. Your CSS specifies the width of the SVGs (100% of a flexbox which is 10em wide), but not the height. I believe the spec does say that a user agent should use a default size for an SVG if the size is not specified. It seems that, in my browser at least, the regular SVG defaults to an aspect-ratio of 1:1 when the height is not specified, whereas the sprite seems to default to zero height.Not sure why you were getting different results, but it could be browser-dependent. I am using Safari on a Mac. I tried in Chrome also and the sprite doesn’t need the aspect-ratio to be specified manually.
Expanding on enxaneta’s comment:
Rule 1:
<svg>
elements don’t adjust their rendered bounding box based on contentThis may be an unsatisfying answer but
there is no exception to this rule:
<svg>
won’t auto-fit to content.No element will cause the parent svg element to expand its rendered bounding box. (you can retrieve a tight content based bounding box via JS method
getBBox()
)A common misconception is to think the
<symbol>
element’s viewBox is somehow "inherited" to the parent svg when appending a<use>
instance (referencing to the symbol). Nope! No exception!This can’t work since we can only inherit properties from parent (svg) to child (use) and not the other way round (at least in a HTML/CSS context).
If you don’t specify either width or height or a viewBox for the parent
<svg>
element – the browser will render the svg with a default of 300×150 px.However, if all your icons are placed in a square bounding box you can as well set a css rule to specify a width and length.
All in all: either copy viewBox attribute values from symbol to svg instances or define dimensions via css.