Nutzung von Kapselung für semantische Auszeichnung

Avatar of Chris Coyier
Chris Coyier am

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

Der folgende Text ist ein Gastartikel von Chris Scott. Chris führt uns durch einen großartigen Anwendungsfall für den Shadow DOM. Als Designer möchten wir Dinge vielleicht auf eine bestimmte Weise gestalten, aber manchmal geraten wir im Kampf mit HTML, CSS und JS, um dies zu erreichen. Und selbst dann können die Ergebnisse schwerfällig, hacky und unsemantisch sein. Shadow DOM könnte uns davor retten und uns einen neuen Ort geben, an dem wir jedes benötigte HTML verwenden können (brauchen Sie 20 leere Elemente? Kein Problem!), ohne dieses Durcheinander dem eigentlichen DOM auszusetzen (was schlecht für Barrierefreiheit, Semantik usw. wäre).

Datei-Inputs sind notorisch schwer zu stylen. Nehmen wir an, Sie möchten ein SVG-Icon *anstelle* des Standard-Buttons mit Dateinamen-Styling verwenden.

onjective

Das ist keine triviale Styling-Änderung. Input-Elemente sind "no content"-Elemente, d. h. ein Element, das keinen schließenden Tag hat. Daher gibt es keine Möglichkeit, ein SVG-Element "hineinzulegen". Wie könnte man also vorgehen?

Nun, lassen Sie uns einen progressiven Verbesserungsansatz wählen und mit der grundlegenden Funktionalität beginnen.

<form>
  <input type="file"></input>
</form>

Was bekommen wir "out of the box"? Semantisch ist es sehr beschreibend: Es gibt ein Element, das dem Benutzer ermöglicht, eine Datei einzugeben. Es ist auch funktional: Wenn Sie darauf klicken, öffnet sich ein Dateisystemdialog.

Denken wir nun über das Hinzufügen des Icons nach. Sie könnten das Bild in die Nähe des Inputs setzen, vielleicht unter Verwendung von z-Indizes und transparenten Inputs, um die Funktionalität des Inputs beizubehalten (wie dies). Dieser Ansatz ist semantisch in Ordnung (Sie haben einen Input *und* ein Bild), aber er fühlt sich definitiv hacky an und ist schwieriger zu testen. Alternativ könnten Sie ein <img>-Tag verwenden und den <input> ganz weglassen, aber dann haben Sie all die Probleme, die Funktionalität des Datei-Inputs zu replizieren, und Sie haben einen Großteil der Bedeutung aus dem Markup verloren.

(Es gibt auch andere Wege, dies zu tun. Zum Beispiel ein Label mit einem versteckten Input, aber um zum Punkt zu kommen, machen wir weiter.)

Shadow DOM

Ein besserer Ansatz, meiner Meinung nach, ist die Verwendung von Shadow DOM. Shadow DOM ist eine der Zutaten für Web Components, und Sie können all das hier nachlesen. Ich möchte jetzt nur über seine semantischen Vorteile sprechen.

Shadow DOM wird in der W3C-Entwurfspezifikation als "funktionale Kapselung" oder "funktionale Grenzen" beschrieben. Das bedeutet, wir können ein Design *kapseln* und es an einen bestehenden Knoten im DOM-Baum anheften (genannt Shadow Root). Zurück zu unserem Beispiel:

