Mehr Stolpersteine beim Einsatz von Inline-SVG in der Produktion – Teil II

Avatar of Rob Levin
Rob Levin am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der folgende Beitrag ist ein Gastbeitrag von Rob Levin und Chris Rumble. Rob und Chris arbeiten beide im Produkt-Design-Team bei Mavenlink. Rob ist außerdem Gründer und Gastgeber des SVG Immersion Podcasts und schrieb bereits 2014 den ursprünglichen Artikel „5 Gotchas“. Chris ist ein UI- und Motion-Designer/Entwickler mit Sitz in San Francisco. In diesem Artikel gehen sie auf einige zusätzliche Probleme ein, auf die sie gestoßen sind, nachdem sie vor über 2 Jahren Inline-SVGs in Mavenslink's Flaggschiffanwendung integriert hatten. Die Illustrationen des Artikels stammen von Rob und – im Geiste unseres Themas – sind 100% Vektor-SVGs!

Explorations in Debugging

Wow, es ist über 2 Jahre her, seit wir den Artikel „5 Gotchas Getting SVG Into Production“ veröffentlicht haben. Nun, wir sind auf einige neue Stolpersteine gestoßen, was eine weitere Fortsetzung notwendig macht! Wir werden diese mit 6-10 nummerieren und so dem ersten 5 Gotchas im ursprünglichen Beitrag Tribut zollen :)

Stolperstein Sechs: IE Drag & Drop SVG verschwindet

SVG Disappears After Drag and Drop in IE

Wenn Sie sich das animierte GIF oben ansehen, werden Sie feststellen, dass ich links eine Dropdown-Liste mit Icon-Aufgaben habe, ich versuche, die Zeile aus dem Container-Element des Sortierbaren zu ziehen, und dann, wenn ich die Zeile zurückfallen lasse, sind die SVG-Icons vollständig verschwunden. Dieser heimtückische Bug schien unter Windows 7 IE11 in meinen Tests nicht aufzutreten, trat aber unter Windows 10 IE11 auf! Obwohl das Problem in unserem Beispiel durch die Kombination von jQuery UI Sortable und dem nestedSortable-Plugin verursacht wird (das Elemente vom Container ziehen muss, um die Verschachtelung zu erreichen), könnte jede Art von Abtrennung von DOM-Elementen und/oder deren Verschiebung im DOM zu diesem Verschwinden führen. Seltsamerweise konnte ich zum Zeitpunkt des Schreibens kein Microsoft-Ticket finden, aber wenn Sie Zugriff auf ein Windows 10 / IE11-Setup haben, können Sie selbst sehen, wie dies in diesem einfachen Pen passiert, der von fergaldoyle geforkt wurde. Der Pen zeigt das gleiche essentielle Verschwinden, aber diesmal wird es durch einfaches Verschieben eines Elements, das ein SVG-Icon enthält, mithilfe von JavaScripts appendChild verursacht.

Eine Lösung hierfür ist, das Attribut href.baseVal zurückzusetzen für alle <use>-Elemente, die vom Container-Element von event.target abstammen, wenn ein Callback aufgerufen wird. Im Falle der Verwendung von Sortable konnten wir beispielsweise die folgende Methode aus dem stop-Callback von Sortable aufrufen

function ie11SortableShim(uiItem) {
  function shimUse(i, useElement) {
    if (useElement.href && useElement.href.baseVal) {
      // this triggers fixing of href for IE
      useElement.href.baseVal = useElement.href.baseVal;
    }
  }

  if (isIE11()) {
    $(uiItem).find('use').each(shimUse);
  }
};

Ich habe die Implementierung von isIE11 weggelassen, da sie auf verschiedene Weise erfolgen kann (leider am zuverlässigsten durch Abfragen des window.navigator.userAgent-Strings und Abgleich eines Regex). Aber die allgemeine Idee ist, alle <use>-Elemente in Ihrem Container-Element zu finden und dann deren href.baseVal neu zuzuweisen, um IE dazu zu bringen, diese externen xlink:hrefs neu zu laden. Nun, Sie haben möglicherweise eine ganze Zeile komplexer verschachtelter Unteransichten und müssen möglicherweise einen etwas brachialeren Ansatz verfolgen. In meinem Fall musste ich auch

