Wrong viewBox inside a sprite

72 Views Asked by At

I struggled for a long time to satisfy the customer and found a solution. I use svg sprites and tags inside to insert icons in css. It works great, but changing the resolution obviously breaks the ViewBox. It's as if the icons don't fit within their dimensions and disappear around the edges.

What am I supposed to do? What is the correct way to set them viewBox?

my svg sprite:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style><![CDATA[
      .sprite { display: none; }
      .sprite:target { display: inline; }
    ]]></style>
  </defs>
  <g class="sprite" id="circle">
    <circle cx="50" cy="50" r="45" stroke-width="5" stroke="#0f0" fill="#0f0" fill-opacity="0.5" />
  </g>
  <g class="sprite" id="square">
    <rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00"
      fill-opacity="0.5" />
  </g>
  <g class="sprite" id="triangle">
    <path d="M20,7 L92,50 L6,93 z" stroke-width="5" stroke="#00f" fill="#00f" fill-opacity="0.5" />
  </g>

  <g id="popup" fill="none" class="sprite" viewBox="0 0 14 9">
    <path d="M1 1L7 7L13 1" stroke="#29354D" stroke-width="1.5" />
  </g>

  <g id="filter" class="sprite" fill="none">
    <path
      d="M21 15H14M4 15H1M21 4H18M8 4H1M6 12H12C13.1 12 14 12.5 14 14V16C14 17.5 13.1 18 12 18H6C4.9 18 4 17.5 4 16V14C4 12.5 4.9 12 6 12ZM10 1H16C17.1 1 18 1.5 18 3V5C18 6.5 17.1 7 16 7H10C8.9 7 8 6.5 8 5V3C8 1.5 8.9 1 10 1Z"
      stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"
      stroke-linejoin="round" />
  </g>

</svg>

I style it like this:

background-image: url('/sprite.svg#filter');

In html markup like this:

<svg class="popup-block__more-icon">
   <use xlink:href="sprite.svg#popup" />
 </svg>  

Please help me. This topic with svg has been tormenting me for a week, the customer is very angry and time is getting shorter. I can't change my approach, I need to figure out exactly this variant.

enter image description here

enter image description here

I tried without using viewBox at all, tried to set both and tag, but all without result....

1

There are 1 best solutions below

0
herrstrietzel On

SVG <g> groups can't have individual viewBox values like <symbol> elements.

You icons have different bounding boxes/dimensions and the browser won't automatically adjust or fit these icons to the desired layout width and height.

This is how your icons actually look like in your sprite svg.

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" >
  <g class="sprite" id="circle">
    <circle cx="50" cy="50" r="45" stroke-width="5" stroke="#0f0" fill="#0f0" fill-opacity="0.5" />
  </g>
  <g class="sprite" id="square">
    <rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00" fill-opacity="0.5" />
  </g>
  <g class="sprite" id="triangle">
    <path d="M20,7 L92,50 L6,93 z" stroke-width="5" stroke="#00f" fill="#00f" fill-opacity="0.5" />
  </g>
  <g id="filter" class="sprite" >
    <path d="M21 15H14M4 15H1M21 4H18M8 4H1M6 12H12C13.1 12 14 12.5 14 14V16C14 17.5 13.1 18 12 18H6C4.9 18 4 17.5 4 16V14C4 12.5 4.9 12 6 12ZM10 1H16C17.1 1 18 1.5 18 3V5C18 6.5 17.1 7 16 7H10C8.9 7 8 6.5 8 5V3C8 1.5 8.9 1 10 1Z" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" fill="none" />
  </g>
  <g id="popup" fill="none" class="sprite" viewBox="0 0 14 9">
    <path d="M1 1L7 7L13 1" stroke="#29354D" stroke-width="1.5" />
  </g>
</svg>

Workaround 1: scale in editor (e.g inkscape)

Adjust/scale the icons' sizes manually to fit into your desired viewBox.

Workaround 2: convert the icons to symbols/use elements

You could write your custom converter in vanilla JS to create <use> elements with an appropriate viewBox retrieved via getBBox() method.

