Công cụ khung cho tính năng dự phòng phông chữ

Janicklas Ralph James
Janicklas Ralph James

Các trang web tải phông chữ bằng font-display: swap thường bị thay đổi bố cục (CLS) khi phông chữ web tải và được hoán đổi với phông chữ dự phòng.

Bạn có thể ngăn CLS bằng cách điều chỉnh kích thước của phông chữ dự phòng cho phù hợp với kích thước của phông chữ chính. Các thuộc tính như size-adjust, ascent-override, descent-overrideline-gap-override trong quy tắc @font-face có thể giúp ghi đè các chỉ số của phông chữ dự phòng, cho phép nhà phát triển kiểm soát nhiều hơn cách hiển thị phông chữ. Bạn có thể đọc thêm về font-fallbacks và các thuộc tính ghi đè trong bài đăng này. Bạn cũng có thể xem cách triển khai kỹ thuật này trong bản minh hoạ này.

Bài viết này khám phá cách điều chỉnh cỡ chữ được triển khai trong các khung Next.js và Nuxt.js để tạo CSS phông chữ dự phòng và giảm CLS. Bài viết này cũng minh hoạ cách bạn có thể tạo phông chữ dự phòng bằng các công cụ cắt ngang như Fontaine và Capsize.

Thông tin khái quát

font-display: swap thường được dùng để ngăn FOIT (Hiện văn bản không hiển thị) và hiển thị nội dung nhanh hơn trên màn hình. Giá trị của swap cho trình duyệt biết rằng văn bản sử dụng phông chữ phải hiển thị ngay lập tức bằng phông chữ hệ thống và chỉ thay thế phông chữ hệ thống khi phông chữ tuỳ chỉnh đã sẵn sàng.

Vấn đề lớn nhất với swap là hiệu ứng chói mắt, trong đó sự khác biệt về kích thước ký tự của hai phông chữ dẫn đến nội dung trên màn hình bị dịch chuyển. Điều này dẫn đến điểm CLS thấp, đặc biệt là đối với các trang web có nhiều văn bản.

Hình ảnh sau đây cho thấy ví dụ về vấn đề này. Hình ảnh đầu tiên sử dụng font-display: swap mà không điều chỉnh kích thước phông chữ dự phòng. Hình thứ hai cho thấy cách điều chỉnh kích thước bằng quy tắc @font-face CSS giúp cải thiện trải nghiệm tải.

Không điều chỉnh kích thước phông chữ

body {
  font-family: Inter, serif;
}
Văn bản đột nhiên thay đổi phông chữ và kích thước gây ra hiệu ứng chói tai.

Sau khi điều chỉnh cỡ chữ

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");
}
Văn bản chuyển đổi liền mạch sang một phông chữ khác.

Điều chỉnh kích thước phông chữ dự phòng có thể là một chiến lược hiệu quả để ngăn việc thay đổi bố cục tải phông chữ, nhưng việc triển khai logic từ đầu có thể sẽ gặp khó khăn, như mô tả trong bài đăng này về phông chữ dự phòng. May mắn là có một số tuỳ chọn công cụ để giúp bạn dễ dàng thực hiện việc này trong khi phát triển ứng dụng.

Cách tối ưu hoá phông chữ dự phòng bằng Next.js

Next.js cung cấp một cách tích hợp để bật tính năng tối ưu hoá phông chữ dự phòng. Tính năng này được bật theo mặc định khi bạn tải phông chữ bằng thành phần @next/font.

Thành phần @next/font được giới thiệu trong Next.js phiên bản 13. Thành phần này cung cấp một API để nhập Google Fonts hoặc phông chữ tuỳ chỉnh vào các trang của bạn, đồng thời tích hợp tính năng tự lưu trữ tự động các tệp phông chữ.

Khi được sử dụng, các chỉ số phông chữ dự phòng sẽ tự động được tính toán và chèn vào tệp CSS.

​​Ví dụ: nếu đang sử dụng phông chữ Roboto, bạn thường sẽ xác định phông chữ đó trong CSS như sau:

@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;
}

Cách di chuyển sang next/font:

  1. Di chuyển phần khai báo phông chữ Roboto vào Javascript bằng cách nhập hàm "Roboto" từ "next/font". Giá trị trả về của hàm sẽ là tên lớp mà bạn có thể tận dụng trong mẫu thành phần. Hãy nhớ thêm display: swap vào đối tượng cấu hình để bật tính năng này.

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. Trong thành phần, hãy sử dụng tên lớp được tạo: javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }

Tuỳ chọn cấu hình adjustFontFallback:

Đối với @next/font/google: Giá trị boolean đặt xem có nên sử dụng phông chữ dự phòng tự động để giảm Độ lệch bố cục tích luỹ hay không. Giá trị mặc định là "true". Next.js sẽ tự động đặt phông chữ dự phòng thành Arial hoặc Times New Roman tuỳ thuộc vào loại phông chữ (serif so với sans-serif tương ứng).

Đối với @next/font/local: Một chuỗi hoặc giá trị boolean false (sai) giúp đặt xem có nên sử dụng phông chữ dự phòng tự động để giảm Độ lệch bố cục tích luỹ hay không. Các giá trị có thể là Arial, Times New Roman hoặc false. Giá trị mặc định là Arial. Nếu bạn muốn sử dụng phông chữ serif, hãy cân nhắc đặt giá trị này thành Times New Roman.

