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. 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 proces wczytywania.
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, gdy wczytujesz czcionki za pomocą komponentu @next/font.
Komponent @next/font został wprowadzony w wersji Next.js 13. Komponent udostępnia interfejs API umożliwiający importowanie czcionek Google lub czcionek niestandardowych na strony i wbudowany, automatyczny, samoobsługowy hosting 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. 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 zastępczy należy stosować w celu zmniejszenia kumulatywnej zmiany układu. Wartość domyślna to true (prawda). 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 jest wbudowana w kod CSS czcionki Google w 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 dodawać obsługi tej funkcji z nowo wprowadzonym kierunkiem app
. W dłuższej perspektywie najlepiej jest używać next/font
.
Dostosowywanie wartości zastępczych czcionek za pomocą 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 do jego konfiguracji moduł @nuxtjs/fontaine
:
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
})
Jeśli używasz Google Fonts lub nie masz deklaracji @font-face
dotyczącej czcionki, możesz zadeklarować je jako opcje dodatkowe.
W większości przypadków moduł może odczytywać reguły @font-face
z CSS i automatycznie uzyskiwać szczegółowe informacje, takie jak rodzina czcionek, rodzina czcionek zastępczych 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 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 transformery Vite i Webpack, które umożliwiają łatwe podłączenie wtyczki do łańcucha kompilacji i włączanie wtyczki w sposób pokazany 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 wtyczkę w ten sposób:javascript
// Vite
export default {
plugins: [FontaineTransform.vite(options)]
}
Jeśli używasz Webpack, włącz tę funkcję 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 następujący przykład: żądana czcionka internetowa to Hobster, której czcionka to HDetails Neue, a potem opcję {8}. W usłudze porównywania cen: font-family: Lobster, 'Helvetica Neue', Arial
.
Zaimportuj createFontStack z pakietu głównego:
import { createFontStack } from '@capsizecss/core';
Zaimportuj dane czcionki dla poszczególnych 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 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));