Strony, które wczytują czcionki za pomocą atrybutu font-display: swap, często mają problemy z przesuwającym się układem (CLS), gdy czcionka internetowa wczytuje się i zostaje zastąpiona czcionką zapasową.
Możesz zapobiec CLS, dostosowując wymiary czcionki zapasowej do czcionki podstawowej. Właściwości takie jak size-adjust
, ascent-override
, descent-override
i line-gap-override
w regułach @font-face
mogą zastąpić dane czcionki zastępczej, co daje deweloperom większą kontrolę nad wyświetlaniem czcionek. Więcej informacji o fontach zapasowych i właściwościach zastąpienia znajdziesz w tym poście. Praktyczne wdrożenie tej metody możesz też zobaczyć w tej prezentacji.
Z tego artykułu dowiesz się, jak w ramkach Next.js i Nuxt.js implementować korekty rozmiaru czcionki, aby generować czcionkę zapasową w CSS i zmniejszać CLS. Pokazuje też, jak generować czcionki zapasowe za pomocą narzędzi ogólnych, takich jak Fontaine i Capsize.
Tło
Parametr font-display: swap służy zwykle do zapobiegania zaburzaniu funkcji FOIT (flashowanie niewidocznego tekstu) i do szybszego wyświetlania zawartości ekranu. Wartość swap
informuje przeglądarkę, że tekst, który ma być wyświetlany za pomocą czcionki, powinien być natychmiast wyświetlany za pomocą czcionki systemowej i że czcionka systemowa powinna zostać zastąpiona dopiero wtedy, gdy czcionka niestandardowa będzie gotowa.
Największym problemem w przypadku swap
jest efekt zniekształcenia, ponieważ różnica w rozmiarach znaków w 2 fontach powoduje przesuwanie się treści na ekranie. Prowadzi to do niskich wyników CLS, zwłaszcza w przypadku witryn z dużą ilością tekstu.
Na ilustracji poniżej widać przykład tego problemu. Pierwszy obraz używa font-display: swap
bez próby dostosowania rozmiaru czcionki zastępczej. Drugi przykład pokazuje, jak dostosowanie rozmiaru za pomocą reguły CSS @font-face
poprawia wczytywanie stron.
Bez dostosowywania rozmiaru czcionki
body {
font-family: Inter, serif;
}
Po dostosowaniu rozmiaru czcionki
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");
}
Dostosowywanie rozmiaru czcionki awaryjnej może być skuteczną strategią zapobiegania przesunięciu układu podczas wczytywania czcionki, ale implementacja logiki od podstaw może być trudna, jak opisano w tym poście na temat czcionek awaryjnych. Na szczęście istnieje już kilka opcji narzędzi ułatwiających tworzenie aplikacji.
Jak zoptymalizować zastępcze czcionki dzięki Next.js
Next.js udostępnia wbudowany sposób na włączenie optymalizacji czcionek zastępczych. Ta funkcja jest domyślnie włączona podczas wczytywania czcionek za pomocą komponentu @next/font.
Komponent @next/font został wprowadzony w wersji Next.js 13. Komponent udostępnia interfejs API do importowania czcionek Google Fonts lub czcionek niestandardowych na strony oraz zawiera wbudowane automatyczne hostowanie plików czcionek.
Gdy są używane, dane czcionki zastępczej są automatycznie obliczane i wstrzykiwane do pliku CSS.
Jeśli na przykład używasz czcionki Roboto, zwykle definiujesz ją w CSS w ten sposób:
@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;
}
Aby przejść do następnego czcionki:
Przesuń deklarację czcionki Roboto do kodu JavaScript, importując funkcję „Roboto” z „next/font”. Zwracaną wartością funkcji będzie nazwa klasy, której możesz użyć w szablonie komponentu. Pamiętaj, aby dodać obiekt
display: swap
do obiektu konfiguracji, aby włączyć tę funkcję.import { Roboto } from '@next/font/google'; const roboto = Roboto({ weight: '400', subsets: ['latin'], display: 'swap' // Using display swap automatically enables the feature })
W komponencie użyj wygenerowanej nazwy klasy:
javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }
Opcja konfiguracji adjustFontFallback:
W przypadku @next/font/google
: wartość logiczna określająca, czy automatyczny font zastępczy należy stosować w celu zmniejszenia kumulatywnej zmiany układu. Wartość domyślna to prawda (true). Next.js automatycznie ustawia czcionkę zastępczą na Arial
lub Times New Roman
w zależności od typu czcionki (szeryfowa lub bezszeryfowa).
W przypadku @next/font/local
: ciąg znaków lub wartość logiczna false określająca, czy automatyczny czcionka zastępcza ma być używana w celu zmniejszenia skumulowanego przesunięcia układu. Możliwe wartości to Arial
, Times New Roman
i false
. Wartość domyślna to Arial
. Jeśli chcesz używać czcionki szeryfowej, ustaw tę wartość na Times New Roman
.
Inna opcja czcionek Google
Jeśli nie możesz użyć komponentu next/font
, możesz użyć tej funkcji z Google Fonts, korzystając z flagi optimizeFonts
. W Next.js funkcja optimizeFonts jest już domyślnie włączona. Ta funkcja umieszcza czcionkę Google Fonts w odmowie HTML. Możesz też włączyć funkcję dostosowywania czcionek zapasowych, ustawiając flagę experimental.adjustFontFallbacksWithSizeAdjust
w pliku next.config.js, jak pokazano w tym fragmencie kodu:
// In next.config.js
module.exports = {
experimental: {
adjustFontFallbacksWithSizeAdjust: true,
},
}
Uwaga: nie planujemy obsługi tej funkcji w nowym poleceniu app
. Na dłuższą metę najlepiej używać polecenia next/font
.
Jak dostosować czcionki zastępcze w Nuxt
@nuxtjs/fontaine to moduł platformy Nuxt.js, który automatycznie oblicza wartości danych czcionki zastępczej i generuje zastępczy kod CSS @font-face
.
Aby włączyć moduł, dodaj @nuxtjs/fontaine
do konfiguracji modułów:
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
})
Jeśli używasz Google Fonts lub nie masz deklaracji @font-face
dla czcionki, możesz je zadeklarować jako opcje dodatkowe.
W większości przypadków moduł może odczytać reguły @font-face
z pliku CSS i automatycznie określić szczegóły, takie jak rodzina czcionek, zapasowa rodzina czcionek i typ wyświetlania.
Jeśli czcionka jest zdefiniowana w miejscu, które nie jest dostępne dla modułu, możesz przekazać informacje o danych w sposób pokazany w tym fragmencie kodu.
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
fontMetrics: {
fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})
Moduł automatycznie skanuje plik CSS, aby odczytać deklaracje @font-face i wygenerować substytucyjne reguły @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%;
}
Możesz teraz użyć Roboto override
jako czcionki zastępczej w CSS, jak pokazano w poniższym przykładzie
:root {
font-family: 'Roboto';
/* This becomes */
font-family: 'Roboto', 'Roboto override';
}
Samodzielne generowanie kodu CSS
Biblioteki samodzielne mogą też pomóc w generowaniu kodu CSS do dostosowywania rozmiaru czcionki do zapasowego.
Korzystanie z biblioteki Fontaine
Jeśli nie używasz Nuxt ani Next.js, możesz użyć Fontaine. Fontaine to podstawowa biblioteka, na której działa kod @nuxtjs/fontaine. Możesz użyć tej biblioteki w projekcie, aby automatycznie wstrzyknąć czcionkę zapasową w CSS za pomocą wtyczek Vite lub Webpack.
Załóżmy, że masz czcionkę Roboto zdefiniowaną w pliku 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 udostępnia moduły Vite i Webpack, które można łatwo wdrożyć w łańcuchu kompilacji. Aby włączyć moduł, użyj kodu JavaScript przedstawionego poniżej.
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
}
Jeśli używasz Vite, dodaj tę wtyczkę w ten sposób:
javascript
// Vite
export default {
plugins: [FontaineTransform.vite(options)]
}
Jeśli używasz pakietu Webpack, włącz go w następujący sposób:
// Webpack
export default {
plugins: [FontaineTransform.webpack(options)]
}
Moduł automatycznie przeskanuje pliki, aby zmodyfikować reguły @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%;
}
Teraz możesz używać czcionki Roboto override
jako czcionki zastępczej w CSS.
css
:root {
font-family: 'Roboto';
/* This becomes */
font-family: 'Roboto', 'Roboto override';
}
Korzystanie z biblioteki Capsize
Jeśli nie korzystasz z Next.js, Nuxt, Webpack ani Vite, możesz też użyć biblioteki Capsize, by wygenerować zastępczy kod CSS.
Nowy interfejs API createFontStack
Interfejs API jest częścią pakietu @capsize/core o nazwie createFontStack
, który akceptuje tablicę danych czcionki w tej samej kolejności, w jakiej określasz zestaw czcionek (właściwość font-family
).
Tutaj znajdziesz dokumentację dotyczącą korzystania z funkcji Capsize.
Przykład
Rozważmy ten przykład: wybrana czcionka internetowa to Lobster, a zastępcza Helvetica Neue, a potem Arial. W usłudze porównywania cen: font-family: Lobster, 'Helvetica Neue', Arial
.
Importuj createFontStack z pakietu podstawowego:
import { createFontStack } from '@capsizecss/core';
Zaimportuj dane dotyczące poszczególnych czcionek (patrz: Dane czcionek powyżej):
javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`
Utwórz zestaw czcionek, przekazując dane jako tablicę w takim samym porządku, jak w przypadku właściwości CSS font-family.
javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);
Zwraca to:
{
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%;
}
}
]
}
Musisz dodać kod fontFamily i fontFaces do kodu CSS. Poniższy kod pokazuje, jak zaimplementować go w arkuszu stylów CSS lub w bloku <style>
.
<style type="text/css">
.heading {
font-family:
}
</style>
Spowoduje to wygenerowanie takiego kodu 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%;
}
Możesz też użyć pakietu @capsize/metrics, aby obliczyć wartości zastąpienia i samodzielnie je zastosować w usłudze porównywania cen.
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));