字型備用廣告架構工具

珍妮卡斯.雷夫.詹姆斯 (Janicklas Ralph James)
Janicklas Ralph James

如果網站使用 font-display: Swap 載入字型,當網站字型載入並替換為備用字型時,版面配置位移 (CLS) 常會發生版面配置位移。

如要預防 CLS,您可以調整備用字型的尺寸,使其與主要字型相符。@font-face 規則中的 size-adjustascent-overridedescent-overrideline-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;
}
文字突然變更字型與大小,造成誇大效果。

調整字型大小後

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");
}
流暢轉換至其他字型的文字。

調整備用廣告字型的大小是防止字型載入版面配置位移的有效策略,但從頭開始實作邏輯可能不太容易,詳情請參閱有關字型備用的這篇文章。幸好,現在有許多工具選項能協助您輕鬆開發應用程式。

如何使用 Next.js 最佳化字型備用

Next.js 提供內建方法,可啟用備用字型最佳化功能。當您使用 @next/font 元件載入字型時,系統預設會啟用這項功能。

@next/font 元件是在 Next.js 13 版中導入。這個元件提供的 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;
}

如何遷移至下一個/字型:

  1. 從「next/font」匯入「Roboto」函式,即可將 Roboto 字型宣告移到 JavaScript 中。函式的傳回值將成為可在元件範本中使用的類別名稱。 請記得將 display: swap 新增至設定物件,以啟用這項功能。

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. 在元件中,使用產生的類別名稱: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 自動設定),將備用字型設為 ArialTimes New Roman

@next/font/local字串或布林值 false 值,用來設定是否要使用自動備用字型來減少「累計版面配置位移」。可能的值為 ArialTimes New Romanfalse。預設值為 Arial。如果您想使用 Serif 字型,建議將這個值設為 Times New Roman

Google 字型的另一個選項

如果無法選擇使用 next/font 元件,您也可以透過 optimizeFonts 旗標來搭配 Google Fonts 使用這項功能。Next.js 已預設啟用 optFonts 功能。這項功能會在 HTML 回應中內嵌 Google Font CSS。此外,您也可以在 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 提供 ViteWebpack 轉換器,方便您輕鬆插入建構鏈結,啟用外掛程式,如以下 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 屬於名為 createFontStack@capsize/core 套件,可接受字型指標陣列,與指定字型堆疊 (font-family 屬性) 的順序相同。

您可以參閱這篇文章,瞭解如何使用 Capsize 功能。

範例

假設使用以下例子:想要使用的網頁字型是 Lobster,先改回 Helvetica Neue,然後是 SMTP。在 CSS 中,font-family: Lobster, 'Helvetica Neue', Arial

  1. 從核心套件匯入 createFontStack:

    import { createFontStack } from '@capsizecss/core';
    
  2. 為各個所需字型匯入字型指標 (請參閱上方的「字型指標」):javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. 建立字型堆疊,並將指標以陣列的形式傳遞,使用的順序與字型系列 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%;
     }
   }
 ]
}

您必須在 CSS 中新增 fontFamily 和 fontFaces 程式碼。以下程式碼說明如何在 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 提供。