Compass / compass

Compass is no longer actively maintained. Compass is a Stylesheet Authoring Environment that makes your website design simpler to implement and easier to maintain.
http://compass-style.org
Other
6.72k stars 1.18k forks source link

SVG Support for Image Sprites #685

Open Snugug opened 12 years ago

Snugug commented 12 years ago

I'd be very interested in SVG support for Compass Image Sprites, so PNGs would output a PNG file and SVGs would output an SVG file. There are a few unique things that can be accomplished with SVG Image Sprites that can't be as easily accomplished with PNGs, including infinite scaling using background-size.

chriseppstein commented 12 years ago

I don't know enough about SVG to know how to assemble a unified sprite sheet. But in theory it would just mean snagging the contents of the svg files and munging them together so it seems doable. Also not sure what the implications for the generated css would be. If you can outline this in detail I will consider this for a post 0.12 feature.

Snugug commented 12 years ago

So generating an SVG sprite is actually fairly easy. SVGs are, at their core, XML files, so as you can parse an XML file, you can make an SVG Sprite. Here's the actual XML of an SVG file:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
    <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
     x="0px" y="0px" width="69px" height="64px" viewBox="0 -0.166 69 64" overflow="visible" enable-background="new 0 -0.166 69 64"
     xml:space="preserve">
<defs>
    <symbol  id="Home" viewBox="-34.731 -31.834 69.462 63.668">
        <path d="M34.73,5.319l-12.597,9.617v15.908h-9v-9.038L0,31.833l-34.73-26.513l5.461-7.155l3.741,2.856v-32.856H25.53V1.021
            l3.74-2.855L34.73,5.319z M20.53-26.834H9v19H-9v-19h-11.529V4.204L0,19.88L20.53,4.204V-26.834z"/>
    </symbol>
</defs>
<use xlink:href="#Home"  width="69.462" height="63.668" x="-34.731" y="-31.834" transform="matrix(1 0 0 -1 34.731 31.8335)" overflow="visible"/>
</svg>

The key things that are going to change when adding one SVG into another is the height/width definition in <svg> and the <symbol> and <use> tags. Let's say we want to add the following SVG into this Home SVG:

<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     width="64px" height="64px" viewBox="-2.731 0 69.461 128" enable-background="new -2.731 0 69.461 128" xml:space="preserve"
    >
<symbol  id="Save" viewBox="-32 -32 64 64">
    <path d="M21.239,32H-32v-64h64v53.522L21.239,32z M8.574,25V7.285h-7V25H8.574z M25-25h-50v50h8.858V0.283h31.717V25h2.819
        L25,18.568V-25z"/>
</symbol>
<use xlink:href="#Save"  width="64" height="64" x="-32" y="-32" transform="matrix(1 0 0 -1 32 32)" overflow="visible"/>
</svg>

What we need to do is change the height (and if the width was wider, width) of the <svg> tag, add the <symbol> tag in as-is, and add the Save SVG's <use> tag with the x/y edited to position it correctly. Added bonus, because <use> is the canvas you're drawing the symbol on, you've got the full dimensions of the symbol right there! Anyway, so here's the final SVG image sprite:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     width="69px" height="128px" viewBox="-2.731 0 69.461 128" enable-background="new -2.731 0 69.461 128" xml:space="preserve"
    >
<symbol  id="Home" viewBox="-34.731 -31.834 69.462 63.668">
    <path d="M34.73,5.319l-12.597,9.617v15.908h-9v-9.038L0,31.833l-34.73-26.513l5.461-7.155l3.741,2.856v-32.856H25.53V1.021
        l3.74-2.855L34.73,5.319z M20.53-26.834H9v19H-9v-19h-11.529V4.204L0,19.88L20.53,4.204V-26.834z"/>
</symbol>
<symbol  id="Save" viewBox="-32 -32 64 64">
    <path d="M21.239,32H-32v-64h64v53.522L21.239,32z M8.574,25V7.285h-7V25H8.574z M25-25h-50v50h8.858V0.283h31.717V25h2.819
        L25,18.568V-25z"/>
</symbol>
<use xlink:href="#Home"  width="69.462" height="63.668" x="-34.731" y="-96" transform="matrix(1 0 0 -1 34.731 31.8335)" overflow="visible"/>
<use xlink:href="#Save"  width="64" height="64" x="-32" y="-32" transform="matrix(1 0 0 -1 32 32)" overflow="visible"/>
</svg>

Then just save as .svg

chriseppstein commented 12 years ago