Một lựa chọn khác cho phông chữ Google

Nếu bạn không thể sử dụng thành phần next/font, thì một phương pháp khác để sử dụng tính năng này với Google Fonts là thông qua cờ optimizeFonts. Next.js đã bật tính năng optimizeFonts theo mặc định. Tính năng này đưa CSS Phông chữ Google vào cùng dòng trong phản hồi HTML. Ngoài ra, bạn có thể bật tính năng điều chỉnh phông chữ dự phòng bằng cách đặt cờ experimental.adjustFontFallbacksWithSizeAdjust trong next.config.js, như trong đoạn mã sau:

// In next.config.js
module.exports = {
 experimental: {
   adjustFontFallbacksWithSizeAdjust: true,
 },
}

Lưu ý: Chúng tôi không có kế hoạch hỗ trợ tính năng này bằng thư mục app mới ra mắt. Về lâu dài, bạn nên sử dụng next/font.

Cách điều chỉnh phông chữ dự phòng bằng Nuxt

@nuxtjs/fontaine là một mô-đun cho khung Nuxt.js, tự động tính toán các giá trị chỉ số phông chữ dự phòng và tạo CSS @font-face dự phòng.

Bật mô-đun bằng cách thêm @nuxtjs/fontaine vào cấu hình mô-đun:

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

Nếu sử dụng Google Fonts hoặc không có nội dung khai báo @font-face cho phông chữ, bạn có thể khai báo phông chữ đó dưới dạng tuỳ chọn bổ sung.

Trong hầu hết các trường hợp, mô-đun có thể đọc các quy tắc @font-face từ CSS và tự động suy luận các chi tiết như bộ phông chữ, bộ phông chữ dự phòng và loại màn hình.

Nếu phông chữ được xác định ở một vị trí mà mô-đun không thể phát hiện, bạn có thể truyền thông tin về chỉ số như trong đoạn mã sau.

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
  fontMetrics: {
  fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})

Mô-đun này tự động quét CSS để đọc nội dung khai báo @font-face và tạo các quy tắc @font-face dự phòng.

@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%;
}

Giờ đây, bạn có thể sử dụng Roboto override làm phông chữ dự phòng trong CSS, như trong ví dụ sau

:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto override';
}

Tự tạo CSS

Thư viện độc lập cũng có thể giúp bạn tạo CSS để điều chỉnh cỡ chữ dự phòng.

Sử dụng thư viện Fontaine

Nếu không sử dụng Nuxt hoặc Next.js, bạn có thể sử dụng Fontaine. Fontaine là thư viện cơ bản hỗ trợ @nuxtjs/fontaine. Bạn có thể sử dụng thư viện này trong dự án để tự động chèn CSS phông chữ dự phòng bằng trình bổ trợ Vite hoặc Webpack.

Giả sử bạn có một phông chữ Roboto được xác định trong tệp 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;
}

Fontaine cung cấp các trình chuyển đổi ViteWebpack để dễ dàng cắm vào chuỗi xây dựng, bật trình bổ trợ như trong JavaScript sau.

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
}

Nếu bạn đang sử dụng Vite, hãy thêm trình bổ trợ như sau: javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Nếu sử dụng Webpack, hãy bật Webpack như sau:

// Webpack
export default {
  plugins: [FontaineTransform.webpack(options)]
}

Mô-đun sẽ tự động quét các tệp của bạn để sửa đổi quy tắc @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%; }

Giờ đây, bạn có thể sử dụng Roboto override làm phông chữ dự phòng trong CSS. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Sử dụng thư viện Capsize

Nếu bạn không sử dụng Next.js, Nuxt, Webpack hoặc Vite, thì bạn có thể sử dụng thư viện Capsize để tạo CSS dự phòng.

API createFontStack mới

API này là một phần của gói @capsize/core có tên là createFontStack. Gói này chấp nhận một mảng các chỉ số phông chữ theo thứ tự mà bạn chỉ định ngăn xếp phông chữ (thuộc tính font-family).

Bạn có thể tham khảo tài liệu về cách sử dụng Capsize tại đây.

Ví dụ:

Hãy xem xét ví dụ sau: Phông chữ web mong muốn là Lobster, sử dụng phông chữ dự phòng là Helvetica Neue rồi đến Arial. Trong CSS, font-family: Lobster, 'Helvetica Neue', Arial.

  1. Nhập createFontStack từ gói core:

    import { createFontStack } from '@capsizecss/core';
    
  2. Nhập các chỉ số phông chữ cho từng phông chữ mong muốn (xem Chỉ số phông chữ ở trên): javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. Tạo ngăn xếp phông chữ, truyền các chỉ số dưới dạng một mảng, sử dụng cùng thứ tự như bạn thực hiện thông qua thuộc tính CSS font-family. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

Thao tác này sẽ trả về kết quả sau:

{
  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%;
     }
   }
 ]
}

Bạn phải thêm mã fontFamily và fontFaces vào CSS. Đoạn mã sau đây cho biết cách triển khai thuộc tính này trong một trang kiểu CSS hoặc trong một khối <style>.

<style type="text/css">
  .heading {
    font-family: 
  }

  
</style>

Thao tác này sẽ tạo ra CSS sau:

.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%;
}

Bạn cũng có thể sử dụng gói @capsize/metrics để tính toán các giá trị ghi đè và tự áp dụng các giá trị đó cho 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));