Jak efektivně načítat webfonty

Posted 15. 11. 2015 / By Petr Soukup / Rychlost

Jeden z největších problémů při tvorbě rychlých webů je načítání písem. Musíte totiž řešit několik různých formátů, každý prohlížeč (a každá verze) načítá písmo jinak, také ho jinak cachuje a jinak vykresluje. Aby toho nebylo málo, tak se to navíc ještě každou chvíli mění. Úplně nejlepší by bylo používat jen systémová písma, ale to bychom pak neměli co řešit :)

Proč?

Zkuste si načíst svůj web v metru na zastávce. Pokud nemáte metro, můžete použít WebPagetest a nastavit rychlost 2G.

U drtivé většiny webů to dopadne tak, že budete zírat na bílou stránku, protože se odmítnou zobrazit, dokud nemají všechny zdroje staženy - to jsme tu už řešili. Pokud se ale načte layout, tak stejně můžete dál zírat na hezký web, který ale neobsahuje ani písmenko. Prohlížeč totiž ještě čeká na stažení písem a nic nezobrazí, dokud ho nestáhne (různé prohlížeče a verze tuhle situaci řeší různě). Metro se rozjede, vy se můžete až do příští zastávky kochat layoutem a doufat, že v další zastávce se stáhne písmo. A přitom to je úplně zbytečné.

no-webfont

Google Fonts

V příkladech budu používat Google Fonts, protože jsou nejrozšířenější - naprosto stejný postup ale platí i pro jiné služby i pro vaše vlastní fonty.

Pokud nad nimi ohrnujete nos, tak doporučuji ještě je zvážit, protože jsou chytřejší, než by se mohlo zdát. Vždycky například podporují nejmodernější formáty - naposledy například WOFF2, který srazil velikost fontů na třetinu. Také za vás rovnou řeší subsety, takže se zase přenáší menší soubory. Původně jsme si hostovali písma sami, ale nové formáty a možnosti optimalizace se objevují v takovém tempu, že se vyplatí outsourcovat to do Google.

Úplně špatně - @import v CSS

S přehledem nejhorší způsob (zkopírováno z ukázky na Google Fonts) je použít direktivu @import ve vašem CSS. Důsledkem je, že se zpracování vašeho CSS zcela zastaví, dokud se nestáhne externí CSS. Nejen že blokujete načítání, ale zdroje se navíc musí načítat sériově v samostatných spojeních a to je na mobilu konec.

