如何避免由 Web 字體引起的布局偏移

前言

一些布局上的完全載入前後的變化很容易解決:為動態元素預先分配正確的空間,在影像上使用寬度和高度屬性,並優先考慮 HTML 文檔中的可見元素。但是,導致布局偏移的還有一個難以解決的問題:無樣式文本 (FOUT) 的閃爍。

這篇文章我們將探索令人驚訝的複雜文本渲染世界,以及一些解決無樣式文本閃爍的技術。

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~

為什麼字體會導致布局變化?

意外的布局變化(頁面內容在沒有用戶交互的情況下移動)不利於用戶體驗。下載網路字體時,當字體題發生變化時,會導致包含元素(例如<div>,段落或段落)的大小發生變化,從而導致布局發生變化。當 Web 字體的字體高度或段落長度與系統字體相比不同時,就會出現這種情況。布局頁面時,瀏覽器將使用後備字體的尺寸和屬性來確定包含元素的大小,即使你已聲明 Web 字體以阻止系統字體font-display: block

兩種不同的字體是可能會導致布局發生變化的,但不是一定,這主要取決於字體的字體高度。

font-1.gif

如何避免

我們現階段的網頁為了滿足用戶的審美往往會使用一些特殊字體,但與此同時也會帶來一些體驗上的問題,最常見的就是頁面的載入速度以及文本閃爍等。所以我們有必要對字體進行一些優化操作來滿足我們「日益挑剔」的用戶。

font-display

最粗暴的解決方案是只需要一行CSS程式碼就能夠解決。

font-display: optional;

為什麼說只需要這一行程式碼就能夠解決呢,因為如果 Web 字體在呈現文本時不可用(加上 100 毫秒),它會告訴瀏覽器使用備用系統字體。這意味著在未快取的頁面載入時,可能會使用備用字體,但所有後續頁面載入都應使用 Web 字體呈現,因為它將被下載並在快取中可用。

它一共有以下幾個屬性:

  • auto: 字體顯示策略由用戶代理
  • block: 為字體提供一個短暫的阻塞周期和無限的交換周期,在等待網路字體時隱藏文本最多三秒鐘,並在載入時始終交換網路字體
  • swap: 為字體提供一個非常小的阻塞周期和無限的交換周期,儘快顯示文本,並在載入時始終交換網路字體
  • fallback: 為字體提供一個非常小的阻塞周期和短暫的交換周期,隱藏文本最多 100 毫秒,然後僅在三秒內載入時交換網路字體
  • optional: 為字體提供一個非常小的阻塞周期,並且沒有交換周期,隱藏文本最多 100 毫秒,然後僅使用可用的網路字體,從不交換

上面這樣解釋如果還不太明白的話,可以看看下面這張圖:

font-2.png

Optional 是唯一保證不發生布局偏移的字體顯示值

不幸的是,系統字體不一定是最好的設計,並且它們在各個作業系統之間並不一致。大多數設計師一想到向用戶展示一個備用系統字體就會畏縮。接下來介紹各種優化以更快地將字體文件傳送到瀏覽器,允許使用任何字體顯示選項,但顯示系統字體的風險最小,或者用於optional: 以外的選項而不觸發布局轉換。

優化字體文件

優化網路字體有兩種關鍵方法:子集和格式。

子集字體

許多字體將具有來自多個字母的字形(字形是單個字元,例如a&)如果你僅以拉丁字母 (a – Z) 提供並且不使用連字(如é),那麼這些字形表示您的字體文件中浪費的位元組。

從此字體中刪除非拉丁字元會產生woff2一個大小為六分之一的文件。

字體格式

各主流設備基本都支援 woff2 字體格式,因此網站中沒有必要再引入多種不同格式的字體了。一般地,建議只引入 woff2 就好了,既可以保持程式碼的簡潔性,又可以減少上傳到你伺服器的文件。

載入更少的字體

