Kodėl šriftų įkėlimas vis dar skaudžiai kandžiojasi
Turbūt kiekvienas esame matę tą nemalonų efektą, kai atsidarai puslapį ir tekstas pirmiausia pasirodo vienu šriftu, o po sekundės ar dviejų staiga pakeičia išvaizdą į visai kitą. Arba dar blogiau – kai tuščias ekranas žiūri į tave kelias sekundes, kol pagaliau atsiranda turinys. Tai ne dizainerių kaprizas ar vartotojų išrankumas – tai realios problemos, turinčios oficialius pavadinimus: FOUT (Flash of Unstyled Text) ir FOIT (Flash of Invisible Text).
FOUT pasireiškia kai naršyklė pirmiausia parodo tekstą sisteminiu šriftu, o vėliau, kai pagaliau įsikrauna jūsų gražusis custom šriftas, viskas „šokteli” ir persiformatuoja. FOIT dar klastingesnis – naršyklė tiesiog slepia visą tekstą, kol šriftas nebus pilnai įkeltas. Chrome ir kitos Chromium bazės naršyklės laukia iki 3 sekundžių, Firefox – begalybę (arba kol vartotojas neužsidaro langą iš nekantrybės).
Šios problemos nėra kosmetinės. Jos tiesiogiai veikia Core Web Vitals metrikas, ypač Cumulative Layout Shift (CLS) ir Largest Contentful Paint (LCP). O tai jau reikštų, kad Google gali jums sumažinti pozicijas paieškoje. Be to, vartotojų patirtis tampa šiurkšti – niekas nenori skaityti puslapio, kuris šokinėja kaip ant karštos keptuvės.
Kaip naršyklės elgiasi su šriftais pagal nutylėjimą
Prieš pradedant kovoti su problema, verta suprasti, kas iš tikrųjų vyksta po gaubtu. Kai naršyklė susiduria su `@font-face` deklaracija, ji nepradeda iš karto krauti šrifto failo. Šriftai kraunami tik tada, kai naršyklė nustato, kad jie tikrai reikalingi – tai yra, kai DOM medyje atsiranda elementas, kuriam taikomas tas šriftas.
Štai tipinė situacija:
„`html
„`
Naršyklė matys šią deklaraciją, bet šriftas nebus kraunamas, kol nebus apdorotas DOM ir CSS, ir naršyklė nesupranta, kad `body` elementui reikia šio šrifto. Tai gali užtrukti šimtus milisekundžių, o pats šrifto failas dar turi būti parsisiųstas.
Skirtingos naršyklės skirtingai sprendžia, ką daryti laukimo metu. Safari iš karto rodo tekstą sisteminiu šriftu (sukeldamas FOUT). Chrome ir Firefox slepia tekstą trumpam laikui (sukeldami FOIT), o jei šriftas neįsikrauna per nustatytą timeout’ą, parodo sisteminį šriftą ir vėliau pakeičia į custom šriftą, kai jis pagaliau atsiranda (sukeldami ir FOIT, ir FOUT).
font-display savybė – pirmasis gelbėjimosi ratas
CSS turi gana paprastą, bet galingą įrankį valdyti šriftų įkėlimo elgseną – `font-display` savybę. Ji leidžia nurodyti naršyklei, kaip elgtis tuo laikotarpiu, kai šriftas dar kraunasi.
Galimos reikšmės:
**auto** – naršyklė pati sprendžia (paprastai tai FOIT su 3 sekundžių timeout’u)
**block** – tekstas slepiamas iki 3 sekundžių, po to rodomas sisteminis šriftas. Kai custom šriftas įsikrauna, vyksta swap’as. Tai gali sukelti ryškų FOUT efektą.
**swap** – tekstas rodomas iš karto sisteminiu šriftu, o kai įsikrauna custom šriftas, vyksta pakeitimas. Tai garantuoja, kad tekstas bus matomas iš karto, bet FOUT neišvengiamas.
**fallback** – trumpas blokavimo periodas (~100ms), po to rodomas sisteminis šriftas. Custom šriftas gali būti panaudotas tik jei įsikrauna per ~3 sekundes, kitaip sisteminis šriftas lieka visam puslapio gyvavimui.
**optional** – labai trumpas blokavimo periodas (~100ms), po to naršyklė pati sprendžia, ar naudoti custom šriftą pagal tinklo spartą. Lėtame tinkle custom šriftas gali būti visai ignoruojamas.
Praktiškai dažniausiai naudojamos dvi strategijos:
„`css
@font-face {
font-family: ‘Fancy Font’;
src: url(‘fancy-font.woff2’) format(‘woff2’);
font-display: swap; /* Prioritetas – turinys */
}
„`
arba
„`css
@font-face {
font-family: ‘Brand Font’;
src: url(‘brand-font.woff2’) format(‘woff2’);
font-display: optional; /* Prioritetas – stabilumas */
}
„`
`swap` tinka, kai šriftas yra svarbus jūsų brand’ui, bet norite užtikrinti, kad tekstas bus matomas iš karto. `optional` puikus variantas, kai norite maksimaliai sklandžios patirties ir nesijaudinate, jei kai kurie vartotojai su lėtu tinklu matys sisteminį šriftą.
Preload – duodam naršyklei užuominą
Viena didžiausių problemų su šriftų įkėlimu yra ta, kad naršyklė apie juos sužino per vėlai. Ji turi parsisiųsti HTML, parsinti jį, parsisiųsti CSS, parsinti ir jį, sukonstruoti CSSOM, ir tik tada suprasti, kad reikia šrifto. Tai gali užtrukti sekundę ar daugiau.
`preload` direktyva leidžia pasakyti naršyklei: „Ei, šitas resursas tau tikrai prireiks, pradėk jį krauti dabar, nesikaustyk”.
„`html
„`
Keletas svarbių niuansų:
1. **crossorigin atributas privalomas**, net jei šriftas yra iš to paties domeno. Šriftai visada kraunami su CORS režimu.
2. **Nenaudokite preload visiems šriftams**. Preload duoda aukštą prioritetą resursui, o jei preload’insite per daug dalykų, prarasite prioritetizavimo naudą. Preload’inkite tik tuos šriftus, kurie tikrai reikalingi above-the-fold turiniui.
3. **type atributas padeda naršyklei**. Jei naršyklė nepalaiko woff2, ji nebeš preload’ins šio failo.
Praktiškai, dažniausiai preload’inti reikėtų tik vieną ar du pagrindinius šriftus:
„`html „`
Subsetting ir optimizavimas – mažesnis failas, greičiau įsikrauna
Vienas efektyviausių būdų pagreitinti šriftų įkėlimą – sumažinti jų dydį. Pilnas šrifto failas su visais unicode simboliais gali sverti keletą megabaitų. Bet ar tikrai jums reikia kinų hieroglifų, jei kuriate lietuvišką svetainę?
Subsetting – tai procesas, kai iš šrifto ištraukiami tik tie simboliai, kurių tikrai reikia. Yra keletas įrankių, kurie tai daro:
**glyphhanger** – Node.js įrankis, kuris gali išanalizuoti jūsų HTML ir sukurti subset’ą tik su naudojamais simboliais:
„`bash
glyphhanger –whitelist=”AaBbCcĄąĘę…” –formats=woff2 –subset=fancy-font.ttf
„`
**Font Squirrel** – online įrankis su grafiniu interface’u, leidžiantis pasirinkti simbolių rinkinius.
**fonttools** – Python biblioteka profesionalams, norintiems pilnos kontrolės.
Praktiškai, lietuviškai svetainei dažniausiai užtenka:
– Lotynų abėcėlės (A-Z, a-z)
– Lietuviškų raidžių (Ąąčęėįšųūž)
– Skaitmenų (0-9)
– Pagrindinių skyrybos ženklų
– Galbūt kai kurių specialių simbolių (@, €, ©)
Toks subset’as gali sumažinti šrifto dydį nuo 200KB iki 20-30KB. Tai dramatiškai pagreitina įkėlimą, ypač mobiliuose tinkluose.
Taip pat verta:
– **Naudoti WOFF2 formatą** – jis geriausiai suspaustas ir palaikomas visų modernių naršyklių
– **Optimizuoti šrifto hinting’ą** – dažnai galima išmesti dalį hinting duomenų be didelės žalos kokybei
– **Apsvarstyti variable fonts** – jei naudojate kelis to paties šrifto variantus (regular, bold, italic), variable font gali būti mažesnis nei visi atskirai
Font Loading API – pilna kontrolė JavaScript’u
Kai `font-display` ir `preload` nebeužtenka, galima imtis JavaScript. Font Loading API suteikia pilną kontrolę, kada ir kaip šriftai kraunami.
Pagrindinis API objektas – `document.fonts`, kuris turi `load()` metodą:
„`javascript
// Įkeliame šriftą programiškai
document.fonts.load(‘1em Fancy Font’).then(() => {
// Šriftas įkeltas, galime pridėti klasę
document.documentElement.classList.add(‘fonts-loaded’);
});
„`
Galima stebėti visų šriftų įkėlimo būseną:
„`javascript
document.fonts.ready.then(() => {
console.log(‘Visi šriftai įkelti!’);
});
„`
Praktiškas pattern’as – įkelti šriftus, bet su timeout’u, kad vartotojas nebelauktų amžinai:
„`javascript
const fontTimeout = new Promise((resolve) => {
setTimeout(resolve, 2000); // 2 sekundės max
});
const fontLoad = document.fonts.load(‘1em Fancy Font’);
Promise.race([fontLoad, fontTimeout]).then(() => {
document.documentElement.classList.add(‘fonts-loaded’);
});
„`
CSS pusėje galima naudoti klasę, kad valdyti šriftų taikymą:
„`css
body {
font-family: Arial, sans-serif;
}
.fonts-loaded body {
font-family: ‘Fancy Font’, Arial, sans-serif;
}
„`
Šis metodas suteikia maksimalią kontrolę, bet reikalauja JavaScript. Jei JS nepasikrauna ar yra išjungtas, vartotojas matys tik fallback šriftą. Tai gali būti arba feature, arba bug, priklausomai nuo jūsų prioritetų.
Strategija Google Fonts ir kitoms trečiųjų šalių paslaugoms
Google Fonts yra patogus, bet sukelia papildomų iššūkių. Standartinis embed’as atrodo taip:
„`html „`
Problema – tai papildomas DNS lookup, TCP connection, SSL handshake į Google serverius. Tai gali pridėti 200-500ms latency.
Geresnė strategija:
**1. Preconnect į Google Fonts domeną:**
„`html „`
Tai leidžia naršyklei iš anksto užmegzti ryšį su Google serveriais.
**2. Naudoti font-display parametrą:**
Google Fonts palaiko `display` parametrą URL:
„`html „`
**3. Self-host šriftus:**
Dar geriau – parsisiųsti šriftus ir host’inti juos savo serveryje. Įrankiai kaip `google-webfonts-helper` padeda lengvai gauti šriftų failus ir sugeneruoti reikiamą CSS:
„`css
@font-face {
font-family: ‘Roboto’;
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(‘/fonts/roboto-v30-latin-regular.woff2’) format(‘woff2’);
}
„`
Self-hosting privalumai:
– Nėra papildomo DNS lookup
– Pilna kontrolė dėl caching
– Nėra privacy concerns (Google Fonts gali track’inti vartotojus)
– Galima optimizuoti subset’us savo poreikiams
Trūkumai:
– Reikia pačiam valdyti šriftų versijas
– Prarandate Google CDN privalumus (nors šiuolaikiniai CDN jau nebedaro tokio didelio skirtumo)
Kai viskas jau optimizuota, bet vis tiek norisi daugiau
Jei jau įdiegėte visas aukščiau minėtas strategijas, bet vis tiek ieškote būdų, kaip padaryti dar geriau, štai keletas pažangesnių technikų:
**Critical FOFT (Flash of Faux Text)** – strategija, kai pirmiausia įkeliamas tik regular šrifto variantas, o bold ir italic emuliumai su `font-synthesis`. Vėliau, kai įsikrauna pilni variantai, vyksta swap’as. Tai sumažina pradinį payload, bet reikalauja kruopštaus įgyvendinimo.
**Session Storage caching** – kai šriftas įkeliamas pirmą kartą, išsaugoti flag’ą session storage. Kituose puslapiuose jau žinoti, kad šriftas yra cache’e, ir galima drąsiau naudoti jį be fallback strategijų:
„`javascript
if (sessionStorage.getItem(‘fontsLoaded’)) {
document.documentElement.classList.add(‘fonts-cached’);
} else {
document.fonts.ready.then(() => {
sessionStorage.setItem(‘fontsLoaded’, ‘true’);
document.documentElement.classList.add(‘fonts-loaded’);
});
}
„`
**Service Worker caching** – dar pažangesnis variantas, kai service worker’is aktyviai valdo šriftų cache’inimą ir gali juos atnaujinti background’e:
„`javascript
// service-worker.js
self.addEventListener(‘fetch’, (event) => {
if (event.request.url.includes(‘/fonts/’)) {
event.respondWith(
caches.open(‘fonts-v1’).then((cache) => {
return cache.match(event.request).then((response) => {
return response || fetch(event.request).then((response) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
}
});
„`
**Variable fonts su ašių optimizavimu** – jei naudojate variable font, galite apriboti ašių range’us tik tam, ko tikrai reikia:
„`css
@font-face {
font-family: ‘Variable Font’;
src: url(‘variable-font.woff2’) format(‘woff2-variations’);
font-weight: 400 700; /* Tik šis range’as */
font-display: swap;
}
„`
**Lokalių šriftų panaudojimas** – kai kurie šriftai (ypač sisteminiai) jau gali būti vartotojo sistemoje. Galima pabandyti juos naudoti prieš kraunant web šriftą:
„`css
@font-face {
font-family: ‘Optimized Font’;
src: local(‘Arial’),
local(‘Helvetica’),
url(‘/fonts/custom-font.woff2’) format(‘woff2’);
font-display: swap;
}
„`
Bet būkite atsargūs – lokalūs šriftai gali skirtis versijomis ir turėti netikėtų metrikų skirtumų.
Kaip viską sujungti į veikiančią sistemą
Teorija teorija, bet praktikoje reikia pasirinkti konkrečią strategiją ir ją įgyvendinti. Štai keletas rekomendacijų skirtingiems scenarijams:
**Jei kuriate content-heavy svetainę** (naujienos, blogas, dokumentacija):
– Naudokite `font-display: swap`
– Preload’inkite tik pagrindinį šriftą
– Apsvarstykit self-hosting vietoj Google Fonts
– Optimizuokite subset’us savo kalbai
– Prioritetas – turinys turi būti skaitomas iš karto
**Jei kuriate brand-focused svetainę** (landing’as, portfolio, e-commerce):
– Naudokite `font-display: optional` arba custom JS loading
– Preload’inkite kritinius šriftus
– Investuokite į subset’ų optimizavimą
– Apsvarstykite FOFT strategiją
– Prioritetas – vizualinis konsistentumas
**Jei kuriate web aplikaciją**:
– Naudokite `font-display: optional`
– Įdiekite service worker caching
– Naudokite session storage flag’us
– Apsvarstykit variable fonts
– Prioritetas – greitis ir stabilumas
Nepriklausomai nuo pasirinktos strategijos, būtina:
1. **Testuoti lėtame tinkle** – Chrome DevTools turi Network throttling. Išbandykite „Slow 3G” režimą.
2. **Matuoti realias metrikas** – naudokite Lighthouse, WebPageTest, arba RUM (Real User Monitoring) įrankius.
3. **Stebėti Core Web Vitals** – ypač CLS ir LCP metrikas Google Search Console.
4. **Turėti fallback planą** – kas nutiks, jei šriftas neįsikraus? Ar svetainė vis tiek bus naudojama?
Galiausiai, nepamirškite, kad šriftai – tai tik viena dalis performance puzzle. Kartais geriausia optimizacija – naudoti mažiau šriftų. Jei turite 5 skirtingus šriftus su 3 variantais kiekvieno, galbūt verta pergalvoti dizainą.
Šriftų įkėlimo optimizavimas nėra vienkartinis darbas – tai nuolatinis procesas. Naršyklės tobulėja, atsiranda nauji standartai (kaip font-display), keičiasi best practices. Svarbu sekti tendencijas, bet dar svarbiau suprasti pagrindines problemas ir jų priežastis. Tada galėsite pritaikyti bet kokią naują techniką savo kontekste ir priimti informuotus sprendimus, o ne aklai sekti „geriausiomis praktikomis”, kurios gali netikti jūsų specifinei situacijai.