Pinging @johnbintz as he knows a lot about SVG -- I think the devil's in the details on this.

Snugug commented 12 years ago

Sounds good. As a bit of backstory, @simurai and I went back and forth on Twitter last night about SVG Image Sprites and some of the pitfalls of using SVG in CSS, namely that if the resulting size of a straight line winds up being 0<x≤1px, the line gets blurry. Not only would SVG Image Sprites be just good to have, but having the compiler be aware of the actual size of the image (like it can currently do w/PNG Image Sprites) could go a long way to alleviating that pain and potentially preventing it all together using some basic math that already exists (like floor/ceiling).

chriseppstein commented 12 years ago

I caught a bit of your conversation on twitter. I think this can be a good addition to compass, but we need to know details about how to avoid these issues, how the generated css would differ, etc. Is there any open source tool that sprites svg files? It'd be great to read their code or even reuse it (if it's ruby)

Snugug commented 12 years ago

There's nothing I can find (admittedly a quick search) that currently does SVG sprites (every web SVG library I know of off the top of my head are all focused on drawing SVGs, not combining/working with existing SVGs). With that being said, most of the CSS would be the same as it currently works: if you want to have it be the same size as the image, just use it in the background url definition:

background-position: x y;

If you want to resize it, CSS3 background-size will let you resize it. The biggest issue we would have is, for background-size, running that through some checks in the back end of compiling to make sure we don't get partial pixels.

Now that I think of it, Compass support for SVGs would make it easier to do things like style your SVGs based on the variables/mixins currently being used throughout your Sass file as SVGs can be styled with CSS: http://www.w3.org/TR/SVG/styling.html#StylingWithCSS

Anyway, that last part is kind of pie in the sky, but in my experience w/SVG, an SVG Image Sprite works exactly the same way as a PNG one does, but is better for resolution-independent design.

chriseppstein commented 12 years ago

Ping @mental, can you offer any insights or pitfalls to be aware of?

Snugug commented 12 years ago

Was there anything else done with this? Is there something I can do to help move this along w/my basically non-existent knowledge of Ruby? Or should I learn Ruby and write a patch?

chriseppstein commented 12 years ago

Really just need someone to prototype and test the svg construction. That can be done in any language.

Hunt & pecked on my iPhone... Sorry if it's brief!

On Feb 14, 2012, at 8:15 PM, Snugugreply@reply.github.com wrote:

Was there anything else done with this? Is there something I can do to help move this along w/my basically non-existent knowledge of Ruby? Or should I learn Ruby and write a patch?


Reply to this email directly or view it on GitHub: https://github.com/chriseppstein/compass/issues/685#issuecomment-3974766

Snugug commented 12 years ago

Sounds good, I'll give it a go later this week.

xjamundx commented 12 years ago

Hey this sounds like exactly what I need right now!

metaskills commented 12 years ago

My comments may be more noise vs signal, but I've been doing some reading on SVG sprites lately. I found these articles interesting.

http://simurai.com/post/20251013889/svg-stacks http://www.eleqtriq.com/2012/01/enhancing-css-sprites-and-background-image-with-svg/ http://coding.smashingmagazine.com/2012/01/16/resolution-independence-with-svg/

xjamundx commented 12 years ago

FWIW, I've been favoring Base64-encoded SVG lately over sprites, because the positioning of sprites I found to be difficult. There are definitely some cons of the base64 approach, but general it's easier and sass has some built-in ways to inline files :)

metaskills commented 12 years ago

@xjamundx I would be interested in knowing how you style your SVG files, if at all. I have found that using inline-image() or Rails asset-data-url means that I can not style the SVG's using an externally stylesheet. I have also found that external stylesheets fail when using object or embed'ed SVG files as assets. There are many hacks that even place your external stylesheet into the SVG document. Here lately I have been using jQuery SVG and some custom CoffeeScript to make things happen for me. That said, much of that topic is a diversion from this thread. But I do think there are a ton of janky ways to dealing with SVG's and as of yet I have not reached a conclusive picture of a best practice when using with sass/compass, CSS in general.

xjamundx commented 12 years ago

Thanks @metaskills I'm by no means an SVG expert and didn't even think about styling them. Super interesting discussion. I feel like SVG is sorely under-utilized and under-documented, but I imagine its hey day is coming soon with the rise of retina displays and the like. Best of luck to you in figuring out the optimal way to use SVG with Sass. It will come together eventually :)

metaskills commented 12 years ago

