Using SVG as Icon System

A few days ago, Chris Coyier published an article "Icon System with SVG Sprites" talking about how to utilize SVG Sprites as an Icon System.

After reading this, I've done some experiments on this topic.

Limitation of CSS Styling

By using inline SVG, it is able to style parts of icons with CSS, but it doesn't apply to the content of <use> tag, according to spec:

CSS selectors only apply to the formal document tree, not on the generated tree; thus, these selectors will not yield a match.

Because of this, we can only style <use> tag itself in the document by CSS.
Firefox fail to follow this rule at the moment, but it would be so useful that we could style those parts included with <use> tag, I'd be happy to take this bug as a feature and wish every browser do this in the same way.

Since it is an inevitable obstacle, so I turn to make variations in the svg template, then include it with <use> tag, here is a simple example:

<html>
<head>
<style>
    .flower {
        width: 150px;
        height: 150px;
    }
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display: none">
    <defs>
        <circle id="bud" cx="75" cy="75" r="25"/>
        <ellipse id="petal" cx="75" cy="75" rx="60" ry="20"/>
        <symbol id="petals">
            <use xlink:href="#petal" />
            <use xlink:href="#petal" transform="rotate(45 75 75)" />
            <use xlink:href="#petal" transform="rotate(90 75 75)" />
            <use xlink:href="#petal" transform="rotate(135 75 75)" />
        </symbol>
    </defs>
    <g id="pink">
        <use xlink:href="#petals" fill="pink" stroke="red" />
        <use xlink:href="#bud" fill="yellow" stroke="red" />
    </g>
    <g id="blue">
        <use xlink:href="#petals" fill="blue" stroke="black" />
        <use xlink:href="#bud" fill="orange" stroke="black" />
    </g>
</svg>
<svg class="flower">
    <use xlink:href="#blue" />
</svg>
<svg class="flower">
    <use xlink:href="#pink" />
</svg>
</body>
</html>

Flowers

Reveal more SVG power

Frankly, I think using external SVG file is better then inline SVG, I'd like to squash every templates into the SVG, then linking the variation by id with <use> tag, and the most wonderful thing is that although we could not use CSS to make more style change but SVG still has some useful features to make color transformation which font icons are not able to, so I made these tests to see if it worked and you can get the idea.

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="150" height="150">
    <defs>
        <style>
        .brick { display: none }
        .brick:target { display: inline }
        </style>
        <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0" stop-color="white" stop-opacity="0" />
            <stop offset="1" stop-color="white" stop-opacity="1" />
        </linearGradient>
        <filter id="darker">
            <feColorMatrix type="matrix" values="
             0.764 0 0 0 0
             0 0.764 0 0 0
             0 0 0.764 0 0
             0 0 0 1 0"/>
        </filter>
        <filter id="lighter">
            <feColorMatrix type="matrix" values="
             1.176 0 0 0 0
             0 1.176 0 0 0
             0 0 1.176 0 0
             0 0 0 1 0"/>
        </filter>
        <symbol id="up">
            <path d="M5.369 48.659l73.988-42.868 65.182 47.823-74.27 42.416z" />
        </symbol>
        <symbol id="left">
            <path d="M5.501 96.76v-48.168l65.027 47.451v48.166z" />
        </symbol>
        <symbol id="right">
            <path d="M144.632 101.602v-48.168l-74.255 42.402v48.332z" />
        </symbol>
        <mask id="mask">
            <use xlink:href="#up" fill="url(#gradient)" />
        </mask>
        <mask id="mask2">
            <use xlink:href="#right" fill="white" fill-opacity="0.150" />
        </mask>
        <mask id="mask3">
            <use xlink:href="#up" fill="white" fill-opacity="0.100" />
        </mask>
        <mask id="mask4">
            <use xlink:href="#up" fill="white" fill-opacity="0.150" />
        </mask>
        <g id="brick">
            <use xlink:href="#left" />
            <use xlink:href="#right" />
            <use xlink:href="#right" fill="black" mask="url(#mask2)" />
            <use xlink:href="#up" />
            <use xlink:href="#up" fill="white" mask="url(#mask3)" />
            <g mask="url(#mask)">
                <use xlink:href="#up" fill="black" mask="url(#mask4)" />
            </g>
        </g>
        <g id="brick-filter">
            <use xlink:href="#left" />
            <use xlink:href="#right" filter="url(#darker)" />
            <use xlink:href="#up" filter="url(#lighter)" />
            <use xlink:href="#up" filter="url(#darker)" mask="url(#mask)" />
        </g>
    </defs>
    <use class="brick" id="green" fill="#9c3" xlink:href="#brick" />
    <use class="brick" id="orange" fill="orange" xlink:href="#brick" />
    <use class="brick" xlink:href="#brick" />
