@@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 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.)
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 }
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):
<ul>
<li><svg><use xlink:href="#house"/></svg> mein Haus</li>
<li><svg><use xlink:href="#car"/></svg> mein Auto</li>
<li><svg><use xlink:href="#boat"/></svg> mein Boot</li>
</ul>
(EDIT: href
geändert in xlink:href
. Safari will das so.)
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 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:
<ul>
<li><svg><use xlink:href="/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use xlink:href="/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use xlink:href="/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
bzw.
<ul>
<li><svg><use xlink:href="https://example.net/assets/icons.svg#house"/></svg> mein Haus</li>
<li><svg><use xlink:href="https://example.net/assets/icons.svg#car"/></svg> mein Auto</li>
<li><svg><use xlink:href="https://example.net/assets/icons.svg#boat"/></svg> mein Boot</li>
</ul>
Leider wird die Freude durch IrgendEinen Browser getrübt. Internet Explorer kann zwar <use xlink:href="#house"/>
, wenn die symbol
s in derselben HTML-Datei sind; aber nicht <use xlink:href="icons.svg#house"/>
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 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:
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))
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 eingefallen. Nicht, dass ich nicht gefragt 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]
). 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.
svg4everybody fällt in meinem Test durch. (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 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:
LLAP 🖖
--
„Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann