Geschrieben von

Webschriften und Pagespeed

WebDev

Typografie ist ein wichtiger Bestandteil des Designs und spielt bei der Nutzerfreundlichkeit der Webseite eine große Rolle. Wenn es um das Thema Pagespeed bei Webschriftarten geht, wird dem Thema wenig Beachtung geschenkt, obwohl man hier große Leistungssteigerungen erzielen kann.

Bevor wir die Ansätze zur Optimierung angehen zunächst einmal ein paar Probleme, die sich beim Einsatz von Webfonts hinsichtlich Pagespeed ergeben:

  • Webfonts sind zusätzliche Ressourcen
  • Webfonts können das Text-Rendering blockieren

Bei internationalen und mehrsprachigen Webseiten muss zudem berücksichtigt werden, dass eine Schriftart gewählt wird, die alle notwendigen Zeichensätze unterstützt. Dadurch wird sichergestellt, dass alle Sprachen einheitlich aussehen.

Schriftartformate

Im Internet werden aktuell 4 verschiedene Formate bei Schriftarten verwendet:

  • EOT nur für den Internet Explorer
  • TTF wird nur von bestimmten Versionen des Internet Explorers unterstützt
  • WOFF wird von der Mehrzahl der Browser verwendet
  • WOFF 2.0 wird aktuell von nur sehr wenigen Browsern unterstützt

Da es noch kein Format gibt, welches von allen gängigen Browsern unterstützt wird, sollten alle Varianten bereitgestellt werden.

Schriftartfamilie definieren

Bei einer Schriftartfamilie handelt es sich um eine “…Gruppe zusammengehörender Schriftschnitte mit unterschiedlichen Breiten (schmal, breit, …), Strichstärken (leicht, normal, fett, …) und Zeichenlagen…”. Mit der CSS-Regel @font-face können solche Schriftartfamilien erstellt werden. Das kann beispielsweise folgendermaßen aussehen:

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 600;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
}

@font-face {
font-family: 'Coole Font';
font-style: italic;
font-weight: 500;
src: local('Coole Font Italic'),
url('/fonts/cool-i.woff2') format('woff2'),
url('/fonts/cool-i.woff') format('woff'),
url('/fonts/cool-i.ttf') format('ttf'),
url('/fonts/cool-i.eot') format('eot');
}

Im obigen CSS-Code ist auch der SRC-Descriptor (src:) zu erkennen. Dieser gibt den Speicherort der Ressourcen an. Dadurch weiß der Browser, wo die Schriftarten liegen und kann so beurteilen, welche er herunterladen muss. Die Anweisung “local()” referenziert auf eine lokal installierte Webfont; die Anweisung “url()” verweist auf eine externe Webfont. Die externen Schriftarten sind besonders wichtig, da nicht alle Nutzer alle Schriftarten (insbesondere wenn keine Standard-Webfonts eingesetzt werden) lokal installiert haben. So stellt man sicher, dass die gewünschte Schriftart allen Nutzern angezeigt wird.

Sobald der Browser eine Schriftart benötigt, arbeitet er die obige CSS-Anweisung in der definierten Reihenfolge ab, bis er auf die benötigte Ressource stößt. Zunächst wird dabei geprüft, ob die Schriftart lokal vorliegt. Wenn nicht, dann geht der Browser zu den externen Ressourcen über. Bevor er diese herunterlädt wird nochmal überprüft, ob das definierte Format unterstützt wird. Danach wird der Download eingeleitet.

Wieso ist das für die Performance der Seite wichtig? Durch die obigen Anweisungen können wir dem Browser alle verfügbaren Schriftarten zur Verfügung stellen. Der Browser kann ermitteln, welche der Schriftarten notwendig ist, das optimale Format wählen und dieses herunterladen.

Unicode-Bereichs-Descriptor

Beim Unicode-Bereichs-Descriptor handelt es sich um eine zusätzliche Angabe innerhalb von @font-face. Diese Angabe besteht aus einem Codepoint, Intervallbereich und Platzhalterbereich. Mit dem Unicode-Bereichs-Descriptor können wir eine große Schriftart in chinesische oder kyrillische Zeichen unterteilen.

Bleiben wir bei unserem Beispiel “Coole Font”, normal, 600:

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 600;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
}