$(uiItem).hide().show(0);

um die Zeile neu zu rendern. Ihre Ergebnisse können variieren ;)

Wenn Ihnen das außerhalb von Sortable passiert, müssen Sie wahrscheinlich nur in ein „After“-Ereignis für das übergeordnete/Container-Element einhaken und dann dasselbe tun.

Da mich dieses IE11-spezifische Problem verwirrt, würde ich gerne hören, ob Sie dieses Problem selbst schon hatten, alternative Lösungen haben und/oder mehr Verständnis für die zugrundeliegenden IE-Probleme haben, hinterlassen Sie also bitte einen Kommentar, wenn ja.

Stolperstein Sieben: IE Performance-Boosts durch Ersetzen von SVG4Everybody durch Ajax-Strategie

Performance Issues

Im ursprünglichen Artikel empfahlen wir die Verwendung von SVG4Everybody als Mittel zur Unterstützung von IE-Versionen, die keine externen SVG-Definitionsdateien unterstützen und dann über das xlink:href-Attribut referenzieren. Es stellt sich jedoch heraus, dass dies problematisch für die Performance ist und wahrscheinlich auch kludgier, da es auf User-Agent-Sniffing-Regex basiert. Ein „geradlinigerer“ Ansatz ist die Verwendung von Ajax, um den SVG-Sprite zu laden. Hier ist ein Ausschnitt unseres Codes, der dies tut, was im Wesentlichen dasselbe ist wie das, was Sie im verlinkten Artikel finden.

  loadSprite = null;

  (function() {
    var loading = false;
    return loadSprite = function(path) {
      if (loading) {
        return;
      }
      return document.addEventListener('DOMContentLoaded', function(event) {
        var xhr;
        loading = true;
        xhr = new XMLHttpRequest();
        xhr.open('GET', path, true);
        xhr.responseType = 'document';
        xhr.onload = function(event) {
          var el, style;
          el = xhr.responseXML.documentElement;
          style = el.style;
          style.display = 'none';
          return document.body.insertBefore(el, document.body.childNodes[0]);
        };
        return xhr.send();
      });
    };
  })();

  module.exports = {
    loadSprite: loadSprite,
  };

Das Interessante daran für uns war, dass wir auf unseren Icon-reichen Seiten in IE11 von ca. 15 Sekunden auf ca. 1-2 Sekunden (für den ersten un-gecatchten Seitenaufruf) kamen.

Etwas zu bedenken bei der Verwendung des Ajax-Ansatzes: Sie müssen möglicherweise mit einem „Flash of no SVG“ umgehen, bis die HTTP-Anfrage aufgelöst ist. Aber in Fällen, in denen Sie bereits eine stark ladende SPA-ähnliche Anwendung haben, die einen Spinner oder Fortschrittsanzeiger anzeigt, könnte das ein versunkener Kostenpunkt sein. Alternativ können Sie Ihre SVG-Definition/Sprite auch direkt einbetten und den Cache-Treffer für eine bessere wahrgenommene Leistung in Kauf nehmen. Wenn ja, messen Sie genau, wie stark Sie die Payload erhöhen.

Stolperstein Acht: Gestaltung von Nicht-skalierbaren Strich-Icons

In Fällen, in denen Sie verschiedene Größen desselben Icons wünschen, möchten Sie möglicherweise die Strichgrößen dieser Icons sperren…

Warum, was ist das Problem?

Strokes VS Fills

