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

Janicklas Ralph James
Janicklas Ralph James

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

Bạn có thể ngăn chặn CLS (Mức thay đổi bố cục tích luỹ) bằng cách điều chỉnh kích thước của phông chữ dự phòng cho khớ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 có nhiều quyền kiểm soát hơn đối với cách hiển thị phông chữ. Bạn có thể đọc thêm về tính năng dự phòng phông chữ và 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 trong bản minh hoạ này.

Bài viết này tìm hiểu cách triển khai việc đ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 (Mức thay đổi bố cục tích luỹ). Tài liệu này cũng minh hoạ cách 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 được dùng để ngăn FOIT (Nhấp nháy 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 gây khó chịu, trong đó sự khác biệt về kích thước ký tự của 2 phông chữ khiến nội dung trên màn hình thay đổi. Điều này sẽ dẫn đến điểm CLS thấp, đặc biệt đối với những trang web có nhiều văn bản.

Các hình ảnh sau đây cho thấy ví dụ về vấn đề. Hình ảnh đầu tiên sử dụng font-display: swap mà không cố điều chỉnh kích thước của phông chữ dự phòng. Hình thứ hai cho thấy việc điều chỉnh kích thước bằng quy tắc CSS @font-face giúp 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 ra 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 phông chữ dự phòng có thể là một chiến lược hiệu quả để ngăn tình trạng thay đổi bố cục khi tải phông chữ. Tuy nhiên, việc triển khai logic từ đầu có thể rất khó khăn, như mô tả trong bài đăng này về phông chữ dự phòng. May mắn là đã có sẵn một số tuỳ chọn công cụ để giúp việc này dễ dàng hơn trong khi phát triển ứng dụng.

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

Next.js cung cấp một phương pháp 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 sẵn tính năng tự động lưu trữ các tệp phông chữ.

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

Ví dụ: nếu bạn đ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;
}

Để di chuyển sang phương thức tiếp theo/phông chữ:

  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à 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 của bạn, hãy sử 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ều chỉnh phông chữ):

Đối với @next/font/google: Một giá trị boolean cho biết 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 (Layout Shift) hay không. Giá trị mặc định là "true". Next.js 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à Sans Serif tương ứng).

Đối với @next/font/local: Một chuỗi hoặc giá trị false boolean xác định việc 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 (Layout Shift) hay không. Các giá trị có thể sử dụng 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ì bạn có thể sử dụng tính năng này với Google Fonts theo cách khác 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 nội tuyến CSS của 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 tính năng dự phòng phông chữ 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 thông qua thư mục app mới được giới thiệu. 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 khai báo phông chữ @font-face, thì bạn có thể khai báo phông chữ đó dưới dạng lựa 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 suy luận các 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 ở vị trí mà mô-đun không thể phát hiện được, bạn có thể chuyể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 sẽ tự động quét CSS của bạn để đọc nội dung khai báo @font-face và tạo quy tắc dự phòng @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%;
}

Bây giờ, bạn có thể sử dụng Roboto override làm phông chữ dự phòng trong CSS của mình, 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, bạn có thể sử dụng Fontaine. Fontaine là thư viện cơ bản hỗ trợ cho @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ác trình bổ trợ Vite hoặc Webpack.

Hãy tưởng tượng 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 biến đổi ViteWebpack để dễ dàng cắm vào chuỗi bản dựng, bật trình bổ trợ 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 gói này 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 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 Kích thước tối đa

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

API createFontStack mới

API này nằm trong gói @capsize/core có tên là createFontStack. Gói này chấp nhận một loạt chỉ số phông chữ theo cùng thứ tự 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 tính năng Tăng kích thước tại đây.

Ví dụ:

Hãy xem xét ví dụ sau: Phông chữ bạn muốn dùng trên web là Tôm hùm, quay lại định dạng gây kỹ thuật số 1 ý 24 giờ rồi đến m. 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ữ, truyền các chỉ số dưới dạng mảng, theo cùng thứ tự bạn thực hiện thông qua thuộc tính CSS bộ phông chữ. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

Hàm này trả về các giá trị 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 của mình. 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/chiso để 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.