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>
I have several svg <path> elements grouped together into two seperate group <g> elements (IDs are po and ph). Both are inside another group, and everything is inside a single <svg>.
The HTML looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<link href="stripped_back_CSS.css" rel="stylesheet">
</head>
<body>
<div id="container">
<div id="content" class="hide">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 595.3 841.9" style="enable-background:new 0 0 595.3 841.9;" xml:space="preserve">
<style type="text/css">
.st5{fill:none;stroke:red;stroke-miterlimit:10;}
</style>
<g id="page">
<g id="po">
<path class="st5" d="M382.1,465.4c1.1,2.6,1.3,7.3,1.4,9.2c0.1-3.3-4-13,2.7-13.1c5.3-0.1,4.4,8.5-2.7,6.7"/>
<path class="st5" d="M395.3,464.1c-4.9,0.9-2.3,8,2.3,5.7c4.7-2.3-0.3-7.5-3.8-3.8"/>
</g>
<g id="ph">
<path class="st5" d="M392.4,522c0,3.2,0.4,6.6,1.1,9.7c-3.7-3.9-2.3-16.4,4.9-11.3c4.1,2.8-3.3,7.3-6.6,5.5"/>
<path class="st5" d="M402.8,519.3c1,3.1,0.3,6.4,2.2,9.3"/>
<path class="st5" d="M410.5,517.6c-1.2,3.6-0.9,8.7,1.1,12.1"/>
<path class="st5" d="M403.9,525.3c1.8-0.8,4-1.9,6-2.2"/>
</g>
</g>
</svg>
</div>
</div>
</body>
</html>
and the CSS file like this:
#content {
text-align: center
}
#content svg {
background: #fff;
width: auto;
height: 152vh;
margin: 8vh 0 8vh 0;
box-shadow: -9px -9px 20px 0 rgba(0, 0, 0, .12);
max-width: 90%
}
This produces the following svg characters:
I would like to make each of these groups of text into a clickable link. However, if I try to surround the group tags simply with <a></a> tags then the text stroke is indeed clickable BUT the space between the stroke is not. For example, if the cursor was hovered inside the letter 'O' then at that position nothing is clickable. How can I make the "general area" of each group of characters clickable?
Thank you!
One of the easiest way to do this would be, to use an <rect /> inside your <g>.
You can give opacity to be 0 and then write all your <path>. You can give the <rect /> height and width equal to the area you want clickable/ any mouse event.
<g id="po">
<rect x="" y="" width="" height="" style="fill-opacity:0; stroke-opacity:0" />
<path class="st5" d="M382.1,465.4c1.1,2.6,1.3,7.3,1.4,9.2c0.1-3.3-4-13,2.7-13.1c5.3-0.1,4.4,8.5-2.7,6.7"/>
<path class="st5" d="M395.3,464.1c-4.9,0.9-2.3,8,2.3,5.7c4.7-2.3-0.3-7.5-3.8-3.8"/>
</g>
Just add a transparent rect on top, or under, the other elements that you want to treat as a single link. You'll probably find that on top (ie. later in the SVG file) causes fewer issues. See below.
<g id="po">
<path class="st5" d="M382.1,465.4c1.1,2.6,1.3,7.3,1.4,9.2c0.1-3.3-4-13,2.7-13.1c5.3-0.1,4.4,8.5-2.7,6.7"/>
<path class="st5" d="M395.3,464.1c-4.9,0.9-2.3,8,2.3,5.7c4.7-2.3-0.3-7.5-3.8-3.8"/>
<a xlink:href="http://www.stackoverflow.com" target="_blank">
<rect x="382" y="461" width="18" height="14" fill="transparent"/>
</a>
</g>
The <a> element could either be around the <rect>, around the <g>, or around all the children of the group. It doesn't really matter.
#content {
text-align: center
}
#content svg {
background: #fff;
width: auto;
height: 152vh;
margin: 8vh 0 8vh 0;
box-shadow: -9px -9px 20px 0 rgba(0, 0, 0, .12);
max-width: 90%
}
<div id="container">
<div id="content" class="hide">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 595.3 841.9" style="enable-background:new 0 0 595.3 841.9;" xml:space="preserve">
<style type="text/css">
.st5{fill:none;stroke:red;stroke-miterlimit:10;}
</style>
<g id="page">
<g id="po">
<path class="st5" d="M382.1,465.4c1.1,2.6,1.3,7.3,1.4,9.2c0.1-3.3-4-13,2.7-13.1c5.3-0.1,4.4,8.5-2.7,6.7"/>
<path class="st5" d="M395.3,464.1c-4.9,0.9-2.3,8,2.3,5.7c4.7-2.3-0.3-7.5-3.8-3.8"/>
<a xlink:href="http://www.stackoverflow.com"">
<rect x="382" y="461" width="18" height="14" fill="transparent"/>
</a>
</g>
<g id="ph">
<path class="st5" d="M392.4,522c0,3.2,0.4,6.6,1.1,9.7c-3.7-3.9-2.3-16.4,4.9-11.3c4.1,2.8-3.3,7.3-6.6,5.5"/>
<path class="st5" d="M402.8,519.3c1,3.1,0.3,6.4,2.2,9.3"/>
<path class="st5" d="M410.5,517.6c-1.2,3.6-0.9,8.7,1.1,12.1"/>
<path class="st5" d="M403.9,525.3c1.8-0.8,4-1.9,6-2.2"/>
<a xlink:href="http://www.stackoverflow.com">
<rect x="391" y="517" width="21" height="15" fill="transparent"/>
</a>
</g>
</g>
</svg>
</div>
</div>
I've got a four slice scaling SVG image which works great in all browsers, except when I try and include it via a use tag. Then it works fine in Chrome, Firefox and Safari but not in <=IE11.
The problem seems to be with the clip-path, is there something I'm missing to get this working in IE or is it just not supported? If not, is there a way I can achieve the same effect in an IE friendly way?
Thanks!
#box,
#use {
width: 200px;
height: 50px;
}
#use {
fill: #f00;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div style="display: none;">
<svg id="box" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<symbol id="topleft" viewBox="0 0 500 500">
<path d="M3.1,22.6c0,0.2,0,0.4,0,477.4H5C5,22.9,5,22.5,5,22.6c0-4.3,0-7.9,0.1-8.8C5.1,11.5,5,8.2,6.2,6.1
c0.4-0.6,40.8-0.8,80.2-0.9c0.5,0,413.6,0,413.6,0V3.4c0,0-413.3,0-413.6,0C47.5,3.4,8.2,3.6,6.7,4C6.5,3,6.6,2,6.5,1
C6.4,0.3,5.7-0.1,5,0c-0.7,0.1-1,0.8-1,1.4C4.2,2.2,4.2,3,4.2,3.7C3.4,3.9,2.7,4.2,1.9,4.4c-1.5,0.3-1,2.7,0.5,2.4
C2.9,6.6,3.5,6.4,4,6.3C3.5,7.8,3.5,9.7,3.4,11C3.3,12,3.1,19.8,3.1,22.6z"/>
</symbol>
<symbol id="topright" viewBox="0 0 500 500">
<use xlink:href="#topleft"/>
</symbol>
<symbol id="bottomleft" viewBox="0 0 500 500">
<path d="M5.3,494.5C5,493.1,5,484.3,5,477.4L5,0H3.1l0,477.4c-0.1,7.2,0,15.3,0.4,17.2c-0.8,0.1-1.7,0.1-2.5,0.3
c-0.6,0.2-1.1,0.7-1,1.4c0.1,0.6,0.8,1.1,1.4,1c0.6-0.2,1.3-0.2,1.9-0.2c0,0.2-0.2,1.5-0.3,1.7C3,499.4,3.7,500,4.3,500
c0.7,0,1.2-0.5,1.3-1.1c0-0.1,0.3-2.3,0.3-2.3c1.8,0.4,4.2,0.3,5.3,0.4c0.9,0.1,37.6,0.2,75.2,0.3c0.3,0,413.6,0,413.6,0v-2.1
c0,0-413.3,0-413.6,0C46.8,495.2,6,495,5.3,494.5z"/>
</symbol>
<symbol id="bottomright" viewBox="0 0 500 500">
<use xlink:href="#bottomleft"/>
</symbol>
<clipPath id="crop">
<rect class="mask" width="100%" height="100%" x="0"/>
</clipPath>
</defs>
<svg width="50%" height="50%">
<use xlink:href="#topleft" width="500" height="500" class="box_border__tl"/>
</svg>
<!-- top right: -->
<g transform="scale(-1, 1)">
<svg width="50%" height="50%" x="-100%" y="0">
<use xlink:href="#topright" width="500" height="500" class="box_border__tr"/>
</svg>
</g>
<!-- bottom left: -->
<g transform="scale(1, -1)">
<svg width="50%" height="50%" x="0" y="-100%" clip-path="url(#crop)">
<use xlink:href="#bottomleft" width="500" height="500" y="-500" transform="scale(1, -1)" class="box_border__bl"/>
</svg>
</g>
<!-- bottom right: clip-path="url(#crop)" -->
<g transform="scale(-1, -1)">
<svg width="50%" height="50%" x="-100%" y="-100%" clip-path="url(#crop)">
<use xlink:href="#bottomright" width="500" height="500" y="-500" transform="scale(1, -1)" class="box_border__br"/>
</svg>
</g>
</svg>
</div>
<svg id="use" class="box" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#box"/>
</svg>
The issue is happening because I'm hiding the main SVG in a container with display: none; The same also happens if I use visibility: hidden; or position: absolute; on the parent container.
It only works if the main SVG is rendered on the page. It also works if you start with the main item visible, then hide it using javascript.
It's a really obscure issue, and odd how the non-clipped elements render even when the SVG is hidden. Oh, also the main SVG must be placed above the use links otherwise Safari doesn't render it.