Stellen Sie sich vor, Sie haben ein Icon mit height: 10px; width: 10px; mit einigen 1px Formen und skalieren es auf 15px. Diese 1px Formen werden dann 1,5px sein, was zu einem weichen oder unscharfen Icon führt, da die Ränder auf Subpixelgrenzen angezeigt werden. Diese Weichheit hängt auch davon ab, auf welche Größe Sie skalieren, da dies beeinflusst, ob Ihre Icons auf Subpixelgrenzen liegen. Im Allgemeinen ist es am besten, die Schärfe Ihrer Icons zu kontrollieren, anstatt sie dem Willen des Browsers des Betrachters zu überlassen.

Das andere Problem ist eher ein Problem des visuellen Gewichts. Wenn Sie ein Standard-Icon mit Füllungen skalieren, skaliert es proportional... Ich kann Sie sagen hören: "SVGs sollen das doch tun." Ja, aber die Möglichkeit, den Strich Ihrer Icons zu kontrollieren, kann ihnen helfen, sich besser zueinander zugehörig zu fühlen und als Familie gesehen zu werden. Ich denke gerne daran, als würde man eine Text-Schriftart für Titel verwenden, anstatt eine Display- oder Titel-Schriftart, das können Sie tun, aber warum, wenn Sie eine prägnante und scharfe UI haben könnten.

Vorbereitung des Icons

Ich verwende hauptsächlich Illustrator, um Icons zu erstellen, aber viele Werkzeuge da draußen funktionieren auch gut. Dies ist nur mein Workflow mit einem dieser Werkzeuge. Ich beginne mit der Erstellung eines Icons, indem ich mich darauf konzentriere, was es kommunizieren muss, nicht wirklich auf etwas Technisches. Nachdem ich sicher bin, dass es meinen visuellen Anforderungen entspricht, beginne ich, es zu skalieren und anzupassen, um unseren technischen Anforderungen zu genügen. Zuerst passen Sie die Größe und Ausrichtung Ihres Icons am Pixelraster an (⌘⌥Y in Illustrator für Pixelvorschau auf einem Mac) in der Größe, in der Sie es verwenden werden. Ich versuche, Diagonalen im 45°-Winkel zu halten und Kurven oder ungerade Formen anzupassen, um zu verhindern, dass sie seltsam werden. Es gibt keine Formel dafür, bringen Sie es einfach so nah wie möglich an etwas, das Ihnen gefällt. Manchmal verwerfe ich die ganze Idee, wenn sie nicht in der gewünschten Größe funktioniert, und beginne von vorne. Wenn es die beste visuelle Lösung ist, aber niemand sie identifizieren kann... ist sie nichts wert.

Exportieren aus AI

Ich benutze normalerweise die Option „Als SVG exportieren“ in Illustrator, da sie mir einen standardmäßigen und minimalen Ausgangspunkt bietet. Ich verwende die Einstellung „Präsentationsattribute“ und speichere sie ab. Sie sieht dann ungefähr so aus

<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
  <title>icon-task-stroke</title>
  <polyline points="5.5 1.5 0.5 1.5 0.5 4.5 0.5 17.5 17.5 17.5 17.5 1.5 12.5 1.5" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <rect x="5.5" y="0.5" width="7" height="4" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <line x1="3" y1="4.5" x2="0.5" y2="4.5" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <line x1="17.5" y1="4.5" x2="15" y2="4.5" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <polyline points="6 10 8 12 12 8" fill="none" stroke="#ffa800" stroke-miterlimit="10" stroke-width="1"/>
</svg>

Ich weiß, Sie sehen hier ein paar Halbe Pixel! Es scheint ein paar Denkschulen dazu zu geben. Ich ziehe es vor, den Strich am Pixelraster auszurichten, da dieser am Ende angezeigt wird. Die Koordinaten werden auf dem halben Pixel platziert, damit Ihr 1-px-Strich auf jeder Seite des Pfades um 0,5 liegt. In Illustrator sieht das ungefähr so aus

Strokes on the Pixel Grid

Stolperstein Neun: Implementierung von nicht skalierbaren Strichen

Aufräumen

Galactic Vacuum

