如何避免由 Web 字型引起的佈局偏移

2022-11-14 12:10:47

前言

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

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

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

為什麼字型會導致佈局變化?

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

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

如何避免

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

font-display

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

font-display: optional;

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

它一共有以下幾個屬性:

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

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

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

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

優化字型檔案

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

子集字型

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

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

字型格式

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

載入更少的字型

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

提取字型

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

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

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

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

font-spider index.html

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

使用系統字型

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 不需要網路請求,這意味著我們的字型可以在頁面載入的早期獲取。

渲染樹的構建過程會阻塞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 減少字型交換的影響

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

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