var button = document.querySelector(&#x27;input[type=&quot;file&quot;]&#x27;);
var shadowDom = button.webkitCreateShadowRoot();
shadowDom.innerHTML = &quot;&lt;div&gt;Hello from the other side&lt;/div&gt;&quot;;

Siehe den Pen gHEei von chrismichaelscott (@chrismichaelscott) auf CodePen.

Werfen Sie einen Blick auf das Ergebnis im obigen Pen. Dies mag wie ein langwieriger Weg erscheinen, um etwas HTML zu ändern, aber das ist nicht wirklich passiert. Wenn Sie in Ihrem Browser "Element untersuchen" auswählen, sehen Sie immer noch den ursprünglichen Datei-Input – kein div. Wichtiger ist, dass, wenn Sie auf den String klicken, der Browser einen Dateisystemdialog öffnet. Das ist der Kern der Kapselung: Von außen ist der Input immer noch ein input, aber wenn Sie die "funktionale Grenze" durchqueren, ist es kein input mehr, sondern ein String. Das bedeutet, wir können einen Input (oder jedes andere Tag) beliebig rendern. Wir können das innere HTML des Shadow Root auf jedes gültige HTML setzen, und es wird entsprechend gerendert. Dies beinhaltet natürlich das Hinzufügen von SVG.

var button = document.querySelector(&#x27;input[type=&quot;file&quot;]&#x27;);
var shadowDom = button.webkitCreateShadowRoot();
shadowDom.innerHTML = &quot;&lt;img src=\&quot;https://s3-eu-west-1.amazonaws.com/chrisscott/codepen/iconmonstr-archive-7-icon.svg\&quot; alt=\&quot;Select file\&quot;&gt;&lt;/img&gt;&quot;;

Siehe den Pen jiqoh von chrismichaelscott (@chrismichaelscott) auf CodePen.

Das ist offensichtlich keine besonders gut aussehende Alternative, aber es ist ein Anfang. Und das HTML ist einfach und semantisch korrekt. Um dies weiter zu vertiefen, betrachten wir ein weiteres Werkzeug in der HTML5 Web Components-Toolbox, das für diese Art von Dingen nützlich ist: template-Tags.

Templates

Template-Tags sind im Grunde eine Möglichkeit, HTML zu erstellen, das nicht gerendert wird. Das Coole an Templates ist, dass man mit JavaScript das DOM aus einem Template in ein Element an anderer Stelle im Dokument injizieren kann. In diesem Fall können wir ein Template für unseren schicken Vektorbild-Datei-Input definieren und dieses verwenden, um den Shadow DOM für tatsächliche input-Tags zu definieren. Hier ist ein geeignetes Template:

<template id="file-button-template">
  <style>
    img {
      padding: 6px;
      border: 1px solid grey;
      border-radius: 4px;
      box-shadow: 1px 1px 4px grey;
      background-color: lightgrey;
      width: 30px;
      cursor: pointer;
    }
    img:hover {
      background-color: whitesmoke;
    }
  </style>
  <img src="https://s3-eu-west-1.amazonaws.com/chrisscott/codepen/iconmonstr-archive-7-icon.svg" alt="Select file"></img>
</template>

Alles zusammenbringen

Das template-Element kann im head eines Dokuments platziert werden, und ich denke, das ist ein guter Ort dafür. Der folgende Pen enthält das gerade definierte Template im head. Wenn Sie ihn auf CodePen.io öffnen, können Sie das Template sehen, indem Sie auf das Zahnradsymbol oben links im HTML-Panel klicken.

Siehe den Pen jghes von chrismichaelscott (@chrismichaelscott) auf CodePen.

Ziemlich gut, würde ich sagen. Semantisch ist es genau dasselbe wie reines HTML, aber visuell ist es komplett maßgeschneidert.

Browserunterstützung, Polyfills und Graceful Degradation

Es gibt schlechte Nachrichten (dann noch mehr schlechte Nachrichten und dann ein kleines, winziges Fünkchen guter Nachricht) in Bezug auf die Unterstützung von Shadow DOM in Browsern.

Die erste schlechte Nachricht ist, dass die Bearbeitung von Shadow DOM derzeit nur in WebKit/Blink-Browsern mit der präfixierten Methode element.webkitCreateShadowRoot() unterstützt wird. Firefox 30 sollte Shadow DOM ebenfalls unterstützen, wenn auch mit Präfix.

Schlechte Nachricht Nummer zwei: Polyfills unterstützen diesen Anwendungsfall möglicherweise nicht – sicherlich Polymer nicht. Im Fall der Polymer-Plattform muss man, um zu verstehen, warum das Polyfill nicht so funktioniert wie die native Implementierung, die Mechanismen des Polyfills verstehen. Im Wesentlichen führt die Polymer-Plattform ihre Kapselung durch, indem sie native DOM-Knoten durch *Wrapper* ersetzt. Diese Wrapper emulieren das Verhalten tatsächlicher DOM-Knoten; man kann zum Beispiel innerHTML des Wrappers so setzen, wie man es bei einem nativen Knoten tun würde. Die Wrapper behalten den sogenannten Light DOM und Shadow DOM. In Bezug auf die Kapselung funktioniert das ziemlich gut, da der Wrapper Aufrufe auf der "Light"-Seite abfangen (wie den children Getter) und den Shadow DOM verbergen kann.

var light = document.querySelector(&quot;div&quot;);
var template = document.querySelector(&quot;template&quot;);

light.createShadowRoot().innerHTML = template.innerHTML;

document.querySelector(&quot;code&quot;).innerHTML = 
  light.innerHTML

Siehe den Pen FCknf von chrismichaelscott (@chrismichaelscott) auf CodePen.

In diesem Beispiel zeigt die abgerufene innere HTML des "Light" DOM die funktionierende Kapselung (es gibt kein h1-Tag). Das Polyfill hat jedoch kein tatsächliches Shadow DOM verwendet (sonst wäre es kein Polyfill!) und Sie können dies verifizieren, wenn Sie das erste div aus den Ergebnissen untersuchen. Im DOM sehen Sie, dass es ein h1-Tag gibt – nicht im Shadow DOM versteckt, sondern einfach da. Das liegt daran, dass die Polymer-Plattform unter dem Wrapper einfach den regulären DOM-Baum manipuliert, da kein Shadow-Root vorhanden ist.

In Bezug auf das SVG-Icon für den Datei-Input ist das nicht gut. Wenn man die Demo des Datei-Inputs mit dem Polymer-Polyfill ausführen würde, würde sie, wie im vorherigen Beispiel, einfach das img-Tag als Kind des Inputs einfügen. Wenn Sie sich erinnern, ist das nicht erlaubt. Ein Input ist ein *void* Element und hat keine erlaubten Kindelemente, daher wird der Browser die img- und style-Elemente als ungültige Kinder einfach ignorieren. Hier ist ein Fork des Pens, der das Problem illustriert; inspizieren Sie erneut das Element und Sie können verifizieren, dass das Polyfill das Template als Kind des Inputs hinzugefügt hat.

Und nun zur versprochenen "winzigen Glimmer von guter Nachricht"... Auch wenn die Unterstützung derzeit begrenzt ist, denke ich immer noch, dass es gerechtfertigt ist, Shadow DOM für solche Dinge jetzt zu verwenden. Warum? Wenn der Browser Ihres Benutzers Shadow DOM nicht unterstützt, wird einfach ein normaler Datei-Input gerendert. Das ist keine schlechte Sache, und angesichts der komplexen (sprich: *hacky*) Alternativen denke ich, dass Shadow DOM eine plausible Wahl sein sollte.

Anmerkungen zu Shadow DOM, Web Components usw.

Es gibt eine riesige Anzahl von Anwendungsfällen für Shadow DOM und die zugehörigen Web Components. Dieser Artikel hat die Vorteile der Kapselung in Bezug auf die Entwicklung von Frameworks und Widgets vollständig außer Acht gelassen. Für Interessierte lohnt es sich, die Spezifikationen zu lesen und sich mit Polymer auseinanderzusetzen.