I have been gathering more information on a this topic and wanted to followup. An emergent technique that I really like has been dubbed SVG Stacks. This basically relies on composing an SVG document with multiple <svg> fragments and linking to that fragment in the IRI/URI. This combined with CSS in the SVG document that hides all fragments and shows only the target fragment using the :target pseudo selector is what makes SVG stacks work.

The only problem I have found is that WebKit and hence Chrome do not support SVG fragment identifiers in CSS background images. To that end, I have filed a Bug 91790 to help resolve this. From the original article, there is already a bug filed on Opera. So assuming this has legs and the bugs resolved, it would be technically possible to compose an SVG document with multiple fragments. See the source of the demo SVG for a structural example.

the8472 commented 12 years ago

I have found that using inline-image() or Rails asset-data-url means that I can not style the SVG's using an externally stylesheet.

Indeed, it would be awesome if you could generate a SVG, with inline styles colored based on color definitions in some SASS files. Then embed them directly via data URIs in a stylesheet.

But that would require a way to preprocess an SVG image, import SASS variables into inline styles and then turn the whole thing into a data URI to be included in another SASS file. That way you could just change a single variable from one color to another and all your icons would get adjusted too.

metaskills commented 12 years ago

You can style an SVG if it is embedded in your document. I have found using jQuery.SVG as my go to for that. I have not done any testing with a base64 encoded SVG utilizing an external stylesheet.

the8472 commented 12 years ago

How good is the browser support for linking to an SVG fragment in the main document? svg in data uris works in IE9.

Snugug commented 12 years ago

SVG Fragments only work in Firefox, so, unfortunately, that's not a viable solution.

metaskills commented 12 years ago

SVG Fragments only work in Firefox, so, unfortunately, that's not a viable solution.

To be specific for those reading... I "believe" fragments work in all the latest browsers when viewing a raw SVG, embedded in the document, or linked via object/embed. The place I have found that only Firefox supports is identifier fragments when used in CSS backgrounds and the like, hence making the SVG stacks approach moot till that is solved.

the8472 commented 12 years ago

Ah, what I actually had in mind was the element() function, but that's firefox-only too, it seems. And embedding the SVG images into the main document wouldn't seem all that clean anyway since they won't benefit from caching.

So, for the goal to easily style SVG icons and color them with the same color schema as in your CSS in you can just generate a svg.css to use in your SVG-icons which imports the same color definitions partial as your main stylesheet. Then directly include them via data URIs into the CSS.

gzip compression should mostly take care of the base64 encoding bloat and redundancy created by browser prefixes.

So do we even need SVG sprites?

kaij commented 12 years ago

I have been following this discussion with great interest and have successfully used compass with CSS inline SVG (using inline-image). However, I think this will be a problem when using a lot of / large files: the CSS will get larger. As the rendering engine waits until the CSS has finished loading, you'll get a noticeable delay when viewing the page - especially on slow connections. If the CSS contained the url of a SVG sprite, the CSS would finish loading quickly and the SVG sprite will be loading in the background (while the page is already displayed).

I think this is a strong argument in favor of SVG sprites, at least as long as SVG stacks cannot be broadly used.

fschroiff commented 11 years ago

SVG support would ideally work like Grunticon does:

Quote from their description page:

  1. All of the icons inline in the CSS as vector SVG data URLs,
  2. All of the icons inline in the CSS as PNG data URLs,
  3. All of the icons referenced externally as PNG images, which are automatically generated from the source SVG and placed in a directory alongside the CSS files.

https://github.com/filamentgroup/grunticon

Snugug commented 11 years ago

You've missed the most critical part, that those are three separate CSS files. I don't think that's ideal.

For me, what I think a better Compass solution would be the ability to generate PNGs at specific sizes from SVG and even sprite those together, so in your Sass you could do something like this (assuming a Modernizr-like feature detection, user decision):

.home {
  .svg & {
    background-image: inline-image('icons/home.svg');
  }
  .no-svg, .no-js {
    @include svg-png('icons/home.svg', 25px, 25px);
  }
}

Which would output a Data URI for .svg .home and a sprite based on dir icons' that includes a PNG at 25px width, 25px height with all of the normal Sprite accouterment. Two additional parameters forsvg-pngwould include$inline: falseand$sprite: true` to allow for the sprite to be inlined, the sprite to be external, the image to be individually linked, or the image to be individually inlined. This would give users the most amount of flexibility about how the inlined items work w/o generating multiple CSS files unless a user specifies that they want them.

fschroiff commented 11 years ago

I meant "ideal" in the sense that it

How you structure your CSS should be up to you.