雖然我們會將字體轉換成woff2格式,但文件大小依然有好幾百K,有時甚至是幾M,字體文件的大小也會影響頁面整體的渲染速度。有些時候我們只需要一些極少數的文字用於特殊字體,那我們就沒必要將一整個字體文件引入了。

提取字體

當我們遇到上面這種情況時,千萬不要將一整個字體包引入進去,這將極大地浪費網路頻寬,從而影響頁面的載入。這裡推薦使用font-spider 字蛛來提取文字。

  • 安裝font-spider
npm install font-spider -g
  • 提取

我們還是以上面那段詩句為例,那裡我們用的是漢儀旗黑.woff2字體文件。這裡還是經過縮小文字型檔之後的大概是32K
font-3.png

我們再在項目目錄下執行以下命令

font-spider index.html

這時會生成一個.font-spider目錄,並將提取後的字體文件放在該目錄下。現在的字體文件大概就只有10K,比之前的體積小了好幾倍。

font-4.png

使用系統字體

Web 字體很受歡迎,因為它們允許設計人員在瀏覽器中保持一致的外觀和感覺。如果不需要,系統字體將是呈現文本的最快方法。如果當前的Web字體接近系統字體,您可以使用Monica的Font Style Matcher來調整字體設置,直到獲得近乎完美的匹配。

使用系統字體意味著文本將儘可能早地呈現。我們現在還擁有使字體與作業系統匹配的方法,這可能比以前的備用選項(如 Arial 和 Helvetica)更具吸引力。為此,我們需要按特定順序列出所有作業系統的系統字體:

body {
  font-family: -apple-system, BlinkMacSystemFont,
    "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
    "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
}

快速交付字體文件

很明顯,我們為了確保字體快速且正確地應用在我們網頁上,我們必須讓瀏覽器儘快下載我們的字體文件,在我們自己的CDN上託管字體將獲得最佳性能。

使用CDN託管

一般來說,我們應該提供我們伺服器中的字體以避免連接到第三方伺服器的成本,這對於高延遲連接尤其重要。使用第三方服務意味著您的字體將被延遲。最好的情況是您直接從另一個主機名(例如fonts.gstatic.com)請求字體文件,這會產生連接成本——DNS 查找、TCP 連接和 TLS 協商。最壞的情況是多跳,例如從fonts.googleapis.com載入引用fonts.gstatic.com上的文件的 CSS 文件,會導致兩次連接損失。所以我們一般會將一些字體文件等靜態資源託管在我們自己的CDN伺服器上,以此來加快資源的下載速度。

快取字體

字體可以快取在兩個地方:客戶端和CDN。客戶端上的快取對於會話中的導航很重要,並且應該以避免重新驗證請求的方式完成。重新驗證請求 (if-not-modifiedif-modified-since) 將阻止瀏覽器使用字體文件,直到它驗證它在伺服器上沒有更改。字體很少改變,所以我們應該如下實現一個快取頭,並在字體改變時更新文件名來破壞快取:

cache-control: max-age=31536000,immutable

這告訴瀏覽器他們可以保留字體長達一年並且不需要重新驗證( Firefox 和 Safariimmutable 支援,Chrome 應該自動避免重新驗證請求)。避免將 ETag 添加到這些響應中,因為它們可能會強制重新驗證。

還要檢查您的 Content Delivery Network 配置是否可以將字體文件存儲在快取中,較舊的配置可能不包含.woff2擴展名,從而導致原始命中並減慢響應速度。

使用預載入

一般來講瀏覽器不會隨便地去下載字體文件,它們會等到渲染樹構建完成後才能知道需要哪些字體。這意味著僅在瀏覽器下載並解析 HTML 和 CSS 時,即在呈現文本之前,才請求 Web 字體。但需要注意的是,內聯 CSS 不需要網路請求,這意味著我們的字體可以在頁面載入的早期獲取。

font-5.png

