SVG mittels CSS färben
bearbeitet von
@@Gunnar Bittersmann
> Aber eigentlich soll gar nicht alles SVG in HTML eingebettet sein. Dazu später mehr …
Im [ersten Teil](https://forum.selfhtml.org/self/2018/aug/23/icon-png-mit-transperenten-hintergrund-mittels-css-faerben/1729891#m1729891) des Tutorials hatten wir ein Haus-Icon erstellt und mehrfach wiederverwendet. Was, wenn wir nun mehrere verschiedene Icons haben: Mein Haus, mein Auto, mein Boot, …?
Die sammeln wir alle als `symbol`s in dem einen `svg`-Element:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="house" viewBox="8 0 80 80">
<path d="M48,13 L9,48 H20 V80 H40 V56 H56 V80 H76 V48 H87z"/>
</symbol>
<symbol id="car" viewBox="0 0 100 200">
<path d="…"/>
</symbol>
<symbol id="boat" viewBox="-21 -21 42 42">
<path d="…"/>
</symbol>
</svg>
~~~
Die Pfade für Auto und Boot habe ich nur angedeutet. *You get the picture. (No pun intended.)*{:@en}
Wichtig ist, dass jedes `symbol` sein eigenes `viewBox`-Attribut, d.h. sein eigenes Koordinatensystem hat. Das hat nichts mit der Größe zu tun, mit der das Icon dargestellt wird; diese hatten wir im Stylesheet mit `svg { width: 1em; height: 1em }`{:.language-css} angegeben.
(Man kann natürlich auch für verschiedene Icons verschiedene Größen angeben oder dasselbe Icon an verschiedenen Stellen in verschiedenen Größen verwenden.)
Verwendung (der Einfachheit halber hier ohne Namensräume):
~~~HTML
<ul>
<li><svg><use href="#house"/></svg> mein Haus</li>
<li><svg><use href="#car"/></svg> mein Auto</li>
<li><svg><use href="#boat"/></svg> mein Boot</li>
</ul>
~~~
Nun wollen wir die Icons nicht nur auf einer Webseite, sondern auf allen Seiten unserer Website einsetzen. Dazu müssten wir das `svg`-Element mit den `symbol`s in jedes HTML-Dokument hineinkopieren … was wohl keine so gute Lösung wäre.
Sinnvoll ist, das `svg`-Element mit den `symbol`s in eine Datei auszulagern (bspw. speichern als `icons.svg` im `assets`-Ordner). `style="display: none"` kann dann weg; wichtig ist, dass hier die XML-Namensraumangabe vorhanden ist:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg">
~~~
Diese Datei wird nun nicht mit PHP o.ä. in den HTML-Quelltext geschrieben, denn dann müssten ja sämtliche Icons bei jeder Seite erneut übertragen werden. Das würde den Quelltext unnötig aufblähen, insbesondere, wenn wir nicht nur drei, sondern dreiundzwölfzig Icons in unserer Sammlung haben.
Wir wollen, dass diese SVG-Datei mit allen Icons nur einmal übertragen wird und dann im Browsercache liegt. Wir holen uns die Icons deshalb aus der externen Datei:
~~~HTML
<ul>
<li><svg><use href="/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
bzw.
~~~HTML
<ul>
<li><svg><use href="https://example.net/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="https://example.net/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="https://example.net/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
Leider wird die Freude durch **I**rgend**E**inen Browser getrübt. Internet Explorer kann zwar `<use href="#house"/>`{:.language-svg}, wenn die `symbol`s in derselben HTML-Datei sind; aber nicht `<use href="icons.svg#house"/>`{:.language-svg} mit externer SVG-Datei. (Wohlgemerkt, wir reden von IE, nicht von Edge – der kann das.)
Wenn man die Icons **zusätzlich** zu vorhandem Text einsetzt (was generell eine gute Idee ist), kann man auf *progressive enhancement*{:@en} setzen: Seitenbesucher mit Internet Explorer (3…4%, Tendenz fallend) bekommen die Icons nicht zu sehen. Das sollte nicht tragisch sein; die Icons sind ja „nur“ Zusatz zum Text.
Man kann aber auch einen Polyfill anwenden, um die Icons auch im IE darzustellen. Dazu wird die SVG-Datei per AJAX geladen und als `svg`-Element ins DOM gepackt, womit die `symbol`s im selben Dokument sind und auch vom IE referenziert werden können. Dazu werden die Referenzen zu lokalen Referenzen umgeschrieben, bspw. `icons.svg#house` zu `#house`.
Der Unterschied zur serverseitigen Einbettung ins HTML besteht darin, dass beim zweiten AJAX-Aufruf die dreiundzwölfzig Icons schon im Browsercache liegen und nicht nochmal übers Netzwerk geholt werden müssen.
Das JavaScript sieht in etwa so aus:
~~~JavaScript
document.addEventListener('DOMContentLoaded', function ()
{
'use strict';
if (!(window.CSS && 'supports' in CSS))
{
var useElements = Array.prototype.slice.call(document.querySelectorAll('use'));
if (useElements)
{
var href = useElements[0].getAttribute('href') || useElements[0].getAttribute('xlink:href');
var request = new XMLHttpRequest();
request.open('GET', href.substr(0, href.indexOf('#')), true);
request.addEventListener('load', function ()
{
if (request.status >= 200 && request.status < 400)
{
document.body.appendChild(request.responseXML.documentElement);
}
});
request.send();
useElements.forEach(function (useElement)
{
var href = useElement.getAttribute('href') || useElement.getAttribute('xlink:href');
useElement.setAttribute('xlink:href', href.substr(href.indexOf('#')));
useElement.removeAttribute('href');
});
}
}
});
~~~
Mit `if (!(window.CSS && 'supports' in CSS))`{:.language-js} wird die Spreu (IE) vom Weizen getrennt und der Polyfill nur in Browsern ausgeführt, die ihn nötig haben. Das ist eher ein Hack; mir ist keine geeignete *feature detection*{:@en} eingefallen. Nicht, dass ich nicht [gefragt](https://forum.selfhtml.org/self/2018/jul/12/feature-detection-fuer-use-xlink-href-gleich-external-punkt-svg-nummer-my-strich/1726467#m1726467) hätte.
Das Script geht davon aus, dass alle Icons in einundderselben Datei sind und lädt nur die SVG-Datei des ersten `use`-Elements (`useElements[0]`{:.language-js}). Wenn Icons aus verschiedenen SVG-Dateien eingebunden werden, müsste man das entsprechend umschreiben – oder bestehende Polyfills verwenden. Mein geschätzter Kollege [wies mich auf svg4everybody und svgxuse hin](https://twitter.com/maddesigns/status/1017477816286416897).
[svg4everybody](https://github.com/jonathantneal/svg4everybody) fällt in meinem [Test](https://bittersmann.de/test/use-external-svg-polyfill/) durch.[^Test] (Im IE ansehen!) Die Icons sind verschoben (Liegt das daran, dass `viewBox` so gewählt ist, dass der Punkt links oben nicht der Koordinatenursprung ist?) und damit, dass `use` in einem `symbol` verwendet wird, kommt das Script nicht klar.
[svgxuse](https://github.com/Keyamoon/svgxuse) tut, was es soll. Damit steht der Verwendung von SVG-Icon auch im Internet Explorer nichts im Wege. Und es gibt **keinen Grund für Icon-Fonts** mehr. **Keinen!** Weitere Literatur:
* Chris Coyier: [SVG `use` with External Reference, Take 2](https://css-tricks.com/svg-use-with-external-reference-take-2/) [en]
* Chris Coyier: [Inline SVG vs Icon Fonts](https://css-tricks.com/icon-fonts-vs-svg/) [en]
* Tyler Sticka: [Seriously, Don’t Use Icon Fonts](https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/) [en]
* Sven Wolfermann: [SVG Sprites vs. Icon-Fonts](http://maddesigns.de/svg-sprites-icon-fonts-2309.html) [de]
[^Test]: In der ersten Version hatte ich statt Iframes doch tatsächlich ein [Frameset](https://bittersmann.de/test/use-external-svg-polyfill/frameset.html) gebaut! 😱😏
LLAP 🖖
--
*„Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“* —Kurt Weidemann
> Aber eigentlich soll gar nicht alles SVG in HTML eingebettet sein. Dazu später mehr …
Im [ersten Teil](https://forum.selfhtml.org/self/2018/aug/23/icon-png-mit-transperenten-hintergrund-mittels-css-faerben/1729891#m1729891) des Tutorials hatten wir ein Haus-Icon erstellt und mehrfach wiederverwendet. Was, wenn wir nun mehrere verschiedene Icons haben: Mein Haus, mein Auto, mein Boot, …?
Die sammeln wir alle als `symbol`s in dem einen `svg`-Element:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="house" viewBox="8 0 80 80">
<path d="M48,13 L9,48 H20 V80 H40 V56 H56 V80 H76 V48 H87z"/>
</symbol>
<symbol id="car" viewBox="0 0 100 200">
<path d="…"/>
</symbol>
<symbol id="boat" viewBox="-21 -21 42 42">
<path d="…"/>
</symbol>
</svg>
~~~
Die Pfade für Auto und Boot habe ich nur angedeutet. *You get the picture. (No pun intended.)*{:@en}
Wichtig ist, dass jedes `symbol` sein eigenes `viewBox`-Attribut, d.h. sein eigenes Koordinatensystem hat. Das hat nichts mit der Größe zu tun, mit der das Icon dargestellt wird; diese hatten wir im Stylesheet mit `svg { width: 1em; height: 1em }`{:.language-css} angegeben.
(Man kann natürlich auch für verschiedene Icons verschiedene Größen angeben oder dasselbe Icon an verschiedenen Stellen in verschiedenen Größen verwenden.)
Verwendung (der Einfachheit halber hier ohne Namensräume):
~~~HTML
<ul>
<li><svg><use href="#house"/></svg> mein Haus</li>
<li><svg><use href="#car"/></svg> mein Auto</li>
<li><svg><use href="#boat"/></svg> mein Boot</li>
</ul>
~~~
Nun wollen wir die Icons nicht nur auf einer Webseite, sondern auf allen Seiten unserer Website einsetzen. Dazu müssten wir das `svg`-Element mit den `symbol`s in jedes HTML-Dokument hineinkopieren … was wohl keine so gute Lösung wäre.
Sinnvoll ist, das `svg`-Element mit den `symbol`s in eine Datei auszulagern (bspw. speichern als `icons.svg` im `assets`-Ordner). `style="display: none"` kann dann weg; wichtig ist, dass hier die XML-Namensraumangabe vorhanden ist:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg">
~~~
Diese Datei wird nun nicht mit PHP o.ä. in den HTML-Quelltext geschrieben, denn dann müssten ja sämtliche Icons bei jeder Seite erneut übertragen werden. Das würde den Quelltext unnötig aufblähen, insbesondere, wenn wir nicht nur drei, sondern dreiundzwölfzig Icons in unserer Sammlung haben.
Wir wollen, dass diese SVG-Datei mit allen Icons nur einmal übertragen wird und dann im Browsercache liegt. Wir holen uns die Icons deshalb aus der externen Datei:
~~~HTML
<ul>
<li><svg><use href="/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
bzw.
~~~HTML
<ul>
<li><svg><use href="https://example.net/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="https://example.net/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="https://example.net/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
Leider wird die Freude durch **I**rgend**E**inen Browser getrübt. Internet Explorer kann zwar `<use href="#house"/>`{:.language-svg}, wenn die `symbol`s in derselben HTML-Datei sind; aber nicht `<use href="icons.svg#house"/>`{:.language-svg} mit externer SVG-Datei. (Wohlgemerkt, wir reden von IE, nicht von Edge – der kann das.)
Wenn man die Icons **zusätzlich** zu vorhandem Text einsetzt (was generell eine gute Idee ist), kann man auf *progressive enhancement*{:@en} setzen: Seitenbesucher mit Internet Explorer (3…4%, Tendenz fallend) bekommen die Icons nicht zu sehen. Das sollte nicht tragisch sein; die Icons sind ja „nur“ Zusatz zum Text.
Man kann aber auch einen Polyfill anwenden, um die Icons auch im IE darzustellen. Dazu wird die SVG-Datei per AJAX geladen und als `svg`-Element ins DOM gepackt, womit die `symbol`s im selben Dokument sind und auch vom IE referenziert werden können. Dazu werden die Referenzen zu lokalen Referenzen umgeschrieben, bspw. `icons.svg#house` zu `#house`.
Der Unterschied zur serverseitigen Einbettung ins HTML besteht darin, dass beim zweiten AJAX-Aufruf die dreiundzwölfzig Icons schon im Browsercache liegen und nicht nochmal übers Netzwerk geholt werden müssen.
Das JavaScript sieht in etwa so aus:
~~~JavaScript
document.addEventListener('DOMContentLoaded', function ()
{
'use strict';
if (!(window.CSS && 'supports' in CSS))
{
var useElements = Array.prototype.slice.call(document.querySelectorAll('use'));
if (useElements)
{
var href = useElements[0].getAttribute('href') || useElements[0].getAttribute('xlink:href');
var request = new XMLHttpRequest();
request.open('GET', href.substr(0, href.indexOf('#')), true);
request.addEventListener('load', function ()
{
if (request.status >= 200 && request.status < 400)
{
document.body.appendChild(request.responseXML.documentElement);
}
});
request.send();
useElements.forEach(function (useElement)
{
var href = useElement.getAttribute('href') || useElement.getAttribute('xlink:href');
useElement.setAttribute('xlink:href', href.substr(href.indexOf('#')));
useElement.removeAttribute('href');
});
}
}
});
~~~
Mit `if (!(window.CSS && 'supports' in CSS))`{:.language-js} wird die Spreu (IE) vom Weizen getrennt und der Polyfill nur in Browsern ausgeführt, die ihn nötig haben. Das ist eher ein Hack; mir ist keine geeignete *feature detection*{:@en} eingefallen. Nicht, dass ich nicht [gefragt](https://forum.selfhtml.org/self/2018/jul/12/feature-detection-fuer-use-xlink-href-gleich-external-punkt-svg-nummer-my-strich/1726467#m1726467) hätte.
Das Script geht davon aus, dass alle Icons in einundderselben Datei sind und lädt nur die SVG-Datei des ersten `use`-Elements (`useElements[0]`{:.language-js}). Wenn Icons aus verschiedenen SVG-Dateien eingebunden werden, müsste man das entsprechend umschreiben – oder bestehende Polyfills verwenden. Mein geschätzter Kollege [wies mich auf svg4everybody und svgxuse hin](https://twitter.com/maddesigns/status/1017477816286416897).
[svg4everybody](https://github.com/jonathantneal/svg4everybody) fällt in meinem [Test](https://bittersmann.de/test/use-external-svg-polyfill/) durch.[^Test] (Im IE ansehen!) Die Icons sind verschoben (Liegt das daran, dass `viewBox` so gewählt ist, dass der Punkt links oben nicht der Koordinatenursprung ist?) und damit, dass `use` in einem `symbol` verwendet wird, kommt das Script nicht klar.
[svgxuse](https://github.com/Keyamoon/svgxuse) tut, was es soll. Damit steht der Verwendung von SVG-Icon auch im Internet Explorer nichts im Wege. Und es gibt **keinen Grund für Icon-Fonts** mehr. **Keinen!** Weitere Literatur:
* Chris Coyier: [SVG `use` with External Reference, Take 2](https://css-tricks.com/svg-use-with-external-reference-take-2/) [en]
* Chris Coyier: [Inline SVG vs Icon Fonts](https://css-tricks.com/icon-fonts-vs-svg/) [en]
* Tyler Sticka: [Seriously, Don’t Use Icon Fonts](https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/) [en]
* Sven Wolfermann: [SVG Sprites vs. Icon-Fonts](http://maddesigns.de/svg-sprites-icon-fonts-2309.html) [de]
[^Test]: In der ersten Version hatte ich statt Iframes doch tatsächlich ein [Frameset](https://bittersmann.de/test/use-external-svg-polyfill/frameset.html) gebaut! 😱😏
LLAP 🖖
--
*„Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“* —Kurt Weidemann
SVG mittels CSS färben
bearbeitet von
@@Gunnar Bittersmann
> Aber eigentlich soll gar nicht alles SVG in HTML eingebettet sein. Dazu später mehr …
Im ersten Teil des Tutorials hatten wir ein Haus-Icon erstellt und mehrfach wiederverwendet. Was, wenn wir nun mehrere verschiedene Icons haben: Mein Haus, mein Auto, mein Boot, …?
Die sammeln wir alle als `symbol`s in dem einen `svg`-Element:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="house" viewBox="8 0 80 80">
<path d="M48,13 L9,48 H20 V80 H40 V56 H56 V80 H76 V48 H87z"/>
</symbol>
<symbol id="car" viewBox="0 0 100 200">
<path d="…"/>
</symbol>
<symbol id="boat" viewBox="-21 -21 42 42">
<path d="…"/>
</symbol>
</svg>
~~~
Die Pfade für Auto und Boot habe ich nur angedeutet. *You get the picture. (No pun intended.)*{:@en}
Wichtig ist, dass jedes `symbol` sein eigenes `viewBox`-Attribut, d.h. sein eigenes Koordinatensystem hat. Das hat nichts mit der Größe zu tun, mit der das Icon dargestellt wird; diese hatten wir im Stylesheet mit `svg { width: 1em; height: 1em }`{:.language-css} angegeben.
(Man kann natürlich auch für verschiedene Icons verschiedene Größen angeben oder dasselbe Icon an verschiedenen Stellen in verschiedenen Größen verwenden.)
Verwendung (der Einfachheit halber hier ohne Namensräume):
~~~HTML
<ul>
<li><svg><use href="#house"/></svg> mein Haus</li>
<li><svg><use href="#car"/></svg> mein Auto</li>
<li><svg><use href="#boat"/></svg> mein Boot</li>
</ul>
~~~
Nun wollen wir die Icons nicht nur auf einer Webseite, sondern auf allen Seiten unserer Website einsetzen. Dazu müssten wir das `svg`-Element mit den `symbol`s in jedes HTML-Dokument hineinkopieren … was wohl keine so gute Lösung wäre.
Sinnvoll ist, das `svg`-Element mit den `symbol`s in eine Datei auszulagern (bspw. speichern als `icons.svg` im `assets`-Ordner). `style="display: none"` kann dann weg; wichtig ist, dass hier die XML-Namensraumangabe vorhanden ist:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg">
~~~
Diese Datei wird nun nicht mit PHP o.ä. in den HTML-Quelltext geschrieben, denn dann müssten ja sämtliche Icons bei jeder Seite erneut übertragen werden. Das würde den Quelltext unnötig aufblähen, insbesondere, wenn wir nicht nur drei, sondern dreiundzwölfzig Icons in unserer Sammlung haben.
Wir wollen, dass diese SVG-Datei mit allen Icons nur einmal übertragen wird und dann im Browsercache liegt. Wir holen uns die Icons deshalb aus der externen Datei:
~~~HTML
<ul>
<li><svg><use href="/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
bzw.
~~~HTML
<ul>
<li><svg><use href="https://example.net/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="https://example.net/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="https://example.net/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
Leider wird die Freude durch **I**rgend**E**inen Browser getrübt. Internet Explorer kann zwar `<use href="#house"/>`{:.language-svg}, wenn die `symbol`s in derselben HTML-Datei sind; aber nicht `<use href="icons.svg#house"/>`{:.language-svg} mit externer SVG-Datei. (Wohlgemerkt, wir reden von IE, nicht von Edge – der kann das.)
Wenn man die Icons **zusätzlich** zu vorhandem Text einsetzt (was generell eine gute Idee ist), kann man auf *progressive enhancement*{:@en} setzen: Seitenbesucher mit Internet Explorer (3…4%, Tendenz fallend) bekommen die Icons nicht zu sehen. Das sollte nicht tragisch sein; die Icons sind ja „nur“ Zusatz zum Text.
Man kann aber auch einen Polyfill anwenden, um die Icons auch im IE darzustellen. Dazu wird die SVG-Datei per AJAX geladen und als `svg`-Element ins DOM gepackt, womit die `symbol`s im selben Dokument sind und auch vom IE referenziert werden können. Dazu werden die Referenzen zu lokalen Referenzen umgeschrieben, bspw. `icons.svg#house` zu `#house`.
Der Unterschied zur serverseitigen Einbettung ins HTML besteht darin, dass beim zweiten AJAX-Aufruf die dreiundzwölfzig Icons schon im Browsercache liegen und nicht nochmal übers Netzwerk geholt werden müssen.
Das JavaScript sieht in etwa so aus:
~~~JavaScript
document.addEventListener('DOMContentLoaded', function ()
{
'use strict';
if (!(window.CSS && 'supports' in CSS))
{
var useElements = Array.prototype.slice.call(document.querySelectorAll('use'));
if (useElements)
{
var href = useElements[0].getAttribute('href') || useElements[0].getAttribute('xlink:href');
var request = new XMLHttpRequest();
request.open('GET', href.substr(0, href.indexOf('#')), true);
request.addEventListener('load', function ()
{
if (request.status >= 200 && request.status < 400)
{
document.body.appendChild(request.responseXML.documentElement);
}
});
request.send();
useElements.forEach(function (useElement)
{
var href = useElement.getAttribute('href') || useElement.getAttribute('xlink:href');
useElement.setAttribute('xlink:href', href.substr(href.indexOf('#')));
useElement.removeAttribute('href');
});
}
}
});
~~~
Mit `if (!(window.CSS && 'supports' in CSS))`{:.language-js} wird die Spreu (IE) vom Weizen getrennt und der Polyfill nur in Browsern ausgeführt, die ihn nötig haben. Das ist eher ein Hack; mir ist keine geeignete *feature detection*{:@en} eingefallen. Nicht, dass ich nicht [gefragt](https://forum.selfhtml.org/self/2018/jul/12/feature-detection-fuer-use-xlink-href-gleich-external-punkt-svg-nummer-my-strich/1726467#m1726467) hätte.
Das Script geht davon aus, dass alle Icons in einundderselben Datei sind und lädt nur die SVG-Datei des ersten `use`-Elements (`useElements[0]`{:.language-js}). Wenn Icons aus verschiedenen SVG-Dateien eingebunden werden, müsste man das entsprechend umschreiben – oder bestehende Polyfills verwenden. Mein geschätzter Kollege [wies mich auf svg4everybody und svgxuse hin](https://twitter.com/maddesigns/status/1017477816286416897).
[svg4everybody](https://github.com/jonathantneal/svg4everybody) fällt in meinem [Test](https://bittersmann.de/test/use-external-svg-polyfill/) durch.[^Test] (Im IE ansehen!) Die Icons sind verschoben (Liegt das daran, dass `viewBox` so gewählt ist, dass der Punkt links oben nicht der Koordinatenursprung ist?) und damit, dass `use` in einem `symbol` verwendet wird, kommt das Script nicht klar.
[svgxuse](https://github.com/Keyamoon/svgxuse) tut, was es soll. Damit steht der Verwendung von SVG-Icon auch im Internet Explorer nichts im Wege. Und es gibt **keinen Grund für Icon-Fonts** mehr. **Keinen!** Weitere Literatur:
* Chris Coyier: [SVG `use` with External Reference, Take 2](https://css-tricks.com/svg-use-with-external-reference-take-2/) [en]
* Chris Coyier: [Inline SVG vs Icon Fonts](https://css-tricks.com/icon-fonts-vs-svg/) [en]
* Tyler Sticka: [Seriously, Don’t Use Icon Fonts](https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/) [en]
* Sven Wolfermann: [SVG Sprites vs. Icon-Fonts](http://maddesigns.de/svg-sprites-icon-fonts-2309.html) [de]
[^Test]: In der ersten Version hatte ich statt Iframes doch tatsächlich ein [Frameset](https://bittersmann.de/test/use-external-svg-polyfill/frameset.html) gebaut! 😱😏
LLAP 🖖
--
*„Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“* —Kurt Weidemann
> Aber eigentlich soll gar nicht alles SVG in HTML eingebettet sein. Dazu später mehr …
Im ersten Teil des Tutorials hatten wir ein Haus-Icon erstellt und mehrfach wiederverwendet. Was, wenn wir nun mehrere verschiedene Icons haben: Mein Haus, mein Auto, mein Boot, …?
Die sammeln wir alle als `symbol`s in dem einen `svg`-Element:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="house" viewBox="8 0 80 80">
<path d="M48,13 L9,48 H20 V80 H40 V56 H56 V80 H76 V48 H87z"/>
</symbol>
<symbol id="car" viewBox="0 0 100 200">
<path d="…"/>
</symbol>
<symbol id="boat" viewBox="-21 -21 42 42">
<path d="…"/>
</symbol>
</svg>
~~~
Die Pfade für Auto und Boot habe ich nur angedeutet. *You get the picture. (No pun intended.)*{:@en}
Wichtig ist, dass jedes `symbol` sein eigenes `viewBox`-Attribut, d.h. sein eigenes Koordinatensystem hat. Das hat nichts mit der Größe zu tun, mit der das Icon dargestellt wird; diese hatten wir im Stylesheet mit `svg { width: 1em; height: 1em }`{:.language-css} angegeben.
(Man kann natürlich auch für verschiedene Icons verschiedene Größen angeben oder dasselbe Icon an verschiedenen Stellen in verschiedenen Größen verwenden.)
Verwendung (der Einfachheit halber hier ohne Namensräume):
~~~HTML
<ul>
<li><svg><use href="#house"/></svg> mein Haus</li>
<li><svg><use href="#car"/></svg> mein Auto</li>
<li><svg><use href="#boat"/></svg> mein Boot</li>
</ul>
~~~
Nun wollen wir die Icons nicht nur auf einer Webseite, sondern auf allen Seiten unserer Website einsetzen. Dazu müssten wir das `svg`-Element mit den `symbol`s in jedes HTML-Dokument hineinkopieren … was wohl keine so gute Lösung wäre.
Sinnvoll ist, das `svg`-Element mit den `symbol`s in eine Datei auszulagern (bspw. speichern als `icons.svg` im `assets`-Ordner). `style="display: none"` kann dann weg; wichtig ist, dass hier die XML-Namensraumangabe vorhanden ist:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg">
~~~
Diese Datei wird nun nicht mit PHP o.ä. in den HTML-Quelltext geschrieben, denn dann müssten ja sämtliche Icons bei jeder Seite erneut übertragen werden. Das würde den Quelltext unnötig aufblähen, insbesondere, wenn wir nicht nur drei, sondern dreiundzwölfzig Icons in unserer Sammlung haben.
Wir wollen, dass diese SVG-Datei mit allen Icons nur einmal übertragen wird und dann im Browsercache liegt. Wir holen uns die Icons deshalb aus der externen Datei:
~~~HTML
<ul>
<li><svg><use href="/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
bzw.
~~~HTML
<ul>
<li><svg><use href="https://example.net/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="https://example.net/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="https://example.net/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
Leider wird die Freude durch **I**rgend**E**inen Browser getrübt. Internet Explorer kann zwar `<use href="#house"/>`{:.language-svg}, wenn die `symbol`s in derselben HTML-Datei sind; aber nicht `<use href="icons.svg#house"/>`{:.language-svg} mit externer SVG-Datei. (Wohlgemerkt, wir reden von IE, nicht von Edge – der kann das.)
Wenn man die Icons **zusätzlich** zu vorhandem Text einsetzt (was generell eine gute Idee ist), kann man auf *progressive enhancement*{:@en} setzen: Seitenbesucher mit Internet Explorer (3…4%, Tendenz fallend) bekommen die Icons nicht zu sehen. Das sollte nicht tragisch sein; die Icons sind ja „nur“ Zusatz zum Text.
Man kann aber auch einen Polyfill anwenden, um die Icons auch im IE darzustellen. Dazu wird die SVG-Datei per AJAX geladen und als `svg`-Element ins DOM gepackt, womit die `symbol`s im selben Dokument sind und auch vom IE referenziert werden können. Dazu werden die Referenzen zu lokalen Referenzen umgeschrieben, bspw. `icons.svg#house` zu `#house`.
Der Unterschied zur serverseitigen Einbettung ins HTML besteht darin, dass beim zweiten AJAX-Aufruf die dreiundzwölfzig Icons schon im Browsercache liegen und nicht nochmal übers Netzwerk geholt werden müssen.
Das JavaScript sieht in etwa so aus:
~~~JavaScript
document.addEventListener('DOMContentLoaded', function ()
{
'use strict';
if (!(window.CSS && 'supports' in CSS))
{
var useElements = Array.prototype.slice.call(document.querySelectorAll('use'));
if (useElements)
{
var href = useElements[0].getAttribute('href') || useElements[0].getAttribute('xlink:href');
var request = new XMLHttpRequest();
request.open('GET', href.substr(0, href.indexOf('#')), true);
request.addEventListener('load', function ()
{
if (request.status >= 200 && request.status < 400)
{
document.body.appendChild(request.responseXML.documentElement);
}
});
request.send();
useElements.forEach(function (useElement)
{
var href = useElement.getAttribute('href') || useElement.getAttribute('xlink:href');
useElement.setAttribute('xlink:href', href.substr(href.indexOf('#')));
useElement.removeAttribute('href');
});
}
}
});
~~~
Mit `if (!(window.CSS && 'supports' in CSS))`{:.language-js} wird die Spreu (IE) vom Weizen getrennt und der Polyfill nur in Browsern ausgeführt, die ihn nötig haben. Das ist eher ein Hack; mir ist keine geeignete *feature detection*{:@en} eingefallen. Nicht, dass ich nicht [gefragt](https://forum.selfhtml.org/self/2018/jul/12/feature-detection-fuer-use-xlink-href-gleich-external-punkt-svg-nummer-my-strich/1726467#m1726467) hätte.
Das Script geht davon aus, dass alle Icons in einundderselben Datei sind und lädt nur die SVG-Datei des ersten `use`-Elements (`useElements[0]`{:.language-js}). Wenn Icons aus verschiedenen SVG-Dateien eingebunden werden, müsste man das entsprechend umschreiben – oder bestehende Polyfills verwenden. Mein geschätzter Kollege [wies mich auf svg4everybody und svgxuse hin](https://twitter.com/maddesigns/status/1017477816286416897).
[svg4everybody](https://github.com/jonathantneal/svg4everybody) fällt in meinem [Test](https://bittersmann.de/test/use-external-svg-polyfill/) durch.[^Test] (Im IE ansehen!) Die Icons sind verschoben (Liegt das daran, dass `viewBox` so gewählt ist, dass der Punkt links oben nicht der Koordinatenursprung ist?) und damit, dass `use` in einem `symbol` verwendet wird, kommt das Script nicht klar.
[svgxuse](https://github.com/Keyamoon/svgxuse) tut, was es soll. Damit steht der Verwendung von SVG-Icon auch im Internet Explorer nichts im Wege. Und es gibt **keinen Grund für Icon-Fonts** mehr. **Keinen!** Weitere Literatur:
* Chris Coyier: [SVG `use` with External Reference, Take 2](https://css-tricks.com/svg-use-with-external-reference-take-2/) [en]
* Chris Coyier: [Inline SVG vs Icon Fonts](https://css-tricks.com/icon-fonts-vs-svg/) [en]
* Tyler Sticka: [Seriously, Don’t Use Icon Fonts](https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/) [en]
* Sven Wolfermann: [SVG Sprites vs. Icon-Fonts](http://maddesigns.de/svg-sprites-icon-fonts-2309.html) [de]
[^Test]: In der ersten Version hatte ich statt Iframes doch tatsächlich ein [Frameset](https://bittersmann.de/test/use-external-svg-polyfill/frameset.html) gebaut! 😱😏
LLAP 🖖
--
*„Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“* —Kurt Weidemann
SVG mittels CSS färben
bearbeitet von
@@Gunnar Bittersmann
> Aber eigentlich soll gar nicht alles SVG in HTML eingebettet sein. Dazu später mehr …
Im ersten Teil des Tutorials hatten wir ein Haus-Icon erstellt und mehrfach wiederverwendet. Was, wenn wir nun mehrere verschiedene Icons haben: Mein Haus, mein Auto, mein Boot, …?
Die sammeln wir alle als `symbol`s in dem einen `svg`-Element:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="house" viewBox="8 0 80 80">
<path d="M48,13 L9,48 H20 V80 H40 V56 H56 V80 H76 V48 H87z"/>
</symbol>
<symbol id="car" viewBox="0 0 100 200">
<path d="…"/>
</symbol>
<symbol id="boat" viewBox="-21 -21 42 42">
<path d="…"/>
</symbol>
</svg>
~~~
Die Pfade für Auto und Boot habe ich nur angedeutet. *You get the picture. (No pun intended.)*{:@en}
Wichtig ist, dass jedes `symbol` sein eigenes `viewBox`-Attribut, d.h. sein eigenes Koordinatensystem hat. Das hat nichts mit der Größe zu tun, mit der das Icon dargestellt wird; diese hatten wir im Stylesheet mit `svg { width: 1em; height: 1em }`{:.language-css} angegeben.
(Man kann natürlich auch für verschiedene Icons verschiedene Größen angeben oder dasselbe Icon an verschiedenen Stellen in verschiedenen Größen verwenden.)
Verwendung (der Einfachheit halber hier ohne Namensräume):
~~~HTML
<ul>
<li><svg><use href="#house"/></svg> mein Haus</li>
<li><svg><use href="#car"/></svg> mein Auto</li>
<li><svg><use href="#boat"/></svg> mein Boot</li>
</ul>
~~~
Nun wollen wir die Icons nicht nur auf einer Webseite, sondern auf allen Seiten unserer Website einsetzen. Dazu müssten wir das `svg`-Element mit den `symbol`s in jedes HTML-Dokument hineinkopieren … was wohl keine so gute Lösung wäre.
Sinnvoll ist, das `svg`-Element mit den `symbol`s in eine Datei auszulagern (bspw. speichern als `icons.svg` im `assets`-Ordner). `style="display: none"` kann dann weg; wichtig ist, dass hier die XML-Namensraumangabe vorhanden ist:
~~~SVG
<svg xmlns="http://www.w3.org/2000/svg">
~~~
Diese Datei wird nun nicht mit PHP o.ä. in den HTML-Quelltext geschrieben, denn dann müssten ja sämtliche Icons bei jeder Seite erneut übertragen werden. Das würde den Quelltext unnötig aufblähen, insbesondere, wenn wir nicht nur drei, sondern dreiundzwölfzig Icons in unserer Sammlung haben.
Wir wollen, dass diese SVG-Datei mit allen Icons nur einmal übertragen wird und dann im Browsercache liegt. Wir holen uns die Icons deshalb aus der externen Datei:
~~~HTML
<ul>
<li><svg><use href="/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
bzw.
~~~HTML
<ul>
<li><svg><use href="https://example.net/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use href="https://example.net/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use href="https://example.net/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
~~~
Leider wird die Freude durch **I**rgend**E**inen Browser getrübt. Internet Explorer kann zwar `<use href="#house"/>`{:.language-svg}, wenn die `symbol`s in derselben HTML-Datei sind; aber nicht `<use href="icons.svg#house"/>`{:.language-svg} mit externer SVG-Datei. (Wohlgemerkt, wir reden von IE, nicht von Edge – der kann das.)
Wenn man die Icons **zusätzlich** zu vorhandem Text einsetzt (was generell eine gute Idee ist), kann man auf *progressive enhancement*{:@en} setzen: Seitenbesucher mit Internet Explorer (3…4%, Tendenz fallend) bekommen die Icons nicht zu sehen. Das sollte nicht tragisch sein; die Icons sind ja „nur“ Zusatz zum Text.
Man kann aber auch einen Polyfill anwenden, um die Icons auch im IE darzustellen. Dazu wird die SVG-Datei per AJAX geladen und als `svg`-Element ins DOM gepackt, womit die `symbol`s im selben Dokument sind und auch vom IE referenziert werden können. Dazu werden die Referenzen zu lokalen Referenzen umgeschrieben, bspw. `icons.svg#house` zu `#house`.
Der Unterschied zur serverseitigen Einbettung ins HTML besteht darin, dass beim zweiten AJAX-Aufruf die dreiundzwölfzig Icons schon im Browsercache liegen und nicht nochmal übers Netzwerk geholt werden müssen.
Das JavaScript sieht in etwa so aus:
~~~JavaScript
document.addEventListener('DOMContentLoaded', function ()
{
'use strict';
if (!(window.CSS && 'supports' in CSS))
{
var useElements = Array.prototype.slice.call(document.querySelectorAll('use'));
if (useElements)
{
var href = useElements[0].getAttribute('href') || useElements[0].getAttribute('xlink:href');
var request = new XMLHttpRequest();
request.open('GET', href.substr(0, href.indexOf('#')), true);
request.addEventListener('load', function ()
{
if (request.status >= 200 && request.status < 400)
{
document.body.appendChild(request.responseXML.documentElement);
}
});
request.send();
useElements.forEach(function (useElement)
{
var href = useElement.getAttribute('href') || useElement.getAttribute('xlink:href');
useElement.setAttribute('xlink:href', href.substr(href.indexOf('#')));
useElement.removeAttribute('href');
});
}
}
});
~~~
Mit `if (!(window.CSS && 'supports' in CSS))`{:.language-js} wird die Spreu (IE) vom Weizen getrennt und der Polyfill nur in Browsern ausgeführt, die ihn nötig haben. Das ist eher ein Hack; mir ist keine geeignete *feature detection*{:@en} eingefallen. Nicht, dass ich nicht [gefragt](https://forum.selfhtml.org/self/2018/jul/12/feature-detection-fuer-use-xlink-href-gleich-external-punkt-svg-nummer-my-strich/1726467#m1726467) hätte.
Das Script geht davon aus, dass alle Icons in einundderselben Datei sind und lädt nur die SVG-Datei des ersten `use`-Elements (`useElements[0]`{:.language-js}). Wenn Icons aus verschiedenen SVG-Dateien eingebunden werden, müsste man das entsprechend umschreiben – oder bestehende Polyfills verwenden. Mein geschätzter Kollege [wies mich auf svg4everybody und svgxuse hin](https://twitter.com/maddesigns/status/1017477816286416897).
[svg4everybody](https://github.com/jonathantneal/svg4everybody) fällt in meinem [Test](https://bittersmann.de/test/use-external-svg-polyfill/) durch.[^Test] (Im IE ansehen!) Die Icons sind verschoben (Liegt das daran, dass `viewBox` so gewählt ist, dass der Punkt links oben nicht der Koordinatenursprung ist?) und damit, dass `use` in einem `symbol` verwendet wird, kommt das Script nicht klar.
[svgxuse](https://github.com/Keyamoon/svgxuse) tut, was es soll. Damit steht der Verwendung von SVG-Icon auch im Internet Explorer nichts im Wege. Und es gibt **keinen Grund für Icon-Fonts** mehr. **Keinen!** Weitere Literatur:
* Chris Coyier: [SVG `use` with External Reference, Take 2](https://css-tricks.com/svg-use-with-external-reference-take-2/)
* Chris Coyier: [Inline SVG vs Icon Fonts](https://css-tricks.com/icon-fonts-vs-svg/)
* Tyler Sticka: [Seriously, Don’t Use Icon Fonts](https://cloudfour.com/thinks/seriously-dont-use-icon-fonts/)
* Sven Wolfermann: [SVG Sprites vs. Icon-Fonts](http://maddesigns.de/svg-sprites-icon-fonts-2309.html)
[^Test]: In der ersten Version hatte ich statt Iframes doch tatsächlich ein [Frameset](https://bittersmann.de/test/use-external-svg-polyfill/frameset.html) gebaut! 😱😏
LLAP 🖖
--
*„Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“* —Kurt Weidemann