Unsere Grunt-Aufgabe, über die Rob im vorherigen Artikel spricht, bereinigt fast alles. Leider müssen Sie für den non-scaling-stroke etwas Handarbeit am SVG leisten, aber ich verspreche, es ist einfach! Fügen Sie einfach eine Klasse zu den Pfaden hinzu, bei denen Sie die Strichskalierung einschränken möchten. Fügen Sie dann in Ihrem CSS eine Klasse hinzu und wenden Sie das Attribut vector-effect: non-scaling-stroke; an, was ungefähr so aussehen sollte

.non-scaling-stroke {
  vector-effect: non-scaling-stroke;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
  <title>icon-task-stroke</title>
  <polyline class="non-scaling-stroke" points="5.5 1.5 0.5 1.5 0.5 4.5 0.5 17.5 17.5 17.5 17.5 1.5 12.5 1.5" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <rect class="non-scaling-stroke" x="5.5" y="0.5" width="7" height="4" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <line class="non-scaling-stroke" x1="3" y1="4.5" x2="0.5" y2="4.5" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <line class="non-scaling-stroke" x1="17.5" y1="4.5" x2="15" y2="4.5" stroke="#b6b6b6" stroke-miterlimit="10"/>
  <polyline class="non-scaling-stroke" stroke="currentcolor" points="6 10 8 12 12 8" stroke="#ffa800" stroke-miterlimit="10" stroke-width="1"/>
</svg>

Dies verhindert, dass die Striche, falls vorhanden, verändert werden (mit anderen Worten, die Striche bleiben bei 1 Pixel, auch wenn das gesamte SVG skaliert wird), wenn das SVG skaliert wird. Wir fügen auch fill: none; zu einer Klasse in unserem CSS-Skript hinzu, wo wir auch die Strichfarbe steuern, da sie standardmäßig mit #000000 gefüllt werden. Das ist alles! Jetzt haben Sie schöne, Pixel-genaue Striche, die ihre Strichstärke beibehalten!

Und nach allem Gesagten und getan (und Sie haben es über grunt-svgstore vorverarbeitet, wie im ersten Artikel), sieht Ihr SVG in der Defs-Datei so aus

<svg>
  <symbol viewBox="0 0 18 18" id="icon-task-stroke">
    <title>icon-task-stroke</title>
    <path class="non-scaling-stroke" stroke-miterlimit="10" d="M5.5 1.5h-5v16h17v-16h-5"/>
    <path class="non-scaling-stroke" stroke-miterlimit="10" d="M5.5.5h7v4h-7zM3 4.5H.5M17.5 4.5H15"/>
    <path class="non-scaling-stroke" stroke="currentColor" stroke-miterlimit="10" d="M6 10l2 2 4-4"/>
  </symbol>
</svg>

CodePen-Beispiel

Die Icon-Sammlung auf der linken Seite skaliert proportional, und auf der rechten Seite verwenden wir vector-effect: non-scaling-stroke;. Wenn Sie feststellen, dass die Striche Ihrer skalierten SVG-Icons außer Kontrolle geraten, gibt Ihnen die obige Technik die Möglichkeit, diese zu sperren.

Siehe den Pen SVG Icons: Non-Scaling Stroke von Chris Rumble (@Rumbleish) auf CodePen.

Stolperstein Zehn: Barrierefreiheit

Accessible planet illustration

Bei allem, was dazu gehört, um Ihr SVG-Icon-System einsatzbereit zu machen, ist es leicht, die Barrierefreiheit zu übersehen. Das ist schade, denn SVGs sind von Natur aus barrierefrei, besonders im Vergleich zu Icon-Fonts, die bekanntermaßen nicht immer gut mit Screenreadern funktionieren. Mindestens müssen wir ein wenig Code einfügen, um zu verhindern, dass Text, der in unseren SVG-Icons eingebettet ist, von Screenreadern angekündigt wird. Obwohl wir gerne ein <title>-Tag mit alternativem Text hinzufügen und es dabei belassen würden, haben die Leute von Simply Accessible festgestellt, dass Firefox und NVDA den <title>-Text tatsächlich nicht ankündigen werden.

Ihre Empfehlung ist, das Attribut aria-hidden="true" auf das <svg> selbst anzuwenden und dann ein benachbartes span-Element mit einer Klasse .visuallyhidden hinzuzufügen. Das CSS für dieses visuell versteckte Element wird visuell versteckt, aber sein Text wird für den Screenreader zur Ankündigung verfügbar sein. Ich bin enttäuscht, dass sich das nicht sehr semantisch anfühlt, aber es mag ein vernünftiger Kompromiss sein, während die Unterstützung für das intuitivere <title>-Tag (und Kombinationen von Freunden wie role, aria-labelledby usw.) sowohl im Browser als auch bei Screenreadern funktioniert. Meiner Meinung nach ist aria-hidden auf dem SVG der größte Gewinn, da wir nicht versehentlich z. B. 50 Icons auf einer Seite für den Screenreader auslösen möchten!

Hier ist das allgemeine Muster, das von Simply Accessible's Pen übernommen, aber leicht angepasst wurde

<a href="/somewhere/foo.html">
    <svg class="icon icon-close" viewBox="0 0 32 32" aria-hidden="true">
        <use xlink:href="#icon-close"></use>
    </svg>
    <span class="visuallyhidden">Close</span>
</a>

Wie bereits erwähnt, sind die beiden interessanten Dinge hier:

  1. das Attribut aria-hidden, das angewendet wird, um Screenreadern die Ankündigung von Text innerhalb des SVG zu verhindern.
  2. der hässliche, aber nützliche visuallyhidden-Span, der vom Screenreader angekündigt WIRD.

Ehrlich gesagt, wenn Sie es lieber nur mit dem <title>-Tag usw. codieren würden, würde ich nicht unbedingt widersprechen, da sich das kludgy anfühlt. Aber, wie ich Ihnen den Code zeige, den wir verwendet haben, könnten Sie die Annahme dieser Lösung als eine Implementierung der Version 1 betrachten und den Wechsel später recht einfach vornehmen, wenn die Unterstützung besser ist...

Vorausgesetzt, Sie haben eine Art zentralen Template-Helfer oder ein Utilities-System zum Generieren Ihrer use xlink:href-Fragmente, ist die Implementierung des oben Genannten recht einfach. Wir machen das in Coffeescript, aber da JavaScript universeller ist, hier der Code, der aufgelöst wird zu

  templateHelpers = {
    svgIcon: function(iconName, iconClasses, iconAltText) {
      var altTextElement = iconAltText ? "" + iconAltText + "" : '';
      var titleElement = iconTitle ? "<title>" + iconTitle + "</title>" : '';
      iconClasses = iconClasses ? " " + iconClasses : '';
      return this.safe.call(this, "<svg aria-hidden='true' class='icon-new " + iconClasses + "'><use xlink:href='#" + iconName + "'>" + titleElement + "</use></svg>" + altTextElement);
    },
    ...

Warum platzieren wir das <title>-Tag als Kind von <use> anstatt von <svg>? Laut Amelia Bellamy-Royds (eingeladene Expertin, die SVG- & ARIA-Spezifikationen @w3c entwickelt. Autorin von SVG-Büchern von @oreillymedia) erhalten Sie Tooltips in mehr Browsern.

Hier ist das CSS für .visuallyhidden. Wenn Sie sich fragen, warum wir es auf diese bestimmte Weise und nicht z. B. mit display: none; oder anderen bekannten Mitteln tun, sehen Sie sich Chris Coyiers Artikel an, der dies ausführlich erklärt.

.visuallyhidden {
    border: 0;
    clip: rect(0 0 0 0);
    height: 1px;
    width: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    position: absolute;
}

Dieser Code ist nicht zur direkten Übernahme gedacht, da Ihr System wahrscheinlich Nuancenunterschiede aufweist. Aber er zeigt den allgemeinen Ansatz, und die wichtigen Teile sind:

  • iconAltText, womit der Aufrufer alternativen Text bereitstellen kann, wenn es angemessen erscheint (z. B. wenn das Icon nicht rein dekorativ ist).
  • aria-hidden="true", das nun immer auf dem SVG-Element platziert wird.
  • die Klasse .visuallyhidden versteckt das Element visuell, macht den Text darin aber weiterhin für Screenreader verfügbar.

Wie Sie sehen, wäre es recht einfach, diesen Code später so umzugestalten, dass der üblicherweise empfohlene <title>-Ansatz verwendet wird, und zumindest der Wartungsaufwand wird nicht groß sein, sollten wir uns dafür entscheiden. Die relevanten Umgestaltungsänderungen wären wahrscheinlich ähnlich wie:

var aria = iconAltText ? 'role="img" aria-label="' + iconAltText + '"' : 'aria-hidden="true"';
return this.safe.call(this, "<svg " + aria + " class='icon-new " + iconClasses + "'><use xlink:href='#" + iconName + "'>" + titleElement + "</use></svg>");

Also, in dieser Version (dank Amelia für den Aria-Teil!) verstecken wir das SVG NICHT, wenn der Aufrufer alternativen Text übergibt, und wir verwenden auch nicht die Technik des visuell versteckten Spans, sondern fügen die Attribute role und aria-label zum SVG hinzu. Das fühlt sich viel sauberer an, aber es ist noch nicht klar, ob Screenreader diesen Ansatz genauso gut unterstützen werden wie die Verwendung der Technik des visuell versteckten Spans. Vielleicht melden sich die Experten (Amelia und die Leute von Simply Accessible) in den Kommentaren zu Wort :)

Bonus-Stolperstein: viewBox Breite und Höhe als Ganzzahlen festlegen, sonst wird die Skalierung seltsam

Wenn Sie ein SVG-Icon exportieren mit einem resultierenden viewBox wie: viewBox="0 0 100 86.81", können Sie Probleme haben, wenn Sie transform: scale verwenden. Zum Beispiel, wenn Sie generell Breite und Höhe gleich setzen, wie es üblich ist (z.B. 16px x 16px), würden Sie erwarten, dass sich das SVG in seinem Container-Feld einfach zentriert, besonders wenn Sie die Standardeinstellungen für preserveAspectRatio verwenden. Aber wenn Sie versuchen, es auch nur ein wenig zu skalieren, werden Sie Abschnitte bemerken.

In der folgenden Adobe Illustrator-Bildschirmaufnahme sehen Sie, dass "Auf Raster einrasten" und "Auf Pixel einrasten" beide ausgewählt sind.

Align and Snap to Pixel Grid

Der folgende Pen zeigt, dass die ersten drei Icons abgeschnitten werden. Dieses spezielle Icon (es ist als <symbol> definiert und wird dann über die bereits besprochene xlink:href-Strategie referenziert) hat eine viewBox mit einer nicht-ganzzahligen Höhe von 86,81, und deshalb sehen wir den Zuschnitt an den Seiten. Die nächsten 3 Beispiele (Icons 4-6) haben ganzzahlige Breiten und Höhen (das dritte Argument für viewBox ist die Breite und das vierte die Höhe) und werden nicht abgeschnitten.

Siehe den Pen SVG Icons: Scale Clip Test 2 von Rob Levin (@roblevin) auf CodePen.

Schlussfolgerungen

Die oben genannten Herausforderungen sind nur einige derer, auf die wir bei Mavenlink gestoßen sind, nachdem wir seit über 2 Jahren ein umfassendes SVG-Icon-System in unserer Anwendung haben. Die geheimnisvolle Natur einiger dieser Probleme ist üblich angesichts unserer zersplitterten Welt aus verschiedenen Browsern, Screenreadern und Betriebssystemen. Aber vielleicht helfen Ihnen diese zusätzlichen Stolpersteine, Ihre SVG-Icon-Implementierungen besser abzusichern!