渲染樹的構建過程會阻塞Web字體的請求。

但是如果我們確定頁面文本的渲染肯定會用到一些網路字體時,我們可以使用preload讓瀏覽器提前下載字體文件。

<link rel="preload" href="./public/fonts/漢儀旗黑.woff2" crossorigin="anonymous" type="font/woff2">

當瀏覽器解析這行 HTML 時,它會立即發送一個對字體文件的高優先順序請求。

但是需要注意的是,預載入的請求會佔用其它請求的頻寬,所以我們在使用過程需要考慮清楚是否值得這麼做。

將字體轉為Base64

還有一種常用的方法是將字體作為 Base64 字元串嵌入到 CSS 中,從而無需額外的字體請求並確保在呈現文本時字體可用。

但這個方法也不是絕對的好方法,它只適合一些小型字體文件,例如上面提到的使用font-spider提取後的字體文件,並且該字體文件足夠小,因為將字體文件轉化為Base64字元串往往會增加體積。

一般來講它有以下缺點:

  1. 字體文件是壓縮的二進位對象,編碼為 Base64 字元串會顯著增加大小。CSS 包的 gzip 或 brotli 壓縮並不能完全彌補這種膨脹。
  2. 字體將被發送到每個瀏覽器,即使有的瀏覽器不能使用
  3. 字體很少更改,但 CSS 經常更改,這將降低字體的快取效率,因為每次 CSS 更改都會使整個包無效
  4. 膨脹 CSS 大小几乎肯定會延遲頁面渲染

使用f-mods減少布局偏移

F-mods 是對字體描述符規範的提議更新,其中包括四個新的描述符:

  • ascent-override (%) : 覆蓋分配給上升器的大小
  • descent-override (%) : 覆蓋分配給下降者的行高
  • line-gap-override (%) : 覆蓋行間距
  • advance-override (#) : 為每個字元設置一個額外的提前量,以幫助匹配行寬並防止單詞溢出

前三個都影響線的高度:線框高度 = 上升 + 下降 + 線間隙。基準線位置 = 線框頂部 + 線間隙 / 2 + 上升。

這四個描述符的組合允許我們通過告訴瀏覽器在下載 Web 字體之前字元將佔用多少空間來覆蓋備用字體的布局以匹配 Web 字體。

f-mods 只真正修改垂直間距和定位。這意味著仍然需要處理字元間距和字母間距,否則可能會在不同的點出現斷行的單詞,從而導致元素高度發生變化,從而導致布局發生變化。但是@font-face 聲明中沒有letter-spacingandword-spacing屬性,因此我們必須在主體或元素上聲明。

@font-face {
  font-family: custom-font;
  src: url("./public/fonts/漢儀旗黑.woff2");
}
@font-face {
  font-family: fallback-font;
  src: local(Arial);
  ascent-override: 100%;
  descent-override: 20%;
  line-gap-override: normal;
  advance-override: 10;
}
/* 這些具體數值因字體而已,需要按照自己的字體進行計算調整*/
body {
  font-family: custom-font, fallback-font;
}
.content {
  letter-spacing: -1.1px;
  word-spacing: -0.2px;
}

總結

總之,如果瀏覽器沒有及時獲取網路字體,並且可以應用font-display: optional到網路字體,讓瀏覽器以備用系統字體呈現來防止布局偏移,否則的話就只能優化我們的字體以嘗試在瀏覽器需要它們之前將它們獲取到瀏覽器:

  • 使用woff2最小化文件大小
  • 優化字體文件
  • 載入更少的字體
  • 預載入關鍵字體
  • 在CDN上託管字體文件
  • 使用 f-mods 減少字體交換的影響

喜歡的同學歡迎點個贊呀~ 原文首發地址點這裡,歡迎大家關注公眾號 「前端南玖」,如果你想進前端交流群一起學習,請點這裡

我是南玖,我們下期見!!!

Tags: