如何避免由 Web 字體引起的布局偏移
前言?
一些布局上的完全加載前后的變化很容易解決:為動態元素預先分配正確的空間,在圖像上使用寬度和高度屬性,并優先考慮 HTML 文檔中的可見元素。但是,導致布局偏移的還有一個難以解決的問題:無樣式文本 (FOUT) 的閃爍。
這篇文章我們將探索令人驚訝的復雜文本渲染世界,以及一些解決無樣式文本閃爍的技術。
為什么字體會導致布局變化??
意外的布局變化(頁面內容在沒有用戶交互的情況下移動)不利于用戶體驗。下載網絡字體時,當字體題發生變化時,會導致包含元素(例如<div>,段落或段落)的大小發生變化,從而導致布局發生變化。當 Web 字體的字體高度或段落長度與系統字體相比不同時,就會出現這種情況。布局頁面時,瀏覽器將使用后備字體的尺寸和屬性來確定包含元素的大小,即使你已聲明 Web 字體以阻止系統字體font-display: block!
「兩種不同的字體是可能會導致布局發生變化的,但不是一定,這主要取決于字體的字體高度。」
如何避免?
我們現階段的網頁為了滿足用戶的審美往往會使用一些特殊字體,但與此同時也會帶來一些體驗上的問題,最常見的就是頁面的加載速度以及文本閃爍等。所以我們有必要對字體進行一些優化操作來滿足我們“日益挑剔”的用戶。
font-display
最粗暴的解決方案是只需要一行CSS代碼就能夠解決。
為什么說只需要這一行代碼就能夠解決呢,因為如果 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
- 提取
我們還是以上面那段詩句為例,那里我們用的是漢儀旗黑.woff2字體文件。這里還是經過縮小文字庫之后的大概是32K
我們再在項目目錄下執行以下命令
這時會生成一個.font-spider目錄,并將提取后的字體文件放在該目錄下。現在的字體文件大概就只有10K,比之前的體積小了好幾倍。
使用系統字體
Web 字體很受歡迎,因為它們允許設計人員在瀏覽器中保持一致的外觀和感覺。如果不需要,系統字體將是呈現文本的最快方法。如果當前的Web字體接近系統字體,您可以使用Monica的Font Style Matcher來調整字體設置,直到獲得近乎完美的匹配。
使用系統字體意味著文本將盡可能早地呈現。我們現在還擁有使字體與操作系統匹配的方法,這可能比以前的備用選項(如 Arial 和 Helvetica)更具吸引力。為此,我們需要按特定順序列出所有操作系統的系統字體:
快速交付字體文件
很明顯,我們為了確保字體快速且正確地應用在我們網頁上,我們必須讓瀏覽器盡快下載我們的字體文件,在我們自己的CDN上托管字體將獲得最佳性能。
使用CDN托管
一般來說,我們應該提供我們服務器中的字體以避免連接到第三方服務器的成本,這對于高延遲連接尤其重要。使用第三方服務意味著您的字體將被延遲。最好的情況是您直接從另一個主機名(例如fonts.gstatic.com)請求字體文件,這會產生連接成本——DNS 查找、TCP 連接和 TLS 協商。最壞的情況是多跳,例如從fonts.googleapis.com加載引用fonts.gstatic.com上的文件的 CSS 文件,會導致兩次連接損失。所以我們一般會將一些字體文件等靜態資源托管在我們自己的CDN服務器上,以此來加快資源的下載速度。
緩存字體
字體可以緩存在兩個地方:「客戶端和CDN」。客戶端上的緩存對于會話中的導航很重要,并且應該以避免重新驗證請求的方式完成。重新驗證請求 (if-not-modified和if-modified-since) 將阻止瀏覽器使用字體文件,直到它驗證它在服務器上沒有更改。字體很少改變,所以我們應該如下實現一個緩存頭,并在字體改變時更新文件名來破壞緩存:
這告訴瀏覽器他們可以保留字體長達一年并且不需要重新驗證( Firefox 和 Safariimmutable 支持,Chrome 應該自動避免重新驗證請求)。避免將 ETag 添加到這些響應中,因為它們可能會強制重新驗證。
還要檢查您的 Content Delivery Network 配置是否可以將字體文件存儲在緩存中,較舊的配置可能不包含.woff2擴展名,從而導致原始命中并減慢響應速度。
使用預加載
一般來講瀏覽器不會隨便地去下載字體文件,它們會等到渲染樹構建完成后才能知道需要哪些字體。這意味著僅在瀏覽器下載并解析 HTML 和 CSS 時,即在呈現文本之前,才請求 Web 字體。「但需要注意的是,內聯 CSS 不需要網絡請求,這意味著我們的字體可以在頁面加載的早期獲取。」
渲染樹的構建過程會阻塞Web字體的請求。
但是如果我們確定頁面文本的渲染肯定會用到一些網絡字體時,我們可以使用preload讓瀏覽器提前下載字體文件。
當瀏覽器解析這行 HTML 時,它會立即發送一個對字體文件的高優先級請求。
「但是需要注意的是,預加載的請求會占用其它請求的帶寬,所以我們在使用過程需要考慮清楚是否值得這么做。」
將字體轉為Base64
還有一種常用的方法是將字體作為 Base64 字符串嵌入到 CSS 中,從而無需額外的字體請求并確保在呈現文本時字體可用。
但這個方法也不是絕對的好方法,它只適合一些小型字體文件,例如上面提到的使用font-spider提取后的字體文件,并且該字體文件足夠小,因為將字體文件轉化為Base64字符串往往會增加體積。
一般來講它有以下缺點:
- 字體文件是壓縮的二進制對象,編碼為 Base64 字符串會顯著增加大小。CSS 包的 gzip 或 brotli 壓縮并不能完全彌補這種膨脹。
- 字體將被發送到每個瀏覽器,即使有的瀏覽器不能使用
- 字體很少更改,但 CSS 經常更改,這將降低字體的緩存效率,因為每次 CSS 更改都會使整個包無效
- 膨脹 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-display: optional到網絡字體,讓瀏覽器以備用系統字體呈現來防止布局偏移,否則的話就只能優化我們的字體以嘗試在瀏覽器需要它們之前將它們獲取到瀏覽器:
- 使用woff2最小化文件大小
- 優化字體文件
- 加載更少的字體
- 預加載關鍵字體
- 在CDN上托管字體文件
- 使用 f-mods 減少字體交換的影響