</svg>
<html>
<head>
<style>
    .brick {
        width: 150px;
        height: 150px;
    }
    .psuedo, .background {
        display: inline-block;
    }
    .psuedo.green::before {
        content: url(test.svg#green);
    }
    .psuedo.orange::before {
        content: url(test.svg#orange);
    }
    .background.green {
        background-image: url(test.svg#green);
    }
    .background.orange {
        background-image: url(test.svg#orange);
    }
</style>
</head>
<body>
    <svg class="brick">
        <use xlink:href="test.svg#green" />
    </svg>
    <svg class="brick"><!-- for Blink -->
        <defs>
        <symbol id="up">
            <path d="M5.369 48.659l73.988-42.868 65.182 47.823-74.27 42.416z" />
        </symbol>
        <symbol id="left">
            <path d="M5.501 96.76v-48.168l65.027 47.451v48.166z" />
        </symbol>
        <symbol id="right">
            <path d="M144.632 101.602v-48.168l-74.255 42.402v48.332z" />
        </symbol>
        <mask id="mask">
            <use xlink:href="#up" fill="url(#gradient)" />
        </mask>
        <mask id="mask2">
            <use xlink:href="#right" fill="white" fill-opacity="0.150" />
        </mask>
        <mask id="mask3">
            <use xlink:href="#up" fill="white" fill-opacity="0.100" />
        </mask>
        <mask id="mask4">
            <use xlink:href="#up" fill="white" fill-opacity="0.150" />
        </mask>
        <filter id="darker">
            <feColorMatrix type="matrix" values="
             0.764 0 0 0 0
             0 0.764 0 0 0
             0 0 0.764 0 0
             0 0 0 1 0"/>
        </filter>
        <filter id="lighter">
            <feColorMatrix type="matrix" values="
             1.176 0 0 0 0
             0 1.176 0 0 0
             0 0 1.176 0 0
             0 0 0 1 0"/>
        </filter>
        </defs>
    </svg>
    <h3>SVG Mask</h3>
    <svg class="brick" fill="#9c3">
        <use xlink:href="test.svg#brick" />
    </svg>
    <svg class="brick" fill="orange">
        <use xlink:href="test.svg#brick" />
    </svg>
    <h3>SVG Filter</h3>
    <svg class="brick">
        <use xlink:href="test.svg#brick-filter" fill="#9c3" />
    </svg>
    <svg class="brick">
        <use xlink:href="test.svg#brick-filter" fill="orange" />
    </svg>
    <svg class="brick">
        <use xlink:href="test.svg#template" fill="orange" />
    </svg>
    <h3>Image Tag</h3>
    <img src="test.svg#green">
    <img src="test.svg#orange">
    <h3>Pseudo Element</h3>
    <div class="psuedo green"></div>
    <div class="psuedo orange"></div>
    <h3>Background Image</h3>
    <div class="brick background green"></div>
    <div class="brick background orange"></div>
    <h3>Object Tag</h3>
    <object type="image/svg+xml" data="test.svg#green" class="brick"></object>
    <object type="image/svg+xml" data="test.svg#orange" class="brick"></object>
</body>
</html>

Firefox 28 renders prefect!
Firefox result

Chrome 33 makes you want to cry :'(
Chrome result

By this bug, Chrome could not use filter and mask from external files, so I add them in the HTML file, but filter still fails, you can remove them to see how terrible Chrome actually renders.

Conclusion

Besides color transformation, I also test it using with SVG Stacks technique in the aforementioned file.

It seems that after two years passed, Chrome still merely support this in <object>, <embed> and <iframe>, but there are some works undertaking.

I think this method is better than fragment identifiers sprites since it can be used now if you does not care supporting of IE (you should always do that for your sanity).

So my recommendation about SVG icon system is that write your icon templates in external SVG file, and then link it in <object> tag with SVG Stacks, hope that we can use it with <img> in the very near future.

P.S. If you do not have mysophobia of JavaScript, take a look at this method.

過往今昔…

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *