Browser…​unplugged
Showcases of modern Browser technologies

4. August 2016

Eine Werkzeugkiste für SVG-Icons und Stylesheets

Die Verwenduing von Stylesheets mit eigenständigen SVG-Dateien ist nicht weit verbreitet. Browser verstehen sie, manche Renderer wie librsvg  jedoch nicht. Die Benutzerführung in Editoren unterstützt sie meist nicht, oder ist zumindest nicht hilfreich, selbst wenn sie verarbeitet werden.

Ich habe ein node.js-Modul  veröffentlicht, das versucht diesen Stand der Dinge zu überbrücken und einen Arbeitsablauf etabliert, der

  • Stylesheets in SVG-Dateien einfügt, optional mit Kompilierung aus Sass
  • alle Stile aus Stylesheets auf die style-Attribute von Elementen verteilt ("inline")

Zweitens exportiert es einzelne Objekte aus solchen Dateien in PNG- oder SVG-Dateien (Icons). Dieser Teil des Workflows beruht auf den Kommandzeilen-Funktionen von Inkscape , um

  • die Bounding Box von Objekten zu identifizieren: eine komplexe Aufgabe, die sich nicht einfach implementieren lässt, deshalb der Rückgriff auf eine bestehende Lösung
  • nach PNG zu exportieren: dies stellt die korrekte Interpretation von Stylesheets sicher

Icon-Sammlungen (oder grafische Bestandteile zum Beispiel eines Themes) haben den Anspruch, sich in einheitlichem Design zu präsentieren, wenn es um Farbe, Größe, Striche oder auch um komplexere Begriffe wie Bildwelten oder Metaphern geht.

Designer können solche Charakteristiken am besten überwachen, wenn sie in einer integrierten Umgebung arbeiten und redundanzfreie Sprache verwenden, bei der Gemeinsamkeiten nur einmal definiert und dann wiederverwendet werden. Kurz gesagt: die Definiton von Formen sollte in SVG-Dateien von den Stileigenschaften in Stylesheets separiert werden - diese am besten in einer höheren Sprache wie Sass.

Für den praktischen Einsatz werden dann andere Formate benötigt. Icons müüsen möglicherweise in Einzeldateien vorliegen, in pixelorienterten und/oder Vektor-Format. Die Konversion ist ein rein technischer Prozess, der mit diesm Modul automatisiert werden kann.

XML-Bearbeitung mit Cheerio

Ich habe guter Erfahrungen mit Cheerio  gemacht. Es ist eine Bibliothek, mit der sich SVG-Dateien schnell und in einer flüssigen Sprache bearbeiten lassen. Sie parst XML/HTML-Dateien in einen DOM-artigen Baum, ohne diejenigen Funktionalitäten zu implementieren, die ein Headless-Browser benötigen würde, um das Rendern zu emulieren. Was nicht den XML-Text betrifft, ist auch nicht vorhanden. Die API von Cheerio ist fast identisch zu jQuery. Man lädt einen String mit

var $ = cheerio.load(svgString, {
    xmlMode: true
});

und benutzt dann die bekannten Funktionen. Hier ein Beispiel:

Ich habe das "Inlinen" von Stylesheets als zweistufigen Prozess implementiert. Zuerst werden die Style-Regeln aus dem Stylesheet allen Elementen zugeordnet, die die Selektoren jeweils auswählen:

/*
 * rule = {
 +    selectors: string[] Liste von Selektoren, nach Kommas getrennt
 +    declarations: declaration[] Liste von Objekten mit "property" und "value"
 + }
 */
rule.selectors.forEach((selector) => {
    // Wähle alle ELemente aus, auf die der Selektor zutrifft
    var $selected = $(selector);
    $selected.each((i, elem) => {
        // füge ein Datenobjekt mit dem Selektor als Schlüssel an (vereinfacht)
        $(elem).data(selector, rule);
    });
});

Danach, wenn alle Regeln verteilt wurden, werden die Stile in style-Attribute geschrieben, wobei die Kaskadierungs-Regeln beachtet werden müssen:

// $el ist ein einzelnes Element
// Sortiere alle anwendbaren Regeln nach ihrere Spezifizität
var sorted = sortSpecificity(Object.keys($el.data()));
sorted.forEach((selector) => {
    // lese das Regel-Objekt aus dem verbundenen Datenobjekt
    var rule = $el.data(selector);
    rule.declarations.forEach((declaration) => {
        // vergleiche jede Deklaration mit den schon im style-Attribut vorhandenen
        // Die höchste Spezifizität wird zuerst geschrieben, spätere schlagen fehl
        if (!$el.css(declaration.property)) {
            // fügen neue Eigenschaften im style-Attribut an
            $el.css(declaration.property, declaration.value);
        }
    }, this);
});