Загрузка шрифтов в вебе: чек-лист против сдвигов и лагов
Шрифты решают, увидит ли пользователь текст сразу или пустое место на 3 секунды. Разбираем font-display, preload, Font Loading API и метрики fallback — что выбрать под конкретную задачу.

Кастомный шрифт весит 20–150 КБ на начертание. Пока браузер его качает, у вас есть выбор из двух зол: показать невидимый текст (FOIT — Flash of Invisible Text) или показать системный шрифт, а потом дёрнуть весь текст при подмене (FOUT — Flash of Unstyled Text). Плюс третья беда — сдвиг макета (CLS), когда fallback и кастомный шрифт имеют разную ширину символов. Ниже — приёмы, которые этими тремя проблемами управляют, и таблица, когда что выбирать.
Речь о вебе состоянием на 2024 год: свойство font-display и size-adjust в @font-face поддерживаются всеми актуальными браузерами, Font Loading API — тоже. Формат шрифтов — WOFF2, остальные форматы для современных браузеров не нужны.
Базовый @font-face: без чего всё остальное бессмысленно
Стартовая точка — правильный @font-face. Три ошибки здесь встречаются чаще всего: перечисление устаревших форматов, отсутствие font-display и загрузка всех начертаний, хотя на странице используется два.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap; /* показать fallback сразу, подменить при загрузке */
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter-bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
WOFF2 покрывает все браузеры, которые вообще стоит поддерживать. TTF, EOT и SVG-шрифты выкидывайте: они больше весят и нужны были ради IE и старых Android.
Что делает font-display
Свойство управляет тем, как браузер ведёт себя, пока шрифт грузится. Значения:
- auto — на усмотрение браузера, обычно поведение как у
block. Не полагайтесь на него. - block — короткий период невидимого текста (около 3 с), потом fallback. Это FOIT.
- swap — fallback показывается сразу, подменяется при загрузке. Это FOUT, но текст виден мгновенно.
- fallback — компромисс: очень короткий блок (~100 мс), затем fallback; если шрифт не успел за ~3 с, он больше не применяется до перезагрузки.
- optional — браузер сам решает по скорости сети, применять ли шрифт вообще. Нулевой CLS ценой того, что на медленной сети кастомный шрифт может не показаться.
Для основного текста в большинстве проектов подходит swap: пользователь читает сразу. Для декоративных заголовков, где важнее внешний вид, чем мгновенность, — optional или fallback.
preload: ускорить загрузку критичного шрифта
Браузер узнаёт о шрифте только после того, как разберёт CSS и найдёт правило @font-face, применённое к видимому элементу. Это поздно. <link rel="preload"> в <head> говорит начать загрузку сразу.
<link
rel="preload"
href="/fonts/inter-regular.woff2"
as="font"
type="font/woff2"
crossorigin>
Атрибут crossorigin обязателен, даже если шрифт на том же домене: шрифты грузятся в анонимном CORS-режиме, и без этого атрибута браузер скачает файл дважды.
Preload — острый инструмент. Прелоадите только те 1–2 файла, что нужны в первом экране. Прелоад пяти начертаний отнимает канал у по-настоящему критичных ресурсов и делает страницу медленнее, а не быстрее.
Борьба со сдвигом макета: size-adjust и метрики fallback
При swap текст сначала рисуется системным шрифтом, потом кастомным. Если у них разная ширина и высота строки, макет прыгнет — это и есть основной вклад шрифтов в CLS. Решение — подогнать fallback под метрики целевого шрифта дескрипторами в отдельном @font-face.
/* fallback, подогнанный под метрики Inter */
@font-face {
font-family: "Inter fallback";
src: local("Arial");
size-adjust: 107%; /* масштаб глифов */
ascent-override: 90%; /* высота над базовой линией */
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: "Inter", "Inter fallback", sans-serif;
}
Точные проценты подбираются под конкретную пару «кастомный шрифт + системный fallback». Их не нужно считать вручную: есть открытый инструмент Fontaine и утилита от команды Google, которые генерируют дескрипторы автоматически. Проверьте актуальные проценты для вашего шрифта — вбитые в пример значения для Inter приблизительны.
Font Loading API: контроль из JavaScript
Когда нужно не просто загрузить шрифт, а среагировать на момент загрузки — показать контент, снять класс, отрисовать canvas текстом в правильном шрифте, — используют document.fonts (интерфейс FontFaceSet).
// дождаться готовности всех объявленных шрифтов
document.fonts.ready.then(() => {
document.documentElement.classList.add("fonts-loaded");
});
// загрузить конкретное начертание программно
async function loadInter() {
const inter = new FontFace(
"Inter",
"url(/fonts/inter-regular.woff2)",
{ weight: "400", display: "swap" }
);
await inter.load();
document.fonts.add(inter);
}
document.fonts.ready — это промис, который резолвится, когда завершилась загрузка всех шрифтов, запрошенных к текущему моменту. Он не ждёт шрифты, которые понадобятся позже. Метод document.fonts.check("12px Inter") вернёт, доступен ли шрифт прямо сейчас, — полезно перед отрисовкой в canvas, где системный fallback испортит картинку.
Класс на html вместо swap
Приём «two-stage render»: пока шрифт грузится, применяется fallback без класса; после загрузки JS вешает .fonts-loaded на <html>, и CSS переключает font-family. Это даёт больше контроля, чем font-display, но добавляет зависимость от JS: без него кастомный шрифт не применится. Для основного текста надёжнее CSS-путь через font-display.
Когда что выбирать
| Задача | Приём | Цена |
|---|---|---|
| Текст должен читаться мгновенно | font-display: swap + preload одного начертания | Возможен FOUT-мигание при подмене |
| Минимальный CLS любой ценой | font-display: optional | На медленной сети шрифт может не показаться |
| Убрать сдвиг при swap | size-adjust + *-override в fallback @font-face | Подбор дескрипторов под каждую пару шрифтов |
| Отрисовка текста в canvas | document.fonts.load + check перед отрисовкой | Зависимость от JS |
| Отложить некритичные начертания | Без preload, обычный @font-face | Второстепенный текст мигнёт позже |
Чек-лист перед деплоем
- Все шрифты в WOFF2, лишние форматы удалены.
- У каждого
@font-faceзаданfont-displayосознанно, а не оставленauto. - Прелоадится не больше 1–2 начертаний первого экрана, у
<link rel=preload>стоитcrossorigin. - Fallback в
font-familyподогнан по метрикам (size-adjust+ overrides), CLS замерен. - Если шрифты грузятся из JS — проверено, что при отключённом JS страница читаема системным шрифтом.
- Шрифт сабсетится: убраны неиспользуемые языки и глифы (кириллица+латиница вместо всех юникод-блоков режет вес в разы).
Что мерить и что дальше
Откройте Lighthouse и посмотрите на CLS и на аудит «Ensure text remains visible during webfont load» — он ловит отсутствие font-display. В DevTools на вкладке Network отфильтруйте по типу Font: увидите порядок и время загрузки, дубли из-за забытого crossorigin, лишние начертания. Метрику CLS в проде снимайте через web-vitals или поле CrUX, а не только в лаборатории — на реальных сетях картина другая.
Следующий шаг — сабсеттинг через fonttools (pyftsubset) и, если поддерживаете много языков, разбивка шрифта на unicode-range диапазоны, чтобы браузер качал только нужный. Инвалидация кеша шрифтов через хеш в имени файла — отдельная тема, здесь её не разворачиваем.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: MDN: font-display, MDN: CSS Font Loading API
Частые вопросы
У меня старая сборка со шрифтами в TTF и WOFF, надо ли что-то менять?
font-display: swap стоит, а текст всё равно мигает и прыгает. Что не так?
Preload не ускорил загрузку, а Lighthouse ругается на дубль. В чём дело?
document.fonts.ready резолвится, но нужный шрифт всё равно не готов. Почему?
Стоит ли переключать шрифты через класс на html вместо font-display?
Как сильно сабсеттинг уменьшает вес шрифта?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.