I've just read this article from Google Web Fundamentals on using SVG for icons. Two approaches are given. The first is the inline approach. For example:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="32" height="32" viewBox="0 0 32 32">
<path d="M27 4l-15 15-7-7-5 5 12 12 20-20z" fill="#000000"></path>
</svg>
Demo
The second approach is to use an image tag such as: <img src="credit.svg">
I'm also aware SVGs can be defined as:
an object
an embed tag
an iframe
They can also be set as background images in CSS.
My question:
What are the advantages and disadvantages of each approach?
The advantages of using img, object, embed and other with SVG backgound:
<img class="icon icon_foo"> is much shorter and beautiful than inline SVG — <svg class="icon icon_foo"><use xlink:href="#foo"></use></svg>
The disadvantages of using img, object, embed and other with SVG backgound:
you absolutely have no any control of styling options — you cannot set color, outline width and outline color — all you can do is just to put SVG as background and set styles right in SVG file one time
you cannot reuse your icons because of statement above (if you have one icon in two-three-twenty different places on your web page and it should be colored in different colors the only way is to create two-three-twenty equal SVG files with only one difference — fill option ;)
The advantages of using inline SVG:
reusability (eg. you have only one Twitter SVG icon and you can use it everywhere with any styles, so, you do not need 2 or 3 same SVG files just with different fill attribute in each SVG file)
complete control over your icon appearance — styles, colors, sizes
The disadvantages of using inline SVG:
it is longer than IMG and does not look beautiful
Caching:
Using AngularJS or any other framework:
<ng-include src="'icons.html'"></ng-include>
Using VanillaJS:
file icons.js on your server:
var icons = '<svg><symbol id="icon-XXX" viewBox="0 0 256 256"><path d="{here_goes_compounded_path}"></path></symbol></svg>';
in HEAD section:
<script src="icons.js"></script>
<script>
document.getElementById('icon-placeholder').innerHTML(icons);
</script>
right after the BODY tag:
<div id="icon-placeholder" style="display:none;"></div>
Fiddle with the best approach:
Right after body tag place all your SVG icons (or use Angular's or any other framework ng-include):
<body>
<svg style="display:none;">
<symbol id="icon-XXX" viewBox="0 0 256 256"><path d="{here_goes_compounded_path}"></path></symbol>
<symbol id="icon-YYY" viewBox="0 0 256 256"><path d="{here_goes_compounded_path}"></path></symbol>
</svg>
In any place on your web page put an icon:
<svg class="icon icon_red">
<use xlink:href="#icon-XXX"></use>
</svg>
<svg class="icon icon_green">
<use xlink:href="#icon-YYY"></use>
</svg>
<svg class="icon icon_blue">
<use xlink:href="#icon-XXX"></use>
</svg>
In CSS:
.icon {
width: 16px;
height: 16px;
fill: currentColor;
}
.icon_red { fill: #f00; }
.icon_green { fill: #0f0; }
.icon_blue { fill: #00f; }
.icon_foo {
stroke: #000;
stroke-width: 8;
fill: #0f0;
}
Do not forget about magic "currentColor" CSS variable — it will color your SVG icon in current text color:
fill: currentColor;
Further must read:
any front-end RSS feeds: inline SVG is about a year old discussion
http://tympanus.net/codrops/2013/11/27/svg-icons-ftw/
http://tympanus.net/Tutorials/ResponsiveSVGs/index.html
http://ianfeather.co.uk/ten-reasons-we-switched-from-an-icon-font-to-svg/
The answer is, should you be worrying about who's looking at your code? SVG inline will always bloat your HTML depending how complex the drawn SVG is. Inline does in some respect have better performance implications due to the fact it's being loaded directly with the HTML as opposed to loading the SVG externally when using it, for example, as an <img>. However, it's practically unnoticeable and should be the least priority when coming down to performance.
The disadvantages of inline SVG:
Bloats code
Cannot be cached by the browser
No fallback available
IE, without XHTML (and that's if SVG is supported) doesn't support SVG technically, though this is a low priority and we shouldn't care about it in the current world of the web. Still, it's primarily a disadvantage to those ancient warriors.
The advantages of inline SVG:
I can't think of any, seriously. Other nosey people can see it?
Actually, those without CSS enabled can see it.
The disadvantages of using <img> with SVG:
Again, limited fallback support (You can use Modernizr to replace the .svg extension with .png, but then you rely on the user having javascript enabled)
Limited styling options with the SVG
The advantages of using <img> with SVG:
Semantically better than bloating all your code
Readability in your codebase is better for a) yourself and b) other developers who might be working on the project too.
Better maintainability.
Alternative methods:
You can use the following methods you provided in your question (and below) about using <object>, <embed> and <iframe>, although, these also limit the use cases and would need declaring in every HTML document, which can get messy as you progress in a project, whether it be large or small.
The 'better approach':
Disclaimer: By no means is the a 'better for everyone' method. This is simply how I would declare my SVG elements for reusability and gives me full control of identifying my assets when using SVG.
The biggest pitfall for using inline SVG in your HTML is the fallback support. I have always used SVG as a background image (unless it's a webfont I'm using for icons etc.), purely because I can create a .png version and write the fallback in my CSS, like so:
.icon {
display: inline-block;
vertical-align: baseline;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
}
.icon--16 {
width: 16px;
height: 16px;
}
/* Always declare the PNG before the SVG. */
.icon--foo-blue {
background-image: url('foo-blue.png'); /* Fallback */
background-image: url('foo-blue.svg'), none; /* Modern */
}
.icon--foo-green {
background-image: url('foo-green.png'); /* Fallback */
background-image: url('foo-green.svg'), none; /* Modern */
}
Then, use it:
<span class="icon icon--16 icon--foo-blue"></span>
<span class="icon icon--16 icon--foo-green"></span>
The disadvantage of this:
No support for CSS styling, however, we should be aware of why we are styling SVG in our CSS. If you're using more colours than your website uses then there is generally something not right. Declaring 4 files for the same SVG but in a different colour is not bad practise as it allows us to cache these in the browser for later, throughout any webpage which in the answer constructed below completely removes this purpose. Take advantage of caching on the server/within the browser!
And of course, it depends what kind of image you are trying to render to the user. If you're going to use large SVG files for showcasing something, for example, I would probably think about what kind of users I'm targeting; that being, those on modern browsers because I might want to include fancy inline SVG animations etc.
It really depends on personal preference too. There is no major red flags for using SVG inline to this day, but we still have some heart for those back in the stone ages of less-than IE9. And, not to forget, Android is quirky with SVG too!
EDIT: It seems like there is a large debate over inline SVG's. One word of warning is, there is no reusability doing this. If you're showcasing a fancy website with fancy SVG animations, then by all means, build your entire page in SVG for crying out loud. No one is stopping you. It's personal preference. But for icons, where you will more than likely reuse throughout a project, please, declare them outside your HTML as it gives you greater control in the long run and not because you need a different colour here and there. Think about it. :)
What I tend to do nowadays is wrap it inside a Web Component:
/assets/svg/hex.js
export default `<svg>...</svg>`;
/my-svg.js
class MySvg extends HTMLElement {
connectedCallback() {
const id = this.getAttribute('id');
import(`./assets/svg/${id}.js`).then(svg => {
this.innerHTML = `
${svg.default}
`;
});
}
}
customElements.define("my-svg", MySvg);
index.html
<my-svg id="hex"></my-svg>
This way I get the advantages of inline svg, lazy loading due to dynamic import and without the bloat in the code I'm supposed to work on / maintain, with just one custom element. And the id attribute makes clear which svg I'm loading.
Related
Bootstrap has a nifty way of adding an icon:
<i class="bi bi-arrow-right-square-fill fs-1"></i>
It's really nice cause I can add in the fs-1 class to control the sizing of the icon automatically.
I have my own icon in an SVG file, and would like to do the same:
<i class="bi my-custom-icon fs-1"></i>
But I cannot figure out how to add my svg file to the element in css. I looked at the bootstrap code and they just had this:
.bi-arrow-right-square-fill::before { content: "\f136"; }
Can someone explain an example on how to load an svg file into a <i> HTML tag?
Bootstrap is doing this via an icon font; the glyph in that font at codepoint \f136 in that font will be the icon instead of a normal letter glyph.
Creating or modifying icon fonts can be a bit fiddly; there are some web-based tools to make it a little simpler than working in a font editor, but in the long run I wouldn't recommend it. (They're a pain to maintain, since you have to regenerate and version the font every time you make a change, and cause significant problems for accessibility.)
Instead you could convert your SVG into a data:image URI and put that in your CSS, using whatever method is most convenient for your specific layout, for example
.foo::before {
content: url('data:image/svg+xml;utf8,<svg ... </svg>')
}
or
.foo {
background: url('data:image/svg+xml;utf8,<svg ... </svg>');
width: ..., height: ...
}
...or, of course, just host the SVG at a real URI and embed it using CSS as you would any other image.
I've seen several examples online showing how to import SVG images into HTML and style them using CSS contained in a separate CSS. However, things get tricky when it comes to dynamically styling SVG.
Let's say I have an application that supports different themes. A user can swap themes while the application is running. For our purposes here, the theming mechanism is encapsulated somewhere. Whether it uses custom properties or some parsing technique is irrelevant. We shouldn't try any funny stuff to work around the mechanism. No JavaScript.
I have the following styled SVG I want to display, such as for a button icon:
<?xml-stylesheet type="text/css" href=".path/to/styles/app.css" ?>
<svg>
<circle class="left-circle" cx="33" cy="50" r="33" fill="red" />
<circle class="right-circle" cx="66" cy="50" r="33" fill="green" />
</svg>
The SVG is imported in the HTML using an <object> tag. The HTML uses the same stylesheet as the SVG:
<link rel-"stylesheet" href=".path/to/styles/app.css" data-theme-hook="true"/>
<button>
<object type="image/svg+xml" data="./path/to/images/button.svg"></object>
<span>The Button</span>
</button>
The data-theme-hook attribute is what tells the theming mechanism to work its magic. The CSS looks like this:
button {
color: black; /* Theming mechanism knows how to swap */
background-color: white; /* Theming mechanism knows how to swap */
}
.left-circle {
fill: green; /* Theming mechanism knows how to swap */
}
.right-circle {
fill: red; /* Theming mechanism knows how to swap */
}
Now, this all works pretty well. I start up my application, and I see my button with the SVG image in it. The trouble occurs when I swap themes. Everything in the HTML proper changes colors as expected. But the SVG elements' colors remain fixed, perhaps because the SVG is in a separate document, and the styles imported via xml-stylesheet are static....
To me, this sounds like this should be a pretty straightforward scenario. Am I just doing things wrong? Or is this just not possible without JavaScript interference?
Problem:
On page load, for a button, SVG which is being called in CSS background appears. On some event, I apply a class and to it call another SVG in CSS background. However that new SVG does not appear. If in debugger, I try to toggle the background, it then starts appearing.
What am I missing to get this working in IE11?
In Below picture, we are using <button> tag. On some event, the background SVG is changed.
CSS Code
.buttonIcons{
.svgicon-fields-add{ background-image:url('../assets/themes/svg/arrow_active.svg');
width: 16px;
height: 16px;
}
On disabled state, below is the CSS
.buttonIcons.disabled{
.svgicon-fields-add{ background-image:url('../assets/themes/svg/arrow_disabled.svg');
width: 16px;
height: 16px;
}
So initially all buttons are disabled, but if you see the first button needs to show active svg image, but it is not appearing. I can see it in IE11 console, and if in console, I toggle the property, it starts appearing.
The code works fine in Chrome
Thanks in advance for all the suggestions.
SVG file code
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="16" 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"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<polygon fill="#00a6a0" points="7.6,1.6 6.7,2.4 12.3,8 6.7,13.6 7.6,14.4 14,8 "/>
<polygon fill="#00a6a0" points="2.6,1.6 1.7,2.4 7.3,8 1.7,13.6 2.6,14.4 9,8 "/>
</g>
</svg>
Based on the small number of views, this is an esoteric case, but I'm having the same problem. I've been on it for days. I also had a double problem where IE would not display the background on toggling the style in developer tools.
I thought it was initially a memory issue, where IE was allocating memory for the icon but not actually putting anything there.
So I applied the SVG as an xml-encoded string directly in the class itself. This solved one instance of an SVG background not appearing, but it didn't solve the problem mentioned here. Furthermore, this allowed me to see the toggle event you mentioned.
So I now believe that it is NOT a memory issue, but an issue with IE10/11's SVG rendering engine. The fact that this problem only happens with certain SVG's reinforces the idea that the IE rendering engine is crapping the bed with certain inputs. It also only happens when a CSS SVG background is being overridden by another class's SVG background.
I thus tried setting the object to display:none, then display:block in the hopes of forcing a re-render of the element. This didn't help. I destroyed the element then rebuilt it and appended it back where it was supposed to go. That didn't work.
To make things even more confusing, I was never able to replicate the problem locally. It would only ever manifest in certain environments, leading me to believe that it is a combination of the browser and some server settings. I have no idea what.
Regardless, the point is that IE is remarkably resilient it not re-rendering what it has rendered. And since the SVG engine is apparently separate from the DOM rendering engine, screwing around with the DOM will have no effect on what the SVG renderer has stored. You have to give it quantifiably different data to force the SVG engine to re-render.
The only solution I found was to have my two CSS classes then give IE different image data than what it found on page load.
.Class1 {
background-image: url("data:image/svg+xml,image-data...");
}
.Class2 {
background-image: url("data:image/svg+xml,image-data...");
}
These classes allow default page load states to be covered. Then when JS events change the appearance, instead of changing the class, assign an in-line CSS style with the XML-ified SVG image data with a slight difference. Anything will work. I used an extra space.
onclick="function(){
element.style.backgroundImage = "url(\"data:image/svg+xml,slightly-different-data...\")"
}
To reiterate, it is an SVG rendering problem that occurs on page load. You can force SVG to re-render the image by giving it slightly different data in-line. I would imagine that you could do the same thing with a third class that contains a reference to a slightly different SVG file from the initial one, but I didn't do this.
Make sure the SVG file has the width and height property.
And if there's a 'responsive' option in it, you should remove it.
The option resets the CSS width and height.
I am trying to manipulate an external .svg file via CSS.
HTML
<body>
<div class="mysvg">
<img src="decho.svg" alt="decho" width="200px"></img>
</div>
</body>
CSS
div.mysvg img {
opacity: .3;
transition: opacity 1s linear 0s;
}
div.mysvg img:hover {
opacity: 1;
}
This code works for opacity, but not for fill or other svg specific attributes like stroke. I am aware I can't do that with an img tag, but I've been looking for hours and I can't find the correct way to do it with svg or object.
So basically, my questions is, how do I achieve the same result as the code which I linked, but to be able to manipulate fill, stroke etc. properties and it must be an external file, not just an inline svg code pasted in the html.
If someone is able to show me the correct way to do it, I'd be most grateful. Thanks.
EDIT:
I managed to do it by adding a css inside the .svg file itself. It must be right after the svg opening tag.
<svg ...>
<style type="text/css" media="screen">
<![CDATA[
g {
fill: yellow;
stroke: black;
stroke-width: 1;
transition: fill 1s linear 0s;
}
g:hover {
fill: blue;
}
]]>
</style>
<g>
<path ...>
</g>
</svg>
You also need to insert it as an object in the html, otherwise it won't work.
<object data="decho.svg" type="image/svg+xml">
Hopefully this helps to someone looking for an answer like mine in future. This is what helped me http://www.hongkiat.com/blog/scalable-vector-graphic-css-styling/.
This is in my opinion the greatest flaw in svg: sandboxing.
Svg files are sandboxed: in their own document, which is why a typical 'fill:' style will not apply. Likewise, the css you write in your svg will not apply to the rest of your site.
Adding css directly to an svg: Not a good solution as you will end up rewriting the css in every svg you use.
The real solution: An "icon-system". Svg font-face or svg sprites. Read more about them here.
The reason opacity works: Opacity applies to the svg object/frame itself, not the contents of the svg (which are inaccessible).
I should also note that no matter how you load those svg's, inline, by reference, in an object, as a background, you will not be able to get inside the sandbox. This is why converting them to a font or using sprites is necessary for using hover, focus, and other effects/transitions.
This is possible providing the SVG is hosted on the same domain (thanks #FabienSnauwaert) and it does not have a fill colour defined on itself, and you do not contain a parent selector within the CSS. For example:
I have the following files:
icon-sprite.svg (my external sprite of SVGs)
buttons.scss
test.html
icon-sprite.svg
I have omitted the other SVGs for clarity.
<svg xmlns="http://www.w3.org/2000/svg" style="width:0;height:0;visibility:hidden;">
<symbol viewBox="0 0 1500 828" id="icon-arrow-3pt-down">
<title>arrow-3pt-down</title>
<path d="M1500 0H0l738.9 827.7z"/>
</symbol>
</svg>
test.html
<button class="button--large">
Large button
<svg class="svg" width="20px" height="20px">
<use xlink:href="icon-sprite.svg#icon-arrow-3pt-down"></use>
</svg>
</button>
buttons.scss
.svg {
fill: red;
}
This would not work if I was to use body .svg due to shadow DOM boundaries.
See this CSS Tricks article for more info
I recently ran into this. While SVGs are not part of the DOM for some arbitrary reason, you can move them to the DOM with a bit of javascript:
<object type="image/svg+xml" data="illustration.svg"
onload="this.parentNode.replaceChild(this.contentDocument.documentElement, this);">
</object>
This will replace the <object> with an inline after it has loaded. In case javascript is disabled, it falls back to an <object> tag, and the svg will not be themed. In my case, the styling was for a javascript-controlled dark theme, so having the correct fallback means no theming issue.
Other options considered (xlink is a good one for sprites):
Use an external library to load svgs inside the DOM (the above js is simple enough IMO)
use svg filters for chroma-keying. That makes svgs more complex to edit, might use more resources to perform the filtering, and is less flexible.
Note that I am not sure of the security implications, better save this for files you control.
Unfortunately, there's no built-in feature in Web Standards that makes it possible. SVG Symbols is an option but doesn't work with files hosted on CDNs. Additionally, you need to ensure that SVG files are defined properly to make use of <use> tag.
I have created a library, svg-loader, that makes it easier to achieve this. It uses Javascript but it's only 3kb and it's loaded asynchronously, so the impact on performance is negligible. It's plug 'n play, so you don't need to do anything except including the <script> tag.
Here's a Codepen example.
Usually the browser renders the svg images properly but in chrome the browser doesn't detects the css3 scale and return a poor image. This must be because css scale is a new technology... Is there any way to select the render quality?
Sorry for my english, I'm studying it!
There are a couple of workarounds for this, none as easy as setting a render quality directive, I'm afraid. The most straightforward workaround would be inserting the actual SVG instead of using it inside an img tag.
Your html would look like...
<svg>
...
</svg>
And your css would look like...
svg {
-webkit-transform: scale(2);
}
Here is a demo: http://jsfiddle.net/EgsGe/