Save the converted svg file as a new asset. Now you can place your icons via <use> (but you'll need to a prefix like "use_popup") or <img>/ CSS background-image

let svg = document.querySelector('svg');

// find icons
let icons = svg.querySelectorAll('g[id]');

icons.forEach(icon => {
  // get icon boundaries
  let {
    x,
    y,
    width,
    height
  } = icon.getBBox();

  // copy attributes like classNames or fill
  let iconClass = icon.getAttribute('class');
  let iconFill = icon.getAttribute('fill');

  // replace groups with symbols
  let ns = "http://www.w3.org/2000/svg";
  let symbol = document.createElementNS(ns, 'symbol')
  symbol.setAttribute('viewBox', `${[0, 0, width+x*2, height+y*2].join(' ')}`);
  symbol.id = icon.id;
  if (iconFill) {
    symbol.setAttribute('fill', iconFill)
  }

  // move group children to symbol
  let children = [...icon.children];
  children.forEach(child => {
    symbol.append(child)
  })
  icon.replaceWith(symbol)

  // append use instances
  let use = document.createElementNS(ns, 'use')
  let useID = `use_${icon.id}`;
  use.id = useID
  use.setAttribute('href', '#' + symbol.id);
  use.setAttribute('class', iconClass)
  svg.append(use)

})

// output converted SVG
svgOut.value = new XMLSerializer().serializeToString(svg)
img,
.img,
svg {
  border: 1px solid #ccc;
  width: 10em;
  height: 10em;
}

textarea {
  width: 100%;
  min-height: 20em;
  display: block;
}

.img {
  display: inline-block;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E .sprite %7B display: none; %7D .sprite:target %7B display: inline; %7D %3C/style%3E%3Csymbol viewBox='0 0 100 100' id='circle'%3E%3Ccircle cx='50' cy='50' r='45' stroke-width='5' stroke='%230f0' fill='%230f0' fill-opacity='0.5'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 100 100' id='square'%3E%3Crect y='5' x='5' width='90' height='90' stroke-width='5' stroke='%23f00' fill='%23f00' fill-opacity='0.5'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 98 100' id='triangle'%3E%3Cpath d='M20,7 L92,50 L6,93 z' stroke-width='5' stroke='%2300f' fill='%2300f' fill-opacity='0.5'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 22 19' id='filter'%3E%3Cpath d='M21 15H14M4 15H1M21 4H18M8 4H1M6 12H12C13.1 12 14 12.5 14 14V16C14 17.5 13.1 18 12 18H6C4.9 18 4 17.5 4 16V14C4 12.5 4.9 12 6 12ZM10 1H16C17.1 1 18 1.5 18 3V5C18 6.5 17.1 7 16 7H10C8.9 7 8 6.5 8 5V3C8 1.5 8.9 1 10 1Z' stroke='%23292D32' stroke-width='1.5' stroke-miterlimit='10' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 14 8' id='popup' fill='none'%3E%3Cpath d='M1 1L7 7L13 1' stroke='%2329354D' stroke-width='1.5'/%3E%3C/symbol%3E%3Cuse id='use_circle' href='%23circle' class='sprite'/%3E%3Cuse id='use_square' href='%23square' class='sprite'/%3E%3Cuse id='use_triangle' href='%23triangle' class='sprite'/%3E%3Cuse id='use_filter' href='%23filter' class='sprite'/%3E%3Cuse id='use_popup' href='%23popup' class='sprite'/%3E%3C/svg%3E#use_popup");
}
<div style="position:absolute; width:0; height:0; visibility:hidden;">
  <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <style>
    .sprite { display: none; }
    .sprite:target { display: inline; }
   </style>
  <g class="sprite" id="circle">
    <circle cx="50" cy="50" r="45" stroke-width="5" stroke="#0f0" fill="#0f0" fill-opacity="0.5" />
  </g>
  <g class="sprite" id="square">
    <rect y="5" x="5" width="90" height="90" stroke-width="5" stroke="#f00" fill="#f00" fill-opacity="0.5" />
  </g>
  <g class="sprite" id="triangle">
    <path d="M20,7 L92,50 L6,93 z" stroke-width="5" stroke="#00f" fill="#00f" fill-opacity="0.5" />
  </g>
  <g id="filter" class="sprite" >
    <path d="M21 15H14M4 15H1M21 4H18M8 4H1M6 12H12C13.1 12 14 12.5 14 14V16C14 17.5 13.1 18 12 18H6C4.9 18 4 17.5 4 16V14C4 12.5 4.9 12 6 12ZM10 1H16C17.1 1 18 1.5 18 3V5C18 6.5 17.1 7 16 7H10C8.9 7 8 6.5 8 5V3C8 1.5 8.9 1 10 1Z" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" fill="none" />
  </g>
  <g id="popup" fill="none" class="sprite" viewBox="0 0 14 9">
    <path d="M1 1L7 7L13 1" stroke="#29354D" stroke-width="1.5" />
  </g>
</svg>
</div>

<h3>SVG use: </h3>
<svg>
  <use href="#popup"></use>
</svg>

<svg>
  <use href="#filter"></use>
</svg>

<h3>img: fragment identifier</h3>
<img src="data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E .sprite %7B display: none; %7D .sprite:target %7B display: inline; %7D %3C/style%3E%3Csymbol viewBox='0 0 100 100' id='circle'%3E%3Ccircle cx='50' cy='50' r='45' stroke-width='5' stroke='%230f0' fill='%230f0' fill-opacity='0.5'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 100 100' id='square'%3E%3Crect y='5' x='5' width='90' height='90' stroke-width='5' stroke='%23f00' fill='%23f00' fill-opacity='0.5'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 98 100' id='triangle'%3E%3Cpath d='M20,7 L92,50 L6,93 z' stroke-width='5' stroke='%2300f' fill='%2300f' fill-opacity='0.5'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 22 19' id='filter'%3E%3Cpath d='M21 15H14M4 15H1M21 4H18M8 4H1M6 12H12C13.1 12 14 12.5 14 14V16C14 17.5 13.1 18 12 18H6C4.9 18 4 17.5 4 16V14C4 12.5 4.9 12 6 12ZM10 1H16C17.1 1 18 1.5 18 3V5C18 6.5 17.1 7 16 7H10C8.9 7 8 6.5 8 5V3C8 1.5 8.9 1 10 1Z' stroke='%23292D32' stroke-width='1.5' stroke-miterlimit='10' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/symbol%3E%3Csymbol viewBox='0 0 14 8' id='popup' fill='none'%3E%3Cpath d='M1 1L7 7L13 1' stroke='%2329354D' stroke-width='1.5'/%3E%3C/symbol%3E%3Cuse id='use_circle' href='%23circle' class='sprite'/%3E%3Cuse id='use_square' href='%23square' class='sprite'/%3E%3Cuse id='use_triangle' href='%23triangle' class='sprite'/%3E%3Cuse id='use_filter' href='%23filter' class='sprite'/%3E%3Cuse id='use_popup' href='%23popup' class='sprite'/%3E%3C/svg%3E#use_filter"
  alt="filter">


<div class="img"></div>

<h3>SVG: converterd</h3>
<textarea id="svgOut"></textarea>