Сайты, которые загружают шрифты с помощью font-display: swap, часто страдают от сдвига макета ( CLS ), когда веб-шрифт загружается и заменяется резервным шрифтом.
Вы можете предотвратить CLS, настроив размеры резервного шрифта так, чтобы они соответствовали размеру основного шрифта. Такие свойства, как size-adjust
, ascent-override
, descent-override
и line-gap-override
в правиле @font-face
могут помочь переопределить метрики резервного шрифта, предоставляя разработчикам больше контроля над отображением шрифтов. Подробнее о Font-Fallbacks и свойствах переопределения вы можете прочитать в этом посте . Вы также можете увидеть рабочую реализацию этой техники в этой демо-версии .
В этой статье рассматривается, как в платформах Next.js и Nuxt.js реализованы настройки размера шрифта для создания CSS резервного шрифта и уменьшения CLS. Он также демонстрирует, как можно создавать резервные шрифты с помощью таких сквозных инструментов, как Fontaine и Capsize.
Фон
font-display: swap обычно используется для предотвращения FOIT (вспышки невидимого текста) и для более быстрого отображения содержимого на экране. Значение swap
сообщает браузеру, что текст, использующий этот шрифт, должен немедленно отображаться с использованием системного шрифта и заменять системный шрифт только тогда, когда пользовательский шрифт будет готов.
Самая большая проблема при swap
— это эффект резкости, когда разница в размерах символов двух шрифтов приводит к смещению содержимого экрана. Это приводит к плохим оценкам CLS, особенно для веб-сайтов с большим количеством текста.
На следующих изображениях показан пример проблемы. В первом изображении используется font-display: swap
без попыток настроить размер резервного шрифта. Второй показывает, как регулировка размера с помощью правила CSS @font-face
улучшает процесс загрузки.
Без регулировки размера шрифта
body {
font-family: Inter, serif;
}
После настройки размера шрифта
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");
}
Настройка размера резервного шрифта может быть эффективной стратегией предотвращения смещения макета загрузки шрифта, но реализация логики с нуля может оказаться сложной задачей, как описано в этом посте о резервных шрифтах . К счастью, уже доступно несколько вариантов инструментов, упрощающих разработку приложений.
Как оптимизировать резервные шрифты с помощью Next.js
Next.js предоставляет встроенный способ включения резервной оптимизации шрифтов. Эта функция включена по умолчанию при загрузке шрифтов с помощью компонента @next/font .
Компонент @next/font был представлен в Next.js версии 13. Компонент предоставляет API для импорта шрифтов Google или пользовательских шрифтов на ваши страницы, а также включает встроенное автоматическое размещение файлов шрифтов на собственном хостинге.
При использовании метрики резервного шрифта автоматически рассчитываются и вставляются в файл CSS.
Например, если вы используете шрифт Roboto, вы обычно определяете его в 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;
}
body {
font-family: Roboto;
}
Чтобы перейти к следующему/шрифту:
Переместите объявление шрифта Roboto в свой Javascript, импортировав функцию «Roboto» из «next/font». Возвращаемым значением функции будет имя класса, которое вы можете использовать в шаблоне компонента. Не забудьте добавить
display: swap
к объекту конфигурации, чтобы включить эту функцию.import { Roboto } from '@next/font/google'; const roboto = Roboto({ weight: '400', subsets: ['latin'], display: 'swap' // Using display swap automatically enables the feature })
В своем компоненте используйте сгенерированное имя класса:
javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }
Параметр конфигурации AdjustFontFallback :
Для @next/font/google
: логическое значение, определяющее, следует ли использовать автоматический резервный шрифт для уменьшения совокупного сдвига макета. По умолчанию верно. Next.js автоматически устанавливает резервный шрифт Arial
или Times New Roman
в зависимости от типа шрифта (с засечками или без засечек соответственно).
Для @next/font/local
: строковое или логическое значение false, определяющее, следует ли использовать автоматический резервный шрифт для уменьшения совокупного сдвига макета. Возможные значения: Arial
, Times New Roman
или false
. По умолчанию используется Arial
. Если вы хотите использовать шрифт с засечками, рассмотрите возможность установки для этого значения Times New Roman
.
Еще один вариант Google-шрифтов
Если использование компонента next/font
невозможно, другой способ использования этой функции с Google Fonts — использование optimizeFonts
. В Next.js функция оптимизацииFonts уже включена по умолчанию. Эта функция встраивает CSS шрифта Google в ответ HTML. Кроме того, вы можете включить функцию настройки резервных шрифтов, установив флаг experimental.adjustFontFallbacksWithSizeAdjust
в вашем файле next.config.js, как показано в следующем фрагменте:
// In next.config.js
module.exports = {
experimental: {
adjustFontFallbacksWithSizeAdjust: true,
},
}
Примечание . Поддержка этой функции с помощью недавно представленного каталога app
не планируется. В долгосрочной перспективе идеально использовать next/font
.
Как настроить резервные шрифты с помощью Nuxt
@nuxtjs/fontaine — это модуль для платформы Nuxt.js, который автоматически вычисляет значения показателей резервного шрифта и генерирует резервный CSS @font-face
.
Включите модуль, добавив @nuxtjs/fontaine
в конфигурацию вашего модуля:
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
})
Если вы используете Google Fonts или у вас нет объявления @font-face
для шрифта, вы можете объявить их как дополнительные параметры.
В большинстве случаев модуль может считывать правила @font-face
из вашего CSS и автоматически определять такие детали, как семейство шрифтов, семейство резервных шрифтов и тип отображения.
Если шрифт определен в месте, недоступном для обнаружения модулем, вы можете передать информацию о метриках, как показано в следующем фрагменте кода.
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine'],
fontMetrics: {
fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})
Модуль автоматически сканирует ваш CSS, чтобы прочитать объявления @font-face, и генерирует резервные правила @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%;
}
Теперь вы можете использовать Roboto override
в качестве резервного шрифта в CSS, как показано в следующем примере.
:root {
font-family: 'Roboto';
/* This becomes */
font-family: 'Roboto', 'Roboto override';
}
Генерация CSS самостоятельно
Автономные библиотеки также могут помочь вам сгенерировать CSS для резервной настройки размера шрифта.
Использование библиотеки Fontaine
Если вы не используете Nuxt или Next.js, вы можете использовать Fontaine. Fontaine — это базовая библиотека, лежащая в основе @nuxtjs/fontaine . Вы можете использовать эту библиотеку в своем проекте для автоматического внедрения резервного шрифта CSS с помощью плагинов Vite или Webpack.
Представьте, что у вас есть шрифт Roboto, определенный в файле 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 предоставляет преобразователи Vite и Webpack для легкого подключения к цепочке сборки. Включите плагин, как показано в следующем коде 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
}
Если вы используете Vite, добавьте плагин следующим образом: javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }
Если вы используете Webpack, включите его следующим образом:
// Webpack
export default {
plugins: [FontaineTransform.webpack(options)]
}
Модуль автоматически просканирует ваши файлы, чтобы изменить правила @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%; }
Теперь вы можете использовать Roboto override
в качестве резервного шрифта в CSS. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }
Использование библиотеки Capsize
Если вы не используете Next.js, Nuxt, Webpack или Vite, другой вариант — использовать библиотеку Capsize для создания резервного CSS.
Новый API createFontStack.
API является частью пакета @capsize/core под названием createFontStack
, который принимает массив показателей шрифта в том же порядке, в котором вы указываете свой стек шрифтов (свойство font-family
).
Вы можете обратиться к документации по использованию Capsize здесь .
Пример
Рассмотрим следующий пример: желаемый веб-шрифт — Lobster, сначала Helvetica Neue, а затем Arial. В CSS font-family: Lobster, 'Helvetica Neue', Arial
.
Импортируйте createFontStack из основного пакета:
import { createFontStack } from '@capsizecss/core';
Импортируйте метрики шрифта для каждого из нужных шрифтов (см. «Метрики шрифтов» выше):
javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`
Создайте свой стек шрифтов, передав метрики в виде массива, используя тот же порядок, что и при использовании свойства CSS Font-Family.
javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);
Это возвращает следующее:
{
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%;
}
}
]
}
Вы должны добавить код FontFamily и FontFaces в свой CSS. Следующий код показывает, как реализовать его в таблице стилей CSS или в блоке <style>
.
<style type="text/css">
.heading {
font-family:
}
</style>
Это создаст следующий 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%;
}
Вы также можете использовать пакет @capsize/metrics для расчета значений переопределения и самостоятельно применить их к 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));