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ępowania znajdziesz w tym poście. W tym demo możesz też zobaczyć, jak działa ta technika.
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
font-display: swap jest zwykle używane, aby zapobiec FOIT (miganiu niewidocznego tekstu) i szybciej wyświetlać treści na ekranie. 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 pokazuje, jak dostosowanie rozmiaru za pomocą reguły CSS @font-face
poprawia wczytywanie.
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ć czcionki zastępcze za pomocą Next.js
Next.js udostępnia wbudowany sposób na włączenie optymalizacji czcionek zastępczych. Ta funkcja jest domyślnie włączona, gdy wczytujesz czcionki 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 czcionek zastępczych 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. Aby włączyć tę funkcję, dodaj
display: swap
do obiektu konfiguracji.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 zapasowy ma być używany do 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żyć 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 wstawia czcionkę Google Fonts w odpowiedniej odpowiedzi 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ł dla platformy Nuxt.js, który automatycznie oblicza wartości danych czcionek zapasowych i generuje @font-face
CSS zapasowy.
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%;
}
W kodzie CSS możesz teraz użyć Roboto override
jako czcionki zastępczej, jak pokazano w tym 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 rozmiaru.
Korzystanie z biblioteki Fontaine
Jeśli nie używasz Nuxt ani Next.js, możesz użyć Fontaine. Fontaine to biblioteka, która umożliwia działanie pakietu @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 przekształcenia Vite i Webpack, które można łatwo wstawić do łańcucha kompilacji. Aby włączyć wtyczkę, wykonaj instrukcje podane w tym kodzie JavaScript.
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 wtyczkę w ten sposób:javascript
// Vite
export default {
plugins: [FontaineTransform.vite(options)]
}
Jeśli używasz Webpacka, włącz go w ten 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 używasz Next.js, Nuxt, Webpacka ani Vite, możesz wygenerować zapasowy plik CSS, korzystając z biblioteki Capsize.
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
).
Dokumentację dotyczącą korzystania z Capsize znajdziesz tutaj.
Przykład
Rozważmy ten przykład: wybrana czcionka internetowa to Lobster, a zastępcza Helvetica Neue, a potem Arial. W CSS: font-family: Lobster, 'Helvetica Neue', Arial
.
Zaimportuj createFontStack z pakietu głównego:
import { createFontStack } from '@capsizecss/core';
Zaimportuj dane czcionki dla każdej z wybranych czcionek (patrz Dane czcionki 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 tym samym porządku, w jakim używasz ich 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 tego 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 zastosować je 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));