@import url(https://fonts.googleapis.com/css?family=Open+Sans);
body{font-size:10px}
....

Dost špatně - externí CSS

O trochu lepší je vložení externího CSS. Zase je problém, že dokud se nenačte, tak se z webu nic nevykreslí. A stále je nebude vidět žádný text, dokud se nenačte samotné písmo. Alespoň už se ale můžou ostatní zdroje načítat souběžně.

<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>

Špatné obezličky

Možné jste narazili na různé obezličky jako je třeba vkládání fontů in-line jako data-uri. U tohoto řešení musíte na serveru detekovat podporovaný formát písma a podle toho vracet správné CSS. Prohlížeč ale hlavně musí data-uri ještě dekódovat. Fonty nejsou zrovna malé, takže na telefonech takové dekódování může vykreslování úplně zablokovat a ještě bude žrát baterku. Narozdíl od jiných řešení se toto provádí při každém načtení stránky bez ohledu na to, co je v cache. Podobným řešením je obcházení problému přes localStorage. Trpí ale podobnými problémy, takže na něj také zapomeňte.

Tyto obezličky vznikaly v dávných dobách (2014), kdy ještě nebyla lepší řešení a i autoři těchto obezliček už o tom už píší jako "it seemed like a good idea at the time".

Méně špatně - Web Font Loader

Lepší varianta je načítání pomocí Web Font Loaderu. Nejen že načítá asynchronně, ale také využívá moderní Font API (pokud je dostupné), takže vyřeší spoustu problémů za vás. Je sice nutný externí javascript, ale ten je krátký a načítá se asynchronně, takže nic neblokuje. Pořád ale máme problém s nezobrazeným textem, dokud se nenačte písmo.

<script>
  WebFontConfig = {
    google: { families: [ 'Open+Sans::latin' ] }
  };
</script>
<script src="//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js" async defer></script>
<!--
oficiální zápis je výrazně delší, ale takhle je to jednodušší, kratší a zároveň efektivnější
-->

Skoro dobře - Web Font Loader + fallback písmo

Vložení fontů bude úplně stejné jako v minulém příkladu, ale změníme způsob, jakým fonty používáme v našem CSS. Využijeme faktu, že Web Font Loader přidá na celou stránku třídu v momentě, kdy jsou fonty načtené (nejen jejich definice v CSS, ale přímo font).

/** původní zápis */
p {font-family:'Open Sans'}

/** nový chytrý zápis **/
p {font-family:Helvetica,Arial,sans-serif}
.wf-active p {font-family:'Open Sans',Helvetica,Arial,sans-serif}

S tímto vylepšením zajistíme, že text nebude čekat na externí font, protože může použít systémové písmo. Po načtení se doplní třída a prohlížeč změní písmo. Nevýhodou tohoto postupu je, že text změnou problikne. Při prvním načtení může trvat třeba 2-3 sekundy, než se písmo změní - bez tohoto postupu by ale návštěvník ty 2-3 sekundy zíral na stránku bez textu, takže to je slušný kompromis. Změna písma navíc není až tolik vidět.

Horší to je při dalším procházení webu. Písmo už je v cache, ale protože se načítá asynchronně, tak web "bliká". Není to nic hrozného, ale i toho bychom se rádi zbavili.

webfont-loading

Vychytrale - Web Font Loader + fallback písmo + cookie

Zápis CSS zůstane stejný jako minule, ale vylepšíme zápis Web Font Loaderu. Po načtení písma se změní třída stejně jako minule, ale navíc si nastavíme cookie. Ta bude stručná, protože se přenáší s každým požadavkem - uložíme si do ní jen číslo verze a kdybychom potřebovali později písmo aktualizovat, tak verzi zvýšíme o jedničku.

<!-- první načtení bez cookie (a bez cache) -->
<html>
  <head>
  <script>
    WebFontConfig = {
      google: { families: [ 'Open+Sans::latin' ] },
      active:function(){document.cookie ='wfont=1; expires='+(new Date(new Date().getTime() + 86400000)).toGMTString()+'; path=/'}
    };
  </script>
  <script src="//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js" async defer></script>
  <link rel="prefetch" href="https://fonts.googleapis.com/css?family=Open+Sans">
  </head>
  <body>
  text
  </body>
</html>

Všimněte si navíc doplněného prefetch - to nám zajistí, že se CSS s definicí fontů začne načítat (pokud má prohlížeč kapacitu) ještě před tím, než se vůbec načte Web Font Loader. Proč to je užitečné ještě vysvětlím.

Při prvním načtení je výsledek stejný jako v minulém příkladu - návštěvník vidí nejdřív fallback font a následně se písmo změní na hezké. Při dalším načtení už ale máme k dispozici cookie a tak víme, že návštěvník už nejspíš má font v cache. Při druhém načtení mu proto vrátíme úplně jiné html:

<!-- další načtení s cookie (a s cache) -->
<html class="wf-active">
  <head>
  <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
  </head>
  <body>
  text
  </body>
</html>

Definici písma nyní načítáme přímo a tím blokujeme vykreslování. Tentokrát to ale nevadí, protože písmo už je v cache, takže ve skutečnosti nic neblokujeme. Navíc jsme rovnou nastavili třídu wf-active, takže se rovnou načítá webfont a fallback font se úplně přeskočí.

Nevýhodou je, že může vypršet cookie, ale přitom font bude pořád ještě v cache. V tom případě dojde ale jen k drobnému bliknutí, nastaví se cookie a všechno zase poběží jak má. Také se může stát, že bude nastavená cookie, ale font v cache nebude. V tom případě zablokujeme vykreslování stránky, dokud se písmo nenačte. Takových případů ale bude minimum a web se chovat jako všechny ostatní weby, takže žádná velká tragédie.

Vychytávka na závěr

Google Fonts mají dvě drobné nevýhody:

  • mají krátkou cache (vtipně to reportuje jako chybu Google Pagespeed)
  • načítají se z jiné domény
U nás jsme je proto schovali za naší CDN. Prodlouží se tím cache a navíc pak jen na HTTP/2 možné načítat fonty spolu s ostatními zdroji. Proto jsme do definice doplnili prefetch - díky němu se definice fontů stáhne v jednom spojení zároveň třeba s normálním CSS a tím jsme na mobilu ušetřili kolem 600ms.

Jak to celé v reálu funguje si můžete vyzkoušet tady.

Zní to jako dost práce...

S fonty je opravdu spousta práce. A navíc nepředpokládám, že by nám tohle řešení vydrželo dlouho, protože už se zase objevují nové, lepší možnosti. Základem pro tyto optimalizace ale je automatizace. Všechno co jsem výše popsal máme u nás řešeno automaticky při deploy změn šablon. Pokud si pak klient sám mění šablonu (protože u nás může být každá šablona zcela unikátní), tak ho s ničím takovým nezatěžujeme. Jediné co kodér v šabloně uvede je jednoduše třeba toto:

p {font-family:'Open Sans'};

Deploy skript automaticky projde CSS, doplní fallback fonty, načte správně externí písma, vyřeší cookie atd. V originálním CSS je zápis navíc úplně jednoduchý bez nějakých hacků, takže až se objeví nové možnosti optimalizace, tak jen upravíme deploy skript.

Není to paráda?

 

 

 



O blogu
Blog o provozování eshopů a technologickém zázemí.
Aktuálně řeším hlavně cloud, bezpečnost a optimalizaci rychlosti.

Rozjíždím službu pro propojení eshopů s dodavateli.