EINLEITUNG
Zur Zeit schreibe ich an einer BlogPost-Serie über Microservices und unveränderbare Infrastruktur. Im Teil zwei der Serie möchte ich einfach einen OAuth-Workflow mit GitHub visualisieren. Bisher hätte ich dazu mehrere Grafiken erstellt und diese mit Text nacheinander eingebunden. Doch das nimmt dem ganzen die Dynamik und ist so langweilig. Warum nicht eine Slideshow-Infografik erstellen? Das war mein erster Gedanke. Sofort im Anschluss kam der Gedanke Warum nicht SVG (Scalable Vector Graphics) nutzen? Auch wenn GIFs gerade en vouge sind habe ich meinen Narren an SVGs gefressen. All diese unscharfen Pixelbilder sollten von meinem gestochen scharfen Retina Display verbannt werden! Vektografiken wie SVG sind in jeder Skalierung gestochen scharf.
Damit Sie sehen wovon ich überhaupt spreche, hier das Endergebnis des zu dokumentieren OAuth-Workflows als Slideshow-Infografik:
svg:https://media.comsysto.com/images/2017-03-slideshow-infographics/seevee-stateless-jwt-auth--webfont.svg?v3
Gut. Die Idee war geboren und nun kamen schon wieder die Anforderungen die erfüllt sein sollten.
- Browser Support - Moderne Browser und IE11
- Einfachheit - Einfach zu erstellende Inforgrafiken
- Robustheit - Funktioniert die Animation noch in 10 Jahren? Abhängigkeiten?
Die Umsetzung erfolgte in Form des Hirngespinst Projekts zu finden auf GitHub: https://github.com/codeclou/hirngespinst/.
Ich werde im Folgenden auf die Besonderheiten eingehen, die mir bei der Umsetzung begegnet sind.
GRUNDLEGENDES ZU ANIMATIONEN UND SVGS
Da dieses Thema in vielen anderen Blog-Posts bereits ausführlich behandelt wurde hier nur ein Kurzabriss.
SMIL (Synchronized Multimedia Integration Language) ist faktisch tot, da Chrome bereits den Browser-Support eingestellt hat und künftige Versionen SMIL nicht mehr unterstützen werden. An sich fand ich SMIL eigentlich ganz cool, da die Animation über XML Elemente als Teil des SVG-Codes definiert wird.
Als kurzes Beispiel ein blinkender roter Kreis.
Im Quelltext enthält das circle Element zwei animate Elemente, um die Opacity im Wechsel von 0 auf 1 und von 1 auf 0 zu schalten.
github:f2106497282e4aa8d23da5cb3572338c
Man kann mit SMIL noch mehr machen, aber da es faktisch tot ist, belassen wir es dabei.
Weiter geht es mit CSS3 Animationen mittels ‘@keyframes’ und ‘animate-…‘. Ähnlich wie bei bisherigen CSS Eigenschaften selbst findet nun die Definition der Animation getrennt vom Markup statt. Wir nutzen hierzu ein Inline-Style-Element und animieren wieder unseren blinkenden roten Kreis.
Im Quelltext haben wir nun ein style Element das mittels @keyframes die Animations-Frames definiert und mit Angabe der ‘animation-name’ Eigenschaft innerhalb einer Klasse die Animation zuweist. Mittels ‘animation-duration’ und ‘animation-iteration-count’ werden weitere Eigenschaften der Animation definiert.
github:f2a9eb0a2d89f3ebc357b951e61aca52
An dieser Stelle sei gesagt, dass nicht alle Eigenschaften eines SVGs mittels CSS zu animieren sind. So kann man bspw. leider in manchen Browsern die width eines Rechtecks (Rect) nicht animieren. Hier hilft dann nur JavaScript.
SCHRIFTARTEN UND WHITESPACE
In der Regel wollen wir in den von uns erstellten SVG-Infografiken auch Schriftarten verwenden. Hier gibt es prinzipiell zwei Möglichkeiten:
- (1) Alle Text-Elemente in Pfade umwandeln (Convert to Outlines)
- (2) Webfonts einbetten
Schriften in Pfade umzuwandeln ist sinnvoll, wenn sich bspw. nur ein Wort oder Satz auf einem SVG befindet. Hat man aber viel Text in einem SVG wächst die Dateigröße zu stark an, wenn man alle Text-Elemente in Pfade umwandelt. Ich rate eher davon ab.
Besser fährt man mit eingebettenen Webfonts bspw. direkt von Google Fonts. Verwende ich also in meinem SVG die Schriftart Source Sans Pro Regular so kann ich das Google Font Stylesheet direkt ins SVG einbinden.
github:27d4ef5e28b48e6faf84a6644baf4c7a
Das wird immer funktionieren, außer euer Evil-Server-Admin hat die Hypertext Transport Security (HTST) so restriktiv eingestellt, dass das Laden von CSS aus fremden Quellen untersagt ist. Dazu kommen wir aber später.
Was zu beachten ist:
- Webfont für alle verwendeten Stile einbinden (bold, italic, light, usw.)
- Verwende maximal 2-3 Fonts, um die Ladezeit gering zu halten
Jetzt kommt ein geistiger Exkurs bzgl. tspan-Elemente und Whitespace Verhalten. Als normaler Mensch nimmt man an, dass wenn ich einen Text mit Leerzeichen (Whitespace) einrücke, das dann auch so dargestellt wird. Das dachte ich auch, bis ich feststellen musste, dass es in den unterschiedlichen Browsern zu sehr komischen Interpretationen führte. Generell gibt es zwar eine CSS-Anweisung namens ‘white-space: pre;’ mit verschiedenen Optionen, aber keines führte in meinem Anwendungsfall zum gewünschten Ergebnis. Zumindest nicht bezogen auf alle Browser.
Wovon redet der komische Mann da? Hier ein Beispiel mit eingerücktem JSON Code.
github:a1afde93cfb717d9ac5b909cf28858cc
Wenn wir diesen Text im Vektor-Programm Sketch einpflegen, dann sieht das so aus:
Der zugehörige SVG Quelltext sieht so aus:
github:c66ec7961844696bc1a7545963b6e95b
Was fällt uns auf? Die X-Koordinate ist bei allen Zeilen (tspan) immer gleich und die Leerzeichen sind einfach so im tspan enthalten. Und werden im Default-Fall vom Browser ignoriert. Öffnet man das SVG wiederum in einem SVG-Editor, so wird er sehr wahrscheinlich die Whitespaces berücksichtigen.
Nun wird sich zeigen warum das Projekt Hirngespinst heißt. Denn was wäre, wenn wir alle führenden Leerzeichen eines tspan-Elements einfach durch eine Inkrementierung um 5px der X-Koordinate ersetzen? Das sähe dann so aus:
Genau das macht Hirngespinst automatisch in der Methode fixTspanLeadingWhitespace(). Natürlich könnt ihr auch mit ‘white-space: pre;’ euer Glück versuchen, aber mir haben die Ergebnisse nicht zugesagt.
STYLESHEETS, JAVASCRIPT UND EINBINDUNG VON SVGS IN HTML
Wir haben im vorherigen Abschnitt gesehen, wie man Webfonts mit einer ‘xml-stylesheet’ Anweisung einbindet. Genauso kann man auch andere CSS-Anweisungen einbinden. SVG bietet sogar die Möglichkeit JavaScript einzubinden, was ich mir auch bei Hirngespinst zu nutze gemacht habe.
Man kann beliebige CSS Anweisungen und CSS Animationen mittels ‘xml-stylesheet’ einbinden. Das Hirngespinst Projekt nutzt CSS für folgende Dinge:
- (1) Alle Frames sollen initial ausgeblendet sein (opacity: 0)
- (2) CSS Animationen für Ein- und Ausblenden der Frames (FadeIn, FadeOut)
- (3) Hover Effekte für die Control-Buttons (play, pause usw.)
Mehr nicht? Ja es verwundert irgendwie, denn Anfangs habe ich noch versucht die gesamte Animationslogik mittels CSS umzusetzen, stieß dann aber schnell an folgende Grenzen von CSS Animation in SVGs:
- Chaining: Zeige Frames zeitverzögert nacheinander an. Das geht zwar einmalig mit delay, aber nicht mehrmalig. Siehe Looping.
- Anzahl-Frames: CSS kann nicht (einfach) erkennen viele Frames es gibt. Siehe Looping.
- Looping: Neustart der gesamten Animation nach dem Anzeigen des letzten Frames.
- Browser-Support: Die breite eines Rechtecks (rect) kann in vielen Browsern nicht per CSS animiert werden.
- User-Interaktion: Verwendung von Kontroll-Buttons wie Play, Pause, Next usw. nicht möglich.
Da das so einige Punkte waren, die sich zusammengesammelt haben, bin ich dazu übergegangen JavaScript Animationen zu verwenden. Die Hirngespinst Animationslogik funktioniert im Prinzip so:
- (1) Erkenne die Anzahl der existierenden Frames.
- (2) Initialisiere die Animation.
- (3) Alle 200ms findet ein ‘tick’ statt, der die globale Render-Methode aufruft.
- (3.1) Pro tick wird überprüft, ob es nötig ist gewisse Elemente mit neuen Eigenschaften zu Re-Rendern.
- (3.2) Ist bspw. die Zeit abgelaufen, die ein Frame angezeigt werden soll, so wird die Fade-Out Animation für den vorherigen Frame und die Fade-In Animation für den neuen Frame angestoßen.
- (3.3) Wird erkannt, dass alle Frames angezeigt wurden startet sich die Animation neu (loop).
- (4) Über die Kontroll-Buttons (play, pause, prev, next) lässt sich auch aktiv in die Animation eingreifen.
- (4.1) Drückt man bspw. den ‘Next’-Button, so wird die Re-Render Methode mit gewissen Parametern manuell aufgerufen, und sozusagen ‘zwischen’ den zeitgesteuerten Ticks die Animation in den neuen Zustand versetzt und Re-Rendert.
Nach diesem Exkurs nun zum handwerklichen. Wie binde ich ein SVG in meine HTML Seite ein? Das klingt jetzt banal, aber es gibt zwei sinnvolle Wege SVGs in HTML Seiten einzubinden.
- <img src=”foo.svg” />
- Bindet man ein SVG auf diese Weise ein, wird kein JavaScript Code ausgeführt. In der Regel aber schon die ‘xml-stylesheet’ und Inline-Style Anweisungen interpretiert. Siehe dazu auch den Abschnitt ‘Sicherheit’ im Folgenden.
- <object data=”foo.svg” type=”image/svg+xml” />
- Durch eine Einbindung als Object werden CSS und JavaScript ausgeführt und man kann bspw. auch Text eines SVGs selektieren und kopieren. Auch Dinge wie Hover oder Click Interaktionen mit Elementen sind möglich. Es birgt auch ein Sicherheitsrisiko im Sinne von Cross-Site-Scripting, siehe dazu den Abschnitt ‘Sicherheit’ im Folgenden.
Wie binde ich CSS Anweisungen in SVGs ein?
Wie so oft gibt es auch hier zwei Wege. Über Inline-Styles und mittels ‘xml-stylesheet’. Die Inline-Styles sollte man immer in einem CDATA Block kapseln, da sonst manche Browser evtl. Probleme beim parsen des SVG bekommen.
github:9d62f2b447787175f89deb12dc48c707
Wie binde ich JavaScript-Code in SVGs ein?
Genauso wie bei CSS gibt es bei JavaScript zwei Varianten. Inline-Script oder Externes-Script. Die Inline-Scripts sollte man immer in einem CDATA Block kapseln, da sonst manche Browser evtl. Probleme beim parsen des SVG bekommen. Anders als man es im Browser kennt ist die Anweisung für externe Skripte wirklich ‘script href’ und nicht ‘script src’. Auch wenn der ‘xlink’ Namespace eigentlich veraltet ist, so wissen manche browser mit ‘script href’ nichts anzufangen und man sollte daher zusätzlich ‘xlink:href’ verwenden. Dabei darf man nicht vergessen, den Namespace am Dokumentanfang zu spezifizieren. Wichtig ist auch die Script-Anweisung ans Ende vor das schließende ‘svg’-Tag zu stellen, sodass der DOM bereits geladen ist, wenn das JS geladen wird.
github:e47a268d22c2ec0e2b0ee4aaf04fe50c
INFOGRAFIK ERSTELLEN
Da wir nun alle technischen Möglichkeiten und Einschränkungen kennen, wollen wir eine Slideshow-Infografik erstellen. Was müssen wir dazu tun? Im Prinzip ist der Ablauf sehr einfach:
- (1) Infografik in Sketch zeichnen und als SVG exportieren.
- (2) SVG in einem Texteditor öffnen, Webfonts, CSS und JavaScript einfügen.
- (3) SVG verwenden.
Dank Hirngespinst, können wir die JS-Animationslogik und das zugehörige CSS direkt vom CDN einbinden. Die nötigen Schritte sind mit Bildern und detaillierten Anweisungen bereits ausführlich in der Readme von Hirngespinst dokumentiert und ich verzichte daher an dieser Stelle auf eine erneute Beschreibung und verweise Sie auf die externe Dokumentation.
EINBINDUNG IN SYSTEME WIE CONFLUENCE UND GITHUB README
In Atlassian Confluence lassen sich die Slideshow-Infografiken über das SVG out Plugin für Atlassian Confluence einbinden. Dazu lädt man das SVG als Anhang einer Seite hoch und fügt im Anschluss in der Confluence Seite das Makro ‘SVG out’ ein. Dabei wählt man das entsprechende SVG aus. Wichtig ist hier, dass in den Einstellungen von SVG out entweder alle SVGs erlaubt werden oder md5Summen eingepflegt werden zu erlaubten SVGs. Da das SVG als Object eingebunden wird, müssen Sie selbst entscheiden wie restriktiv Sie die Einbindung bzgl. möglicher Cross-Site-Scripting Angriffsmöglichkeiten gestalten.
Meine initiale Idee war es eigenlich die Infografiken auch in GitHub Readmes einzubinden. Leider kann man keine HTML-Objects in README.md Dateien von GitHub einbinden. Daher ist meine Lösung dazu die Folgende:
- (1) Erstellen eines ‘gh-pages’ Branch im Repository und hochladen des SVGs in den ‘gh-pages’ Branch
- (2) Einbindung eines ‘Preview-PNG’ Bild in die README.md mit einer ‘Play Slideshow’ Aufschrift und Link zum SVG auf GitHub-Pages.
Auf diese Weise habe ich es auch bei der Hirngespinst README.md selbst gemacht und bereits in manchen internen Projekten.
SICHERHEIT: DATENSCHUTZ, CROSS-SITE-SCRIPTING, CONTENT-SECURITY-POLICY, CORS …
Das Thema Datenschutz und Sicherheit kommt oft zu kurz, daher wollen wir uns diesem Thema im Detail widmen. Anfangen werden wir mit etwas Theorie und kommen dann zu technischen Möglichkeiten.
Wir haben gesehen, dass man SVGs als Object einbinden kann mit Webfonts, CSS und JavaScript aus fremden Quellen. Da aber in Deutschland die IP-Adresse des Webseitenbesuchers zu den personenbezogenen Daten zählt und diese besondern Schutz genießen sollte man sich gut überlegen, von welchem Server man etwas einbindet. Lädt man bspw. die Webfonts direkt von Google, so wird die IP-Adresse des Webseitenbesuchers an Google übermittelt. In der Slideshow-Infografik am Anfang dieses Blogposts wird bspw. jeglicher Code und Font über die Domain ‘comsysto.com’ geladen und dabei die IP-Adresse anonymisiert.
In unserem Fall liegen die Daten zwar bei GitHub auf GitHub-Pages, jedoch haben wir einen vorgeschalteten NGINX-SSL-Proxy, der die IP-Adresse des Webseitenbesuchers nicht an GitHub-Pages weiterleitet und auch selbst die IP-Adresse nicht loggt.
Nicht nur aufgrund des Datenschutzes sondern auch aus Sicherheitsgründen sollte man Webfonts und Code selbst hosten. Nehmen wir an wir laden ein Script aus einem CDN. Nun könnte es sein, dass jemand Schadcode in dieses Skript einschleust und uns somit den Schadcode ‘unterjubelt’ - Stichwort Cross-Site-Scripting kurz XSS. Um diesen Angriffsvektor auszuschließen und den Datenschutz zu berücksichtigen sollte man seine Skripte und Webfonts selbst hosten. Gerade für Google-Webfonts gibt es ein super Tool namens google-webfonts-helper, das einem dabei hilft sich die Webfonts auf seiner Infrastruktur aufzusetzen.
Es gibt aber auch noch andere Sicherheitmechanismen, die man anwenden kann, um dem Browser zu verbieten Code von Servern auszuführen, die man nicht auf seiner Whitelist hat. Der Mechanismus heißt Content-Security-Policy kurz CSP und ich kann als weiterführende Lektüre dazu den CSP Quick Reference Guide sehr empfehlen. Was man auf jedenfall verstehen muss, ist dass CSP eigentlich nur aus einer Reihe von HTTP-Response-Header Anweisungen besteht, die der Browser interpretiert. Je nach Browser wird das unterstützt oder nicht. Es bedarf hier ausführlicher Lektüre, um die CSP korrekt auf seine Bedürfnisse anzupassen.
Als einfaches Beispiel könnte unser NGINX-SSL-Proxy auf ‘comsysto.com’ folgenden CSP Header verschicken, der es nur erlauben würde von ‘comsysto.com’ JavaScript zu laden:
github:cfd243a88902e062bfa1f8828488ad2f
Ein weiterer Fallstrick kann Cross-Origin Resource Sharing kurz CORS sein. Diese HTTP-Header-Anweisungen dienen dazu, dem Browser mitzuteilen, von welcher Domain ausgehend bspw. eine JavaScript-Datei eingebunden werden darf.
Legen wir beispielsweise unser JS-Script auf ‘foo.com’ ab und wollen es auf ‘bar.com’ laden, wird der Browser das verbieten, da der Server von ‘foo.com’ keinen entsprechenden Access-Control-Allow-Origin Header sendet. Hier die NGINX Anweisungen, um es zu erlauben:
github:cfa52a2589fe90a31c36bb8b61dd7444
Auch hier sollte man erst nach ausführlicher Lektüre zur Tat schreiten. CSP und CORS sollten aufeinander abgestimmt werden.
Ich fand es sehr spannend einfach mal auf GitHub.com die Entwickler-Tools aufzumachen und mir die HTTP-Header anzusehen. GitHub fährt einen sehr restriktiven CSP Kurs, was ich sehr gut finde.
FAZIT UND AUSBLICK
Eigentlich wollte ich nur einen einfachen Mechanismus zur Erstellung von Slideshow-Infografiken erstellen. Musste mich dann aber zwangsweise mit Datenschutz- und Sicherheitsaspekten befassen. Das Thema zeigt wie vielschichtig und komplex Themen sein können, auch wenn die Anforderung “Ich will bewegte bunte Bilder!” sehr simpel ist. Ich bin trotz der Einschränkungen mit Hirngespinst sehr zufrieden und habe bereits in internen Projekten Dokumentation in Form von Slideshow-Infografiken erstellt und diese in GitHub Readmes und Confluence-Seiten eingebunden.
Der Browser-Support ist mit Hirngespinst und dank intensiver Cross-Browser-Tests gewährleistet. Die Einfachheit ist durch die Erstellung in Sketch auch gegeben. Und zur geforderten Robustheit kann ich nur sagen, wenn sich nichts radikal am SVG support der Browser ändert, sollten die Infografiken mit Hirngespinst auch noch in 10 Jahren funktionieren.
Wer sich weiterführend mit SVGs beschäftigen möchte dem kann ich die Bibliotheken Snap.svg, SVG.js und vivus empfehlen.