使用 font-display: Swap 載入字型的網站,在載入網路字型並替換為備用字型時,通常會出現版面配置位移 (CLS)。
您可以調整備用字型的尺寸,以符合主要字型的尺寸來避免 CLS。@font-face
規則中的 size-adjust
、ascent-override
、descent-override
和 line-gap-override
等屬性可協助覆寫備用字型的指標,讓開發人員進一步控管字型的顯示方式。如要進一步瞭解字型備用屬性和覆寫屬性,請參閱這篇文章。您也可以在這個示範中,查看這項技巧的實際實作方式。
本文將探討如何在 Next.js 和 Nuxt.js 架構中實作字型大小調整,以產生備用字型 CSS 並減少 CLS。同時示範如何使用跨剪輯工具 (例如 Fontaine 和 Capsize) 產生備用字型。
背景
font-display: Swap 通常用於防止 FOIT (隱藏文字的 Flash),加速顯示螢幕上的內容。swap
的值會告知瀏覽器使用系統字型的文字應立即以系統字型顯示,並且僅在自訂字型準備就緒時取代系統字型。
swap
最大的問題是不透明效果,表示這兩種字型的字元大小差異會導致畫面內容在移位。導致 CLS 分數偏低,尤其是含大量文字的網站。
下圖為問題示例。第一張圖片使用 font-display: swap
,未嘗試調整備用字型的大小。第二項說明使用 CSS @font-face
規則調整尺寸能如何改善載入體驗。
不調整字型大小
body {
font-family: Inter, serif;
}
![文字突然變更字型與大小,造成乾擾。](https://developer.chrome.google.cn/static/blog/framework-tools-font-fallback/image/text-suddenly-changes-f-ad5f294238b66.gif?authuser=0000&hl=zh-tw)
調整字型大小後
body {
font-family: Inter, fallback-inter, serif;
}
@font-face {
font-family: "fallback-inter";
ascent-override: 90.20%;
descent-override: 22.48%;
line-gap-override: 0.00%;
size-adjust: 107.40%;
src: local("Arial");
}
![流暢轉換為不同字型的文字。](https://developer.chrome.google.cn/static/blog/framework-tools-font-fallback/image/text-transitions-smoothl-200e9965ef42b.gif?authuser=0000&hl=zh-tw)
如要防止字型載入版面配置位移,調整備用字型的大小是相當有效的策略,但從頭開始實作邏輯並不容易,如這篇有關字型備用的文章所述。幸好,現在有幾種工具選項,可協助您輕鬆開發應用程式。
如何使用 Next.js 最佳化字型備用設定
Next.js 內建啟用備用字型最佳化功能的方法。當您使用 @next/font 元件載入字型時,這項功能預設為啟用。
Next.js 13 版導入 @next/font 元件。這個元件會提供一個 API,讓您將 Google Fonts 或自訂字型匯入至網頁,並內建自動自行代管字型檔案。
使用這個應用程式時,系統會自動計算備用字型指標,並插入 CSS 檔案。
舉例來說,如果您使用的是 Roboto 字型,通常應在 CSS 中定義該字型,如下所示:
@font-face {
font-family: 'Roboto';
font-display: swap;
src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
font-weight: 700;
}
body {
font-family: Roboto;
}
如何遷移到下一個/字型:
匯入「Roboto」檔案,將 Roboto 字型宣告移到 JavaScript 中「next/font」函式。函式的傳回值會是類別名稱,可用於元件範本中。 請記得將
display: swap
新增至設定物件,即可啟用這項功能。import { Roboto } from '@next/font/google'; const roboto = Roboto({ weight: '400', subsets: ['latin'], display: 'swap' // Using display swap automatically enables the feature })
在元件中,使用產生的類別名稱:
javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }
adjustFontFallback 設定選項:
@next/font/google
:這個布林值可設定是否要使用自動備用字型,以減少累計版面配置位移。預設值為 true。Next.js 會根據字型類型 (Serif 與 Sans Serif 分別會將備用字型設為 Arial
或 Times New Roman
)。
@next/font/local
:這個字串或布林值 False ,用於設定是否應使用自動備用字型來減少「累計版面配置位移」。可能的值為 Arial
、Times New Roman
或 false
。預設為 Arial
。如要使用 Serif 字型,建議將這個值設為 Times New Roman
。
Google Fonts 的其他選項
如果無法使用 next/font
元件,您可以透過 optimizeFonts
旗標搭配 Google Fonts 使用這項功能。Next.js 預設已啟用最佳化 Fonts 功能。這項功能會將 Google Font CSS 內嵌在 HTML 回應中。此外,您可以在 Next.config.js 中設定 experimental.adjustFontFallbacksWithSizeAdjust
旗標來啟用字型備用調整功能,如以下程式碼片段所示:
// In next.config.js
module.exports = {
experimental: {
adjustFontFallbacksWithSizeAdjust: true,
},
}
注意:我們目前尚未規劃支援新導入的 app
目錄,支援這項功能。長期來看,建議使用 next/font
。
如何使用 Nuxt 調整字型備用
@nuxtjs/fontaine 是 Nuxt.js 架構的模組,可自動計算備用字型指標值,並產生備用 @font-face
CSS。
將 @nuxtjs/fontaine
新增至模組設定以啟用模組:
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
})
如果您使用 Google Fonts 或沒有字型的 @font-face
宣告,您可以宣告該字型為額外選項。
在多數情況下,模組可讀取 CSS 中的 @font-face
規則,並自動推論字型系列、備用字型系列和顯示類型等詳細資料。
如果在模組無法找到的位置定義字型,您可以傳送指標資訊,如以下程式碼片段所示。
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
fontMetrics: {
fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})
這個模組會自動掃描 CSS 來讀取 @font-face 宣告,並產生備用 @font-face 規則。
@font-face {
font-family: 'Roboto';
font-display: swap;
src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
font-weight: 700;
}
/* This will be generated. */
@font-face {
font-family: 'Roboto override';
src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
local('Arial'), local('Noto Sans');
ascent-override: 92.7734375%;
descent-override: 24.4140625%;
line-gap-override: 0%;
}
您現在可以使用 Roboto override
做為 CSS 中的備用字型,如以下範例所示
:root {
font-family: 'Roboto';
/* This becomes */
font-family: 'Roboto', 'Roboto override';
}
自行產生 CSS
獨立程式庫還可以協助您產生用於調整備用字型大小的 CSS。
使用 Fontaine 程式庫
如果您使用的不是 Nuxt 或 Next.js,可以使用 Fontaine。Fontaine 是 @nuxtjs/fontaine 的基礎程式庫。您可以在專案中使用這個程式庫,透過 Vite 或 Webpack 外掛程式自動插入備用字型 CSS。
假設您在 CSS 檔案中定義的 Roboto 字型:
@font-face {
font-family: 'Roboto';
font-display: swap;
src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
font-weight: 700;
}
Fontaine 提供 Vite 和 Webpack 轉換工具,讓您輕鬆插入建構鏈結,如以下 JavaScript 所示啟用外掛程式。
import { FontaineTransform } from 'fontaine'
const options = {
fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
// You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
resolvePath: (id) => 'file:///path/to/public/dir' + id,
// overrideName: (originalName) => `${name} override`
// sourcemap: false
}
如果您使用的是 Vite,請新增外掛程式,如下所示:
javascript
// Vite
export default {
plugins: [FontaineTransform.vite(options)]
}
如果使用 Webpack,請按照下列步驟啟用:
// Webpack
export default {
plugins: [FontaineTransform.webpack(options)]
}
模組會自動掃描檔案以修改 @font-face 規則:
css
@font-face {
font-family: 'Roboto';
font-display: swap;
src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
font-weight: 700;
}
/* This will be generated. */
@font-face {
font-family: 'Roboto override';
src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
local('Arial'), local('Noto Sans');
ascent-override: 92.7734375%;
descent-override: 24.4140625%;
line-gap-override: 0%;
}
您現在可以使用 Roboto override
做為 CSS 中的備用字型。
css
:root {
font-family: 'Roboto';
/* This becomes */
font-family: 'Roboto', 'Roboto override';
}
使用 Capsize 程式庫
如果您使用的不是 Next.js、Nuxt、Webpack 或 Vite,也可以選擇使用 Capsize 程式庫產生備用 CSS。
新增 createFontStack API
API 屬於 @capsize/core 套件的一部分,稱為 createFontStack
,該套件會按照您指定的字型堆疊順序 (font-family
屬性) 來接受一系列字型指標。
如要瞭解如何使用容量上限,請參閱這篇文章。
範例
請參考以下範例:想要使用的網站字型是 Lobster,依序改回 Helvetica Neue 和 small。在 CSS 中,font-family: Lobster, 'Helvetica Neue', Arial
。
從核心套件匯入 createFontStack:
import { createFontStack } from '@capsizecss/core';
匯入每個所需字型的字型指標 (請參閱上方的「字型指標」):
javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`
按照 font-family CSS 屬性的順序,建立字型堆疊並以陣列形式傳遞指標。
javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);
這會傳回以下內容:
{
fontFamily: Lobster, 'Lobster Fallback: Helvetica Neue', 'Lobster Fallback: Arial',
fontFaces: [
{
'@font-face' {
'font-family': '"Lobster Fallback: Helvetica Neue"';
src: local('Helvetica Neue');
'ascent-override': '115.1741%';
'descent-override': '28.7935%';
'size-adjust': '86.8251%';
}
'@font-face' {
'font-family': '"Lobster Fallback: Arial"';
src: local('Arial');
'ascent-override': 113.5679%;
'descent-override': 28.392%;
'size-adjust': 88.053%;
}
}
]
}
您必須將 fontFamily 和 fontFaces 程式碼新增至 CSS。下列程式碼顯示如何在 CSS 樣式表或 <style>
區塊中實作。
<style type="text/css">
.heading {
font-family:
}
</style>
這會產生下列 CSS:
.heading {
font-family: Lobster, 'Lobster Fallback: Helvetica Neue',
'Lobster Fallback: Arial';
}
@font-face {
font-family: 'Lobster Fallback: Helvetica Neue';
src: local('Helvetica Neue');
ascent-override: 115.1741%;
descent-override: 28.7935%;
size-adjust: 86.8251%;
}
@font-face {
font-family: 'Lobster Fallback: Arial';
src: local('Arial');
ascent-override: 113.5679%;
descent-override: 28.392%;
size-adjust: 88.053%;
}
您也可以使用 @capsize/metrics 套件來計算覆寫值,並自行套用至 CSS。
const fontMetrics = require(`@capsizecss/metrics/inter`);
const fallbackFontMetrics = require(`@capsizecss/metrics/arial`);
const mainFontAvgWidth = fontMetrics.xAvgWidth / fontMetrics.unitsPerEm;
const fallbackFontAvgWidth = fallbackFontMetrics.xAvgWidth / fallbackFontMetrics.unitsPerEm;
let sizeAdjust = mainFontAvgWidth / fallbackFontAvgWidth;
let ascent = fontMetrics.ascent / (unitsPerEm * fontMetrics.sizeAdjust));
let descent = fontMetrics.descent / (unitsPerEm * fontMetrics.sizeAdjust));
let lineGap = fontMetrics.lineGap / (unitsPerEm * fontMetrics.sizeAdjust));
特別銘謝
主頁橫幅由 Alexander Andrews 於 Unsplash 提供。