一種不完美的網頁字體異步加載方法

問題

最近在作一個項目時,遇到了這樣一個問題:網頁大標題要用設計師指定的中文字體,該字體文件比較大,瀏覽器加載字體文件的過程當中是不會顯示使用該字體的文本的,因而出現了初次打開網頁時有一段時間「No title」的BUG。css

解決方案

針對該問題,筆者能想到如下幾種解決方案(歡迎補充):html

  1. 把文字作成png圖片,或改成路徑後作成SVG圖片,考慮作成base64內嵌到CSS文件。
  2. 使用字體工具,將字體包中沒用到的字符刪除來減少字體文件的體積(npm上也有這樣的優秀JS庫)。
  3. 方案1的升級版,在全部用到這個字體的文本中,把首屏出現的文本作成圖片,後面的文本依然使用字體包,並在打開首頁時就加載字體包。
  4. 方案2的升級版,在向服務器請求字體的時候,由服務端分析document中全部用到的字符,動態生成字體包後返回給瀏覽器端。
  5. 在loading字體的過程當中,先用一種最接近目標字體的安全字體來顯示,等字體文件加載完後進行替換。
  6. 打死設計師,想用啥用啥。

方案對比與選擇

  • 因爲後續的頁面內容也有很多文本會用到該字體,且考慮從此網站維護成本,因此一、2兩個解決方案不適合本項目。
  • 第3個方案最省事,作快速開發的話比較合適,但代碼複用性不高,程序也不夠健壯,例如低網速狀況下,有可能會出現字體包未徹底加載時用戶已經滑到下一頁,而這一頁中有文本是使用了目標字體包從而不顯示的狀況。
  • 第4個方案須要後端開發的配合,要考慮如何判斷全部用到的字符,而且在JS向document中寫入新的字符時,要請求增量字體包,會較大程度地增長CDN服務端負擔(主要是怕跟後端開發撕逼)。
  • 第5個方案是一種無可奈何的選擇,在用戶眼皮底下更換字體,是很是影響體驗的,好處是字體屬於異步加載,不會阻塞文本顯示。
  • 最後一個方案成本比較高,須要搭上開發人員的下半輩子,好處是能夠從根本上解決這個BUG,永訣後患。被逼急的程序員能夠嘗試下。

結合項目特色,最終選擇方案5。程序員

實現過程

整個過程邏輯很是簡單:首先標題所用的class在CSS中被定義了一個最接近目標字體的安全字體,而後等待字體文件加載,加載完成後將標題的class換成自定義字體的class。npm

首先設好兩個CSS屬性,一個是用最接近目標字體的安全字體,用於默認字體,第二個是自定義字體:後端

/* 定義字體 */
@font-face{
    font-family: Lanting_light;
    src: url("../res/font/goDieDesigner.woff");
}
/* 安全字體,用於默認 */
.lanting_l{
    font-family: Arial, Helvetica, sans-serif;
}
/* 自定義字體 */
.lanting_light{
    font-family: "Lanting_light",Arial, Helvetica, sans-serif;
}

元素設置成默認字體:瀏覽器

<p class="Lanting_l">
    我是一段可愛的文字,啾咪~
</p>

寫一個函數,用於替換元素的class,在字體文件加載完成後執行它:安全

function onLoadedFont() {
    ele = document.getElementsByClassName("Lanting_l");
    for (let i = 0; i < ele.length; i++){
        ele[i].classList.add("Lanting_light");
        ele[i].classList.remove("Lanting_l");
    }
}

接下來是整個問題的核心:如何判斷字體文件已被加載?
服務器

一個html元素,能夠用監聽load事件來判斷加載,但字體不是html元素,沒法監聽。
再來看一下需求,字體文件是從用戶打開頁面時就要開始加載了,因此只要讓字體一開始就加載,而後監聽window.load事件就行了:異步

window.addEventListener("load",onLoadedFont);

PS:這裏不太嚴謹,可能會有其餘大致積資源加載拖慢load事件,因此大文件最好用lazyload。MDN提供了一個屬性能夠判斷字體加載,可是目前兼容性還有一些問題,這裏就先不用了,感興趣的能夠看一下這裏。小弟才疏學淺,若有大神知道其餘監聽字體文件加載的方法,還請留言告知,謝謝~

函數

而後就是要作些什麼讓字體文件一開始就去加載了,要知道不一樣瀏覽器何時會去加載一個字體,能夠參考這篇文章
在HTML中建立一個文本標籤,讓它去用這個須要被加載的字體,而且讓這個文本不要出如今視窗中:

<h1 class="Lanting_light" style="position: fixed;left:calc(-1000% - 5000px)">字體</h1>

最後擦個屁股,load完成後把上述已經被榨乾剩餘價值的節點刪掉:

window.addEventListener("load",function(){
    let dev = document.getElementsByClassName("Lanting_light");
    for (let i = 0; i < dev.length; i++){
        dev[i].parentElement.removeChild(dev[i]);
    }
    //必須先刪除舊節點後再修改須要改的節點,不然文本節點會被刪掉
    onLoadedFont();
});

到這裏,整個功能就完成了。

總結

總結一下,這個方案可讓字體文件沒有加載完的時候,先用一個接近的安全字體讓文本先顯示出來,待字體加載完後再換字體,核心的點是監聽字體文件的加載,由於MDN上提供的document.font是一個實驗性功能,兼容性不是很好,因此這裏用了投機取巧的辦法去監聽window.load,可能會被其餘大文件阻塞,也會拖慢監聽window.load的其餘函數,因此在項目中仍是要取捨,沒有完美的方案,只有最合適的方案。

相關文章
相關標籤/搜索