Wenn wir diese Schriftart nun in Lateinisch und Japanisch unterteilen, kann der Browser – je nach Bedarf – die notwendige Schrift herunterladen. Der Code würde folgendermaßen aussehen:

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 600;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+000-5FF; /* Lateinisch */
}

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 600;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+3000-9FFF, U+ff??; /* Japanisch */
}

Durch dieses Verfahren kann die notwendige Schriftart schneller und effizienter vom Browser verarbeitet und heruntergeladen werden. Schriften, die für den Nutzer nicht notwendig sind, werden erst gar nicht verwendet, was zu Performance-Verbesserungen führt.

Der einzige Nachteil bei dieser Methode: Es wird nicht von allen Browsern unterstützt. Einige Browser ignorieren diese Angaben und laden alle Schriftarten herunter.

Schriftvarianten minimieren

Bei jeder Schriftart können wir verschiedene Stile wie fett, kursiv und normal sowie Stärken definieren. In unserem Beispiel sehen wir die Schriftstärke anhand der Angabe “font-weight: 600;”. Bleiben wir bei unserem obigen Beispiel mit der japanischen Unicode-Bereichsuntergruppe und definieren für diese 2 verschiedene Stärken:

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 600;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+3000-9FFF, U+ff??; /* Japanisch */
}

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 900;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+3000-9FFF, U+ff??; /* Japanisch */
}

Wie wir sehen können haben wir einmal die Stärke 600 und einmal 900 definiert. Was passiert aber, wenn wir z.B. einen Textbereich im CSS mit der Stärke 800 auszeichnen? Die Stärke 800 wurde von uns im @font-face nicht definiert. In so einem Fall wählt der Browser eine Schrift mit ähnlicher Stärke aus. Die nächst nähere wäre in unserem Beispiel die Stärke 900. Wollen wir aber dennoch erreichen, dass der Browser die Stärke 800 anzeigt, müssten wir also unsere @font-face-Deklaration erweitern:

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 600;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+3000-9FFF, U+ff??; /* Japanisch */
}

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 800;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+3000-9FFF, U+ff??; /* Japanisch */
}

@font-face {
font-family: 'Coole Font';
font-style: normal;
font-weight: 900;
src: local('Coole Font'),
url('/fonts/cool.woff2') format('woff2'),
url('/fonts/cool.woff') format('woff'),
url('/fonts/cool.ttf') format('ttf'),
url('/fonts/cool.eot') format('eot');
unicode-range: U+3000-9FFF, U+ff??; /* Japanisch */
}

Gibt es einen Nachteil? Ja! Jede @font-face-Deklaration erzeugt einen separaten Download. Aus Pagespeed-Sicht empfiehlt es sich, die Anzahl der Varianten zu verringern, damit unnötige Downloads vermieden werden.

Das gilt nicht nur für die Schriftstärke, sondern kann auch auf Stile, Schriftart, etc. ausgedehnt werden. Je weniger unterschiedliche Schriftarten und -varianten erzeugt werden, desto weniger muss der Browser herunterladen, was den Pagespeed verbessert.

Komprimierung

  • Für EOT und TTF sollte die Gzip-Komprimierung aktiviert werden. Standardmäßig sind diese Formate nicht komprimiert.
  • Bei WOFF sollte man die integrierten Komprimierungseinstellungen konfigurieren.
  • Einige Schriftartformate haben Meta-Daten, die – sofern sie nicht benötigt werden – entfernt werden können.
  • Für EOT, TTF und WOF kann zudem Zopfli herangezogen werden, ein Komprimierungsprogramm, welches gegenüber Gzip weitere 5 % an Dateigröße sparen kann.

Schriftart-Rendering optimieren

Fassen wir bisher nochmal zusammen: Durch die CSS-Regel @font-face können wir Schriftartfamilien mit verschiedenen Deklarationen und Varianten definieren sowie den Unicode-Bereichs-Descriptor angeben, damit der Browser nur die aktuell benötige Schrift lädt und nicht alles herunterladen muss. Dadurch erhöhen wir insgesamt die Performance der Seite.

