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

[Tên người]
Janicklas Roll 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ữ trên 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. Nhờ vậy, nhà phát triển có thể kiểm soát tốt hơn cách phông chữ hiển thị. Bạn có thể đọc thêm về phông chữ dự phòng 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 tìm hiểu cách triển khai các tính năng điều chỉnh kích thước phông chữ trong 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 chéo như Fontaine và Capsize.

Thông tin khái quát

font-display: swap thường dùng để ngăn FOIT (Đèn flash của văn bản không hiển thị) và hiện 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ữ sẽ 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 gây khó chịu, trong đó sự khác biệt về kích thước ký tự của hai phông chữ khiến nội dung màn hình thay đổi. Điều này sẽ dẫn đến điểm CLS kém, đặc biệt là đối với những trang web có nhiều văn bản.

Các hình ảnh sau đây minh hoạ 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 của phông chữ dự phòng. Hình ảnh thứ hai cho thấy việc điều chỉnh kích thước bằng quy tắc @font-face của CSS sẽ cải thiện trải nghiệm tải như thế nào.

Không điều chỉnh cỡ chữ

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

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 mượt mà sang phông chữ khác.

Việc điều chỉnh kích thước của phông chữ dự phòng có thể là một chiến lược hiệu quả để ngăn chặn hiện tượng chuyển đổi bố cục tải phông chữ, nhưng có thể sẽ khó triển khai logic từ đầu, như mô tả trong bài đăng này về tính năng dự phòng phông chữ. Thật may là hiện có một số tuỳ chọn công cụ giúp bạn làm việc này dễ dàng hơn trong quá trình phát triển ứng dụng.

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

Next.js cung cấp một phương thức 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 trang của bạn và bao gồm tính năng tự động lưu trữ tích hợp sẵn 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à đưa 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ữ này 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 phông chữ/tiếp theo:

  1. Di chuyển phần khai báo phông chữ Roboto vào JavaScript của bạn bằng cách nhập hàm 'Roboto' từ 'next/font'. Giá trị trả về của hàm sẽ là một tên lớp mà bạn có thể tận dụng trong mẫu thành phần của mình. 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 dùng tên lớp đã 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: Một giá trị boolean xác định xem có nên dùng phông chữ dự phòng tự động để giảm Điểm số tổng hợp về mức thay đổi bố cục hay không. Giá trị mặc định là "true". Next.js sẽ tự động đặt phông chữ dự phòng của bạn thành Arial hoặc Times New Roman tuỳ thuộc vào loại phông chữ (serif và alt-serif tương ứng).

Đối với @next/font/local: Một giá trị sai dạng chuỗi hoặc boolean giúp xác định xem có nên dùng phông chữ dự phòng tự động để giảm Điểm số tổng hợp về mức thay đổi bố cục hay không. 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ữ của Google

Nếu không thể sử dụng thành phần next/font, thì có 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 có tính năng OptimizeFonts đã được bật theo mặc định. Tính năng này đặt cùng dòng CSS Google Font 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ư minh hoạ trong đoạn mã sau:

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

Lưu ý: Chúng tôi chưa có kế hoạch hỗ trợ tính năng này với thư mục app mới được 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 dành cho khung Nuxt.js, giúp tự động tính toán 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 của bạn:

import { defineNuxtConfig } from 'nuxt'

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

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

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

Nếu phông chữ được xác định ở một vị trí mà mô-đun không thể phát hiện được, bạn có thể chuyển thông tin 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 các phần 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

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

Sử dụng thư viện Fontaine

Nếu không sử dụng Nuxt hoặc Next.js, thì 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 của mình để tự động chèn CSS phông chữ dự phòng bằng cách sử dụng các trình bổ trợ Vite hoặc Webpack.

Giả sử bạn có 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 bản dựng, hãy bật trình bổ trợ này như minh hoạ 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 bạn sử dụng Webpack, hãy bật như sau:

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

Mô-đun này sẽ tự động quét các tệp của bạn để sửa đổi các 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ể 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 không sử dụng Next.js, Nuxt, Webpack hoặc Vite, bạn có thể 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 createFontStack, chấp nhận một mảng các chỉ số phông chữ theo thứ tự giống như thứ tự 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, quay lại Trường hợp Nếu có, sau đó trở lại Sử dụng Phông chữ Unicode. Trong CSS, font-family: Lobster, 'Helvetica Neue', Arial.

  1. Nhập createFontStack từ gói cốt lõi:

    import { createFontStack } from '@capsizecss/core';
    
  2. Nhập chỉ số phông chữ cho từng phông chữ bạn 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ữ, chuyển các chỉ số dưới dạng mảng theo thứ tự tương tự như cách bạn thực hiện thông qua thuộc tính CSS của bộ phông chữ. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

Thao tác này sẽ trả về:

{
  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 của mình. Đoạn mã sau đây cho biết cách triển khai trong biểu định kiểu CSS hoặc trong 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/metric để 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));

Xác nhận

Hình ảnh chính của Alexander Andrews trên Unsplash.