Na navigaci | Klávesové zkratky

Jak správně načítat webové fonty

Stránka by měla být čitelná ihned. Je velmi protivné, když si například v metru nemůžete přečíst článek jen kvůli tomu, že se nestihl načíst webový font.

Prohlížeče se totiž chovají tak, že text zobrazí až poté, co se font stáhne, aby zabránili tzv. Flash of Unstyled Text (FOUT), tedy nepříjemnému probliknutí jiného fontu. Problém je, že některé browsery nemají žádný timeout, po jehož uplynutí by se použil systémový font namísto žádného (tj. webového, ale nestaženého).

O tomhle tématu jsem psal už dříve a doporučoval pro mobilní zařízení webové fonty vůbec nepoužívat. Stejně krom autora grafiky to nikdy nepozná :-)

Mobilům (nebo lépe řečeno zařízením do šířky 500px) můžeme ulevit tímto způsobem:

/* font stáhneme jen na větších zařízeních */
@import "https://fonts.googleapis.com/css?family=PT+Serif" screen and (min-width: 500px);

/* systémové písmo */
body {
	font: 18px/1.7 Georgia, serif;
}

@media (min-width: 500px) {
	body { /* webový font použijeme jen na větších zařízeních */
		font-family: 'PT Serif', Georgia, serif;
	}
}

Ale co ostatní prohlížeče? Natahování fontu skrze @import vypadá elegantně, ale blokuje zpracování CSS a to zase blokuje vykreslení stránky. Jde tedy o nejhorší možné řešení. Jak se zbavit blokování a zajistit timeout?

Emulace timeoutu

Timeout lze emulovat pomocí JavaScriptu. Jenže zjistit, že se font načetl, není nic triviálního, dělá se to pomocí triků, jako je třeba detekce změny šířky předpřipraveného text atd. Font Loading API zatím podporuje jen Chrome. Takže lepší bude použít hotové řešení, jako je například Web Font Loader.

Web Font Loader

Knihovnu Web Font Loader vyvíjí Google společně s Typekit. Nedávno o ní psal Aleš Roubíček, takže na něj navážu a pokusím se upravit řešení tak, aby se skript načítal asynchronně a neblokoval stránku.

Do hlavičky stránky (!) vložte tento kód, kterým asynchronně načtete Web Font Loader a také font:

<script>
	WebFontConfig = {
		google: { families: ['PT+Serif:400:latin,latin-ext'] }
	};
</script>
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js" async defer></script>

Web Font Loader umí detekovat stavy, kdy se font načítá a kdy už je načtený, a to pomocí událostí nebo nastavováním tříd elementu <html>. Jakmile je font načtený, nastaví třídu wf-active. Zároveň řeší timeout.

Upravíme styl tak, aby se webový font použil až ve chvíli, kdy bude načtený, a nahradil tak systémové písmo.

/* systémové písmo */
body {
	font: 18px/1.7 Georgia, serif;
}

/* písmo po načtení fontu */
html.wf-active body {
	font-family: 'PT Serif', Georgia, serif;
}

Tohle řešení má ale potíž. Bude docházet k FOUT, tedy k probliknutí systémového fontu. Při prvním načtení stránky, kdy webový font ještě není v cache prohlížeče, může probliknutí trvat sekundu či déle, při každém dalším zobrazení krátký okamžik. A to vypadá velmi ošklivě.

Proto během načítání písmo skryjeme, tak jak to dělají prohlížeče standardně, bez loaderu.

html.wf-loading * {
	color: transparent !important;
}

Dále třídu wf-loading nastavíme ihned, nelze čekat, až se Web Font Loader načte. Ale zároveň ji musíme odstranit, když načtení loaderu selže. Výsledný kód vypadá takto:

<script>
	WebFontConfig = {
		google: { families: ['PT+Serif:400:latin,latin-ext'] }
	};

	var el = document.documentElement;
	el.className += ' wf-loading';
	setTimeout(function() {
		el.className = el.className.replace(/(^|\s)wf-loading(\s|$)/g, ' ');
	}, 1000); // 1 second
</script>
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js" async defer></script>

Pokud je font v cache, stránka se ihned zobrazí se správným písmem bez probliknutí. Pokud v cache není, text nejprve nebude vidět a pokud se do vteřiny nepodaří font stáhnout, zobrazí se systémovým písmem a po stažení se přepne na webové písmo. Tedy při rychlém stažení k probliknutí vůbec nedojde a při pomalém ano, ale uživatel nebude koukat na prázdnou stránku.

Samozřejmě časovou prodlevu můžete dle libosti snížit.

Doplnění: s alternativním řešením přišel Petr Soukup.

Po načtení fontu si do prohlížeče uloží cookie wfont, která později bude značit, že font by měl být už v cache prohlížeče:

<html>
<script>
	WebFontConfig = {
		google: { families: ['PT+Serif:400:latin,latin-ext'] },
		active: function() { document.cookie ='wfont=1; expires='+(new Date(new Date().getTime() + 86400000)).toGMTString()+'; path=/' }
	};
</script>

A při dalším načtení stránky, pokud tato cookie existuje, generuje trošku jiný kód, kde není Web Font Loader, ale:

<html class="wf-active">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Serif">

Tedy webový font se použije rovnou a načítá se bez JavaScriptu, nedochází tedy k žádnému probliknutí.

A tip nakonec: aby prohlížeč mohl započít načítání fontu hodně brzy, vlastně ještě před normálním zpracováváním stránky, mu lze poradit pomocí prefetch:

