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 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-override
và line-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. 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 (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 sẽ dẫn đến điểm CLS thấp, đặc biệt là đối với những 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;
}
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");
}
Đ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:
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 })
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 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ể 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 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 đã bật tính năng optimizeFonts 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 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 khai báo phông chữ @font-face
, thì bạn có thể khai báo các 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 có thể đọc các quy tắc @font-face
từ CSS và tự động suy luận các thông tin 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%;
}
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
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 Vite và Webpack để 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 Kích thước tối đa
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 nằm trong gói @capsize/core có tên là createFontStack
. Gói này chấp nhận một mảng 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 Capsize tại đây.
Ví dụ:
Hãy xem xét ví dụ sau: Phông chữ web mong muốn là Lobster, sau đó chuyển sang Helvetica Neue rồi đến Arial. Trong CSS, font-family: Lobster, 'Helvetica Neue', Arial
.
Nhập createFontStack từ gói core:
import { createFontStack } from '@capsizecss/core';
Nhập 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';`
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, ]);
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));