Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use SVG Sprite Sheet as CSS background-image while maintaining aspect ratio and scalability

Tags:

css

svg

TL;DR: I want to use several icons tiled in an SVG sprite sheet as CSS background-images, which maintain their aspect ratio and automatically scale to fill the parent element, using nothing but SVG and CSS. No JavaScript please.


So I have a spritesheet in SVG format, which I made with a combination of SVG-Edit and some hand-coding in Notepad++. Here's the source code:

<svg version="1.1"   xmlns:svg="http://www.w3.org/2000/svg"   xmlns="http://www.w3.org/2000/svg"   width="600"   height="400"   viewBox="0 0 600 400">   <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->   <title>chosen_sprite</title>   <g>     <title>Add</title>     <rect fill="none" stroke-width="10" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x="5" y="5" width="90" height="90" id="svg_1" stroke="#dcdcdc"/>     <line id="svg_2" y2="50" x2="70" y1="50" x1="30" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#00a00c" fill="none"/>     <line id="svg_3" y2="30" x2="50" y1="70" x1="50" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#00a00c" fill="none"/>   </g>   <g>     <title>Delete</title>     <rect fill="none" stroke-width="10" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x="105" y="5" width="90" height="90" id="svg_1" stroke="#dcdcdc"/>     <line id="svg_2" y2="70" x2="170" y1="30" x1="130" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#ff0000" fill="none"/>     <line id="svg_3" y2="30" x2="170" y1="70" x1="130" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#ff0000" fill="none"/>   </g>   <g>     <title>Expand Dark</title>     <rect stroke="#505050" id="svg_1" height="90" width="90" y="5" x="205" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="10" fill="none"/>     <line fill="none" stroke="#000000" stroke-width="12" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="round" x1="250" y1="65" x2="280" y2="35" id="svg_2"/>     <line fill="none" stroke="#000000" stroke-width="12" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="round" x1="220" y1="35" x2="250" y2="65" id="svg_3"/>   </g>   <g>     <title>Collapse Dark</title>     <rect stroke="#505050" height="90" width="90" y="5" x="305" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="10" fill="none" id="svg_4"/>     <line fill="none" stroke="#000000" stroke-width="12" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="round" x1="350" y1="35" x2="380" y2="65" id="svg_5"/>     <line fill="none" stroke="#000000" stroke-width="12" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="round" x1="320" y1="65" x2="350" y2="35" id="svg_6"/>   </g>   <g>     <title>Expand Green</title>     <rect fill="none" stroke-width="10" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x="405" y="5" width="90" height="90" id="svg_1" stroke="#dcdcdc"/>     <line id="svg_2" y2="35" x2="480" y1="65" x1="450" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#00a00c" fill="none"/>     <line id="svg_3" y2="65" x2="450" y1="35" x1="420" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#00a00c" fill="none"/>   </g>   <g>     <title>Collapse Green</title>     <rect fill="none" stroke-width="10" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x="505" y="5" width="90" height="90" id="svg_1" stroke="#dcdcdc"/>     <line id="svg_2" y2="65" x2="580" y1="35" x1="550" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#00a00c" fill="none"/>     <line id="svg_3" y2="35" x2="550" y1="65" x1="520" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#00a00c" fill="none"/>   </g>   <g>     <title>Search</title>     <circle id="svg_9" r="32" cy="140" cx="60" stroke-width="8" stroke="#000000" fill="none"/>     <line id="svg_11" y2="167.5" x2="32.5" y1="190" x1="10" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#000000" fill="none"/>   </g>   <g>     <title>Search 2</title>     <rect id="svg_10" stroke="#505050" height="90" width="90" y="105" x="105" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="10" fill="none"/>     <circle r="25" cy="142.5" cx="157.5" stroke-width="8" stroke="#000000" fill="none" id="svg_7"/>     <line y2="165" x2="135" y1="180" x1="120" stroke-linecap="round" stroke-linejoin="null" stroke-dasharray="null" stroke-width="12" stroke="#000000" fill="none" id="svg_8"/>   </g> </svg> 

It works fine and looks the way I want it to.

The problem is the CSS. Defining the cells in the spritesheet is a little bit messier than I would like it to be. Here's the page I'm displaying these icons in:

<!DOCTYPE html> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <html> <head> <style>  * {padding: 0px; margin: 0px; outline: 1px solid rgba(0,0,0,0.1);}  html {width: 100%; height: 100%;}  body {width: 100%; height: 100%;}  .svgSprite {     background-image: url('./svgicons/form_icons_sprite.svg');     background-repeat: no-repeat;     background-size: 600%; }  .svgSprite.add {     background-position: 0px 0px;     width: 12px;     height: 12px; }  .svgSprite.delete {     background-position: -16px 0px;     width: 16px;     height: 16px; }  .svgSprite.expandDark {     background-position: -24px 0px;     width: 12px;     height: 12px; }  .svgSprite.collapseDark {     background-position: -36px 0px;     width: 12px;     height: 12px; }  .svgSprite.expandGreen {     background-position: -48px 0px;     width: 12px;     height: 12px; }  .svgSprite.collapseGreen {     background-position: -60px 0px;     width: 12px;     height: 12px; }  .svgSprite.search {     background-position: 0px -12px;     width: 12px;     height: 12px; }  .svgSprite.search2 {     background-position: -16px -16px;     width: 16px;     height: 16px; }  </style> </head>  <body> <div class="svgSprite add"></div> <div class="svgSprite delete"></div> <div class="svgSprite expandDark"></div> <div class="svgSprite collapseDark"></div> <div class="svgSprite expandGreen"></div> <div class="svgSprite collapseGreen"></div> <div class="svgSprite search"></div> <div class="svgSprite search2"></div> </body>  </html> 