benfrain commented 11 years ago

I was recently pointed in the direction of https://github.com/astraw/svg_stack - that manages to take a bunch of SVG images and make them into a single SVG image. Purely theoretical but perhaps...

Given a folder full of SVGs, Compass could create a single SVG (using above magic) and then render that single SVG as a PNG image sprite. That image sprite could then be used in the traditional automagic Compass manner as a fallback PNG image sprite complete with positions class names etc.

Extending this riff, a variable could perhaps be passed for @1x and @2x variants of the PNG image sprite (if necessary - although I would presume any device capable of benefiting from a @2x PNG could render an SVG correctly anyway).

Obviously imagining such fun is a whole lot different than actually implementing it and sadly my mind it too tiny to help in any more meaningful way.

benfrain commented 11 years ago

Update Nov 2013

I removed this enormous comment as it was asking for data-URI for the output. There's a growing body of evidence to suggest that in some instances this can essentially be an anti-pattern. E.g. http://www.mobify.com/blog/css-sprites-vs-data-uris-which-is-faster-on-mobile/

scottdavis commented 11 years ago

First your going to need a pure-ruby svg library that's active and supports output to png's

benfrain commented 11 years ago

Scott, yes, it's certainly a little embarrassing for me to write all that stuff and have nothing to contribute in terms of actual real working code so my apologies. Perhaps until a pure-ruby SVG library makes itself known using Grunticon or a bash script with Inkscape is the only realistic option.

I presume (as I know nothing of Ruby beyond Sass/Compass) a pure-ruby svg library is as rare as rocking-horse poo?

scottdavis commented 11 years ago

I'm not sure I have never looked into it but, that would be at the core of this feature.

pietschy commented 11 years ago

Does it need to be pure ruby or is ok to have dependencies as long as it supports *nix and Windows?

A quick google found https://github.com/minimagick/minimagick which has the option of GraphicsMagick (http://www.graphicsmagick.org/). It claims to multiplatform and free from installation conflict issues (although I've never tried).

scottdavis commented 11 years ago

It depends on if they need to compile anything we want compass to run on a fresh install on any os

Sent from my iPhone

On Mar 4, 2013, at 7:11 PM, Andrew Pietsch notifications@github.com wrote:

Does it need to be pure ruby or is ok to have dependencies as long as it supports *nix and Windows?

A quick google found https://github.com/minimagick/minimagick which has the option of GraphicsMagick (http://www.graphicsmagick.org/). It claims to multiplatform and free from installation conflict issues (although I've never tried).

— Reply to this email directly or view it on GitHub.

metaskills commented 11 years ago

FYI, news today on SVG Stacks front being shot down. Excerpt from the webkit bugzilla below. I don't suspect this adds much to the conversation in regards to moving toward a solution, but did want to followup.

SVG stacks will not be supported for CSS properties taking CSS Image values. This includes, but is not limited to, background-image, mask-image, border-image.

This is a resolution of the SVG and CSS WG to differ between resources (like SVG gradients, masks, clipPath) > and image values during parse time of CSS. This is a security requirement to protect the users privacy and safety.

See following discussions for further information: http://lists.w3.org/Archives/Public/www-style/2012Oct/0406.html http://lists.w3.org/Archives/Public/www-style/2012Oct/0765.html

As an alternative, use SVG image sprites. They work exactly like normal image sprites.

For CSS properties: Short-term: use background-position to shift your sprites Long-term: the fragment identifier #xywh=0,0,100,100 will allow to get a sprite out of an image.

For image element: Short-term: use the fragment identifier #viewbox(0,0,100,100) Long-term: the fragment identifier #xywh=0,0,100,100 will allow to get a sprite out of an image.

pietschy commented 11 years ago

I'm not sure if @StanAngeloff would be able to shed any light here. He's the developer of compass-magick and compass-canvas which itself uses the cairo gem. He's also used http://phantomjs.org/ (a headless webkit renderer with a javascript api) and might have some insights. Once again I haven't checked out the installation requirements on all these but figured it'd be worth mentioning them.

StanAngeloff commented 11 years ago

@pietschy: Cairo is a b*itch to compile on anything not Linux based (this is my experience so far). Getting it to run on Windows/Cygwin is next to impossible.

SVG -> PNG can be accomplished by using Compass callbacks and CSS post-processing. It should be possible to capture all 'image.svg?rasterize' references, extract the width/height from properties, build a script for Phantom to process and inline the resulting PNG from the headless browser.

My 2¢.

xjamundx commented 11 years ago