Aber gibt es Verfahren wie wir Webfonts für den Critical Rendering Path optimieren können? Ja. Dabei ist ein Schritt – während der Browser die Seite lädt – besonders wichtig. Nachdem die Rendering-Baumstruktur (DOM plus CSSOM) erstellt worden ist, wird die Schriftart für den Text vorgegeben. Im anschließenden Layout-Prozess werden die die Inhalte dargestellt (ist in diesem Schritt die Schrift noch nicht verfügbar, kann der Browser den Text nicht anzeigen). Dieser Schritt ist hinsichtlich Pagespeed besonders wichtig. Zwischen dem Rendering des Seiteninhalts (direkt nachdem die Baumstruktur steht) und der Anforderung der Schrift kann es zur Darstellung der Seite ohne Text kommen. Diese Zwischenzeit behandelt jeder Browser anders:

  • Chrome und Firefox: Zeigen 3 Sekunden lang keine Schrift an. Danach wird eine browserinterne Schriftart verwendet. Erst wenn die benötige Font komplett heruntergeladen wurde, wird diese angezeigt.
  • Internet Explorer: Verwendet sofort eine browserinterne Schriftart. Erst wenn die benötige Font komplett heruntergeladen wurde, wird diese angezeigt.
  • Safari: Die Textdarstellung wird so lange verzögert, bis die Schrift heruntergeladen wurde.

Diese Zwischenzeit ohne Text – also “unsichtbaren” Text – und Bereitstellen der Font wird auch als FOIT (Flash Of Invisible Text) bezeichnet. Hingegen bezeichnet FOUT (Flash Of Unstyled Text) die Zeit zwischen Fallback-Schriftart und der tatsächlich angezeigten Schriftart.

Was kann man tun, um das zu optimieren? Hier kommt das Font Loading API zum Einsatz. Mit dieser Anweisung können wir den Browser anweisen, dass die Ressource sofort geladen werden soll. Hinweis: Font Loading API wird noch nicht von allen Browsern unterstützt.

Font Loading API

Schriften wie Arial oder Times New Roman sind bei fast allen Nutzern vorinstallierte Schriften, die schnell zur Verfügung stehen. Neue Schriften werden von der CSS-Regel @font-face vom Browser heruntergeladen. Jedoch dauert es bei diesen neuen Schriften deutlich länger, bis sie dargestellt werden. Hier kann es zu dem oben genannten Problem kommen, dass der Browser zu einer Standardschrift in der Zwischenzeit greift. Hier schaffen JavaScript-Bibliotheken wie TypeKits Web Font Loader oder Font Face Observer Abhilfe. Hier können Schriften dynamisch per JS geladen werden. Zudem wird man über entsprechende Events informiert, wann und ob eine Schrift geladen wurde. Mit der Font Loading API werden die Interfaces “FontFace”, “FontFaceSet”, “FontFaceSource” und “FontFaceSetLoadEvent” definiert. Wie das im Detail funktioniert kann hier nachgelesen werden: https://www.heise.de/developer/artikel/Features-von-uebermorgen-Font-Loading-API-3278867.html.

Gibt es alternativen zur Font Loading API? Ja! Die Schriftarten können inline in ein eigenes Stylesheet (mit passenden Medienabfragen) geschrieben werden. Was passiert dann? Da für die CSSOM-Erstellung das Stylesheet notwendig ist, wird der Browser dieses mit Priorität herunterladen. Dadurch kann der Text direkt nach der Baumstruktur dargestellt werden.

Caching von Schriften

Da Schriftarten nicht sehr häufig geändert werden, sollte man folgende Anweisungen im HTTP-Caching mitgeben:

  • Max-Age: Mit max-age wird das Ablaufdatum der Schriftart festgelegt. Legt man bspw. max-age=40 fest, so bleibt die Ressource 40 Sekunden im Browser zwischengespeichert und muss nicht erneut geladen werden, falls der Nutzer diese innerhalb der nächsten 40 Sekunden erneut anfordert. Bei Schriftarten sollte eine lange Ablaufdauer angegeben werden.
  • HTTP-ETag: Beim ETag handelt es sich um eine Zeichenkette, die der Server bei der ersten Anfrage der Ressource im ETag-Header-Feld an den Client sendet. Die Ressourcen und das ETag-Header-Feld werden zusammen lokal abgespeichert. Wenn die gleiche Ressource erneut aufgerufen wird, sendet der Client diesen Wert mit. Der Server vergleicht nun den zugeschickten Wert mit den Aktuellen. Falls diese übereinstimmen muss die Ressource nicht nochmal mitgeschickt werden und kann lokal abgerufen werden.