Basically, I want to know if there's an easier way to define the cells in the spritesheet and simplify the CSS I use to tell each div which icon to display from the spritesheet.

I would prefer that this solution be strictly SVG and CSS; I am not interested in using JavaScript libraries. I am aiming to get it to a point where I can simply define the cells and have the particular icon I'm aiming for automatically scale to fit its container, while maintaining its aspect ratio. Currently, in order to make the icon fit its parent container, its width and height need to be explicitly defined, and match the width and height of the parent container. If I change the width and height of the parent container, I need to change the background-position sizes as well.

Then, there's the problem of scaling. With this setup, the SVG scales to the appropriate size to be drawn on-screen, but if I decide to zoom using my browser's zoom, it pixelates. This is not how SVG is supposed to work.

I suppose I could just put each icon in its own file, because that seems to work wonderfully, but I just really like using sprites; it not only saves me several server requests, it's just cool.

I am aware of SVG Icon Loader. It's pretty cool, but it's one more JavaScript file that I would rather not rely on.

I've already read the w3 SVG docs, the MDN SVG docs, and the following threads on SO:

SVG & Spritesheets

Fit <svg> to the size of <object> container

Using SVG as background image

...but even after all that, I haven't managed to find a solution.

EDIT: I forgot to mention, this needs to work in IE9. That's a bit of an issue, I'm sure, but IE9's SVG support is decent, which is why I chose SVG for this project.

like image 225
Adrian Avatar asked Jun 08 '12 22:06

Adrian


People also ask

Can I use SVG as background image CSS?

SVG images can be used as background-image in CSS as well, just like PNG, JPG, or GIF. All the same awesomeness of SVG comes along for the ride, like flexibility while retaining sharpness. Plus you can do anything a raster graphic can do, like repeat.

How do I resize my background image to fit CSS?

The background-size CSS property lets you resize the background image of an element, overriding the default behavior of tiling the image at its full size by specifying the width and/or height of the image. By doing so, you can scale the image upward or downward as desired.

Can I use SVG Sprite?

SVG Sprite StacksIt can't be used in an <img> , <iframe> , <object> , or as a CSS background. The method works in all browsers including Internet Explorer 9 and above. SVG stacks are less popular today, because embedding SVGs directly into the HTML has become a best-practice technique.


2 Answers

If your icons have the same size you can do the following:

  1. Pack your icons into sprite horizontally (use svg-sprite if icons are in separate files).
  2. Set background-size: auto 100%; for your target selector.
  3. Set your target elements' width, height or font-size for scale.

.icon {      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="16" viewBox="0 0 64 16"> <circle fill="blue" cx="8" cy="8" r="8"/> <circle fill="red" cx="24" cy="8" r="8"/> <circle fill="yellow" cx="40" cy="8" r="8"/> <circle fill="green" cx="56" cy="8" r="8"/> </svg>');      background-repeat: no-repeat;      background-size: auto 100%;      display: inline-block;  }  .icon.small {      height: 1em;      width: 1em;  }  .icon.medium {      height: 2em;      width: 2em;  }  .icon.large {      height: 4em;      width: 4em;  }  .icon_1 {      background-position: 0 0;  }  .icon_2 {      background-position: 33.33% 0;  }  .icon_3 {      background-position: 66.67% 0;  }  .icon_4 {      background-position: 100% 0;  }
<span class="icon icon_1 small"></span>  <span class="icon icon_1 medium"></span>  <span class="icon icon_2 large"></span>
like image 118
Alexander Shutau Avatar answered Sep 29 '22 03:09

Alexander Shutau


Basically, I want to know if there's an easier way to define the cells in the spritesheet and simplify the CSS I use to tell each div which icon to display from the spritesheet.

No, you can't do it easier.

Try this article

Then, there's the problem of scaling. With this setup, the SVG scales to the appropriate size to be drawn on-screen, but if I decide to zoom using my browser's zoom, it pixelates. This is not how SVG is supposed to work.

In Chromium 18 it looks pretty fine - no pixelations at all.

In my test browsers list (FF3.6 Opera 9.2 IE6) I didn't see what I saw in Chromium

And about IE9, maybe problem in engine

like image 27
b1_ Avatar answered Sep 29 '22 03:09

b1_