I wanted to point out for those worried about in-lining their SVG into CSS making their CSS slow to load that they can just put all of their SVG in a separate CSS file that loads after the main css file. This again would be the same number of downloads as a separate sprite, but the workflow is way simpler by just using inline-data() on individual files instead of having designers building a sprite sheet.

I guess the downside is that it really doesn't work without a pre-processor, but I figure most of us are pretty used to that nowadays anyway ;)

Also to the question about styling SVGs in stylesheets or external files. I really wish that were possible. The closest I can find to that is this approach, which could probably be modified in some way to support the SVGs coming from Data-URIs: http://www.somerandomdude.com/2012/08/12/svg-css-injection/

I've been following various other threads about SVGs, but it just doesn't seem like anyone is particularly talking about styling external SVGs with CSS. Maybe it's totally not allowed, but it seems like if the SVG is base64 encoded into the CSS, then at least that same CSS file should be able to style it. Anyone know what the deal is there?

scottdavis commented 11 years ago

Im going to remove the png check from the sprite importer itself and move it into the engine. As a validate method. that way you can then create a svg engine for sprites if you want as an extension.

metaskills commented 11 years ago

I've been following various other threads about SVGs, but it just doesn't seem like anyone is particularly talking about styling external SVGs with CSS.

I have been doing this, but only using an older Library like jQuery SVG to download and inline the SVG documents. When the SVG document is part of your document, then it can be styled. There may be other cases too, but that solution means the SVG has nothing to do with the topics we have here. Which is sad, because using stacked or sprite'ed SVGs and styling them would be awesome.

the8472 commented 11 years ago

Can't one simply just put a stylesheet link into the data-uri-svg?

css -> embeds data-uri -> references special CSS file just for SVGs -> can import color definitions from a common partial

Snugug commented 11 years ago

For in line SVGs, you can link external stylesheets, but linked stylesheets for SVGs used inside stylesheets, support for linked stylesheets is generally non-existent or very sparsely supported with no good way to test for compatibility.

On Mar 21, 2013, at 10:34 AM, the8472 notifications@github.com wrote:

Can't one simply just put a stylesheet link into the data-uri-svg?

css -> embeds data-uri -> references special CSS file just for SVGs -> can import color definitions from a common partial

— Reply to this email directly or view it on GitHub.

jlong commented 11 years ago

Here's an interesting article about PNG fallbacks for SVG backgrounds without using a "no-svg" class:

http://signaltower.co/2013/02/25/add-png-fallbacks-for-svg-files/

benfrain commented 11 years ago

@jlong that's certainly interesting. I'd be interested to know if the invisible gradient that's part of the technique effects paint times. Will take a look at this with dev tools and see what difference it makes. If it's trivial (or non existent) then it's probably just wise to double check the correlation between browsers. For example:

A large fly in the ointment is that IE doesn't support gradients (but does support SVG). There's also support for gradients in Android 2.0 - 2.3 (but no SVG support) so that could be an issue too? Don't think that's reliable enough IMHO.

the8472 commented 11 years ago

If you want to target IE9 too you could use the CSS3 four-value background positioning syntax instead of transparent gradients.


#myelement {
  // fallback for old browsers
  background: <png> 
  // css3 positioning
  background: <svg> top left / auto auto no-repeat padding-box;
}

That would only leave the ancient-android-browser issue.

benfrain commented 11 years ago

@the8472 that's a good workaround thought. Perhaps there will be some CSS property that could pick out old Android too but I have to be honest and say personally I'd continue using Modernizr until supports is widely implemented.

jcc8 commented 11 years ago

Bounty for this here if anyone else motivated to chip in: https://www.catincan.com/bounty/svg-support-image-sprites-issue-685-chriseppstein-compass-github

Crowdfund issue & get merged into main branch to collect.

rachelnabors commented 11 years ago

Want. I wanted to use an SVG spritesheet with Compass for an article I'm writing. Unfortunately, I didn't realize that Compass only makes PNG sheets. Now I am full of malaise.

jlong commented 11 years ago

@rachelnabors Just found Iconizer, which creates SVG sprite sheets: https://github.com/jkphl/iconizr

benfrain commented 11 years ago

@jlong good find. I'll definitely take a look at that. You tried it yet?

jlong commented 11 years ago

No :)

--John

On Aug 25, 2013, at 3:55 PM, Ben Frain notifications@github.com wrote:

@jlong good find. I'll definitely take a look at that. You tried it yet?

— Reply to this email directly or view it on GitHub.