<link rel="prefetch" href="https://fonts.googleapis.com/css?family=PT+Serif">

Implementace v Latte by mohla vypadat třeba takto:

{var $wfont = isset($_COOKIE[wfont])}
<html n:class="$wfont ? wf-active">
<script n:syntax=off n:if=!$wfont>
WebFontConfig = {
	google: { families: ['PT+Serif:400:latin,latin-ext'] },
	active: function() { document.cookie ='wfont=1; expires='+(new Date(new Date().getTime() + 86400000)).toGMTString()+'; path=/' }
};
</script>
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js" async defer n:if=!$wfont></script>
<link rel="{$wfont ? stylesheet : prefetch}" href="https://fonts.googleapis.com/css?family=PT+Serif">

Komentáře

  1. Aleš Roubíček #1

    avatar

    Lepší než zase texty schovávat je se je pokusit nastylovat (použít letter-spacing a line-height…) tak, aby se rozměry nenastylovaného textu blížily co nejvíce textu s vybraným fontem. Více viz skvělá kniha Responsive typography

    před 9 lety | reagoval [2] David Grudl
  2. David Grudl #2

    avatar

    #1 Aleši Roubíčku, Při asynchronním načítání problikne FOUT i když je vše v cache. Jde o setiny sekundy a lépe vypadá, když k tomu nedojde, tj. text je v ten okamžik skrytý.

    Kompromisem by mohlo být text skrýt maximálně na půl sekundy a pokud se font nenačte, použít alternativní (vhodně nastylovaný, i když je to cross OS ošidné) a teprve po načtení webový font.

    Příklad jsem upravil, aby wf-loading zrušil nejpozději po sekundě.

    před 9 lety | reagoval [4] Petr Soukup
  3. Petr Soukup #3

    avatar

    Pozor na to, že @import zablokuje celý zbytek CSS, dokud se nestáhne externí. Nelze to proto používat ve stejném souboru se styly pro zbytek webu. Z nabízených možností na Google Webfonts je @import ta nejhorší.

    U Google Webfonts je navíc problém, že nejsou nijak extra cachované (vtipně to hlásí jako chybu i Google PageSpeed), takže je výhodnější hostovat je lokálně.

    Při používání wf-active dojde k probliknutí jen při prvním načtení. Zvlášť na mobilu je daleko lepší bliknutí nechat, než text skrývat. Pomocí CSS lze docílit toho, že jsou písma relativně podobná a probliknutí není až tak výrazné.

    před 9 lety | reagoval [5] David Grudl
  4. Petr Soukup #4

    avatar

    #2 Davide Grudle, Při dalších načteních lze probliknutí zrušit. Děláme to tak, že při prvním načtení načítáme přes Web Font Loader a při dalším načtení už bez něj.
    Tak písmo při dalších načteních blokuje vykreslení, ale protože už je v cache, tak to nevadí.

    před 9 lety
  5. David Grudl #5

    avatar

    #3 Petře Soukupe, ad @import: jo, to je dobrá poznámka, doplním ji do článku.

    Můžeš vaše řešení popsat přesněji, nejlépe kódem?

    před 9 lety | reagoval [6] Petr Soukup
  6. Petr Soukup #6

    avatar

    #5 Davide Grudle, Asi o tom konečně sepíšu článek. Ve zkratce to ale funguje tak, že se po načtení nastaví cookie a pokud existuje, tak se webfontloader vkládá bez async.
    Není to spolehlivé na 100%, ale nejhorší co se může stát je, že to třeba problikne při chybějící cookie. V principu je to podobné, jako tohle: https://www.souki.cz/…nchronni-css

    V praxi je to vidět třeba tady – https://www.jungle-gym.cz/. Koukám ale, že tam je teď z nějakého důvodu vypnuté cachovací proxy na fonty.

    před 9 lety | reagoval [7] David Grudl
  7. David Grudl #7

    avatar

    #6 Petře Soukupe, jo, to zní jako hodně dobrý způsob!

    před 9 lety
  8. Jakub Vrána #8

    avatar

    Při vyhazování CSS třídy vyhazuješ jednu mezeru navíc. V moderních prohlížečích se dá použít classList, ve starších tam tu mezeru prostě dej. Ještě ti chybí za tím příkazem středník.

    před 9 lety | reagoval [9] David Grudl
  9. David Grudl #9

    avatar

    #8 Jakube Vráno, díky, opraveno

    před 9 lety
  10. Honza Prachař #10

    avatar

    Ad ukládání do cookie, řeší to tak taky smashingmag, ale problém prý je, že HTTP keš na fonty není dostatečně spolehlivá. Viz odstavec o fontech https://www.smashingmagazine.com/…-case-study/

    před 9 lety | reagoval [11] Petr Soukup
  11. Petr Soukup #11

    avatar

    #10 Honzo Prachaři, To je pravda, ale v tomhle případě je to celkem jedno. Pokud cookie nebo cache z nějakého důvodu selže, tak jediné co se stane je, že jedno načtení bude s bliknutím (nebo s blokovaným vykreslením).
    Je to ale vyjímečná situace, takže nic hrozného – navíc když drtivá většina webů to tak má při každém načtení.

    před 9 lety
  12. Petr Soukup #12

    avatar

    Trvalo to pouhé 4 měsíce a konečně jsem pořádně sepsal, jak to řešíme u nás: https://www.souki.cz/…tat-webfonty
    Načítání bez blokování, bez skrývání písma a nic nebliká.

    před 8 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.