Пока веб-шрифт грузится, браузер показывает текст системным шрифтом — это правильно, текст должен быть виден сразу. Но системный шрифт другой ширины: строки переносятся иначе, блоки меняют высоту. Когда приезжает настоящий шрифт, страница перестраивается. В Chrome это часто незаметно, а на iPhone страница «ползёт» вниз прямо под пальцем.

Это девятнадцатая часть цикла «PageSpeed без мифов».

Насколько разные шрифты

Мы замерили одно и то же слово в настоящем шрифте сайта и в запасном Arial: «Прицеп» — 417,5 px против 349,3 px. Длинная строка — 673,5 против 584,9. Часть названий товаров в запасном шрифте умещалась в две строки вместо трёх, и при подмене шрифта страница вырастала на 142 px.

Почему в Chrome не видно, а на iPhone видно. У Chrome есть scroll anchoring: если контент выше экрана меняет высоту, он сдвигает прокрутку так, чтобы видимая часть осталась на месте. У Safari этого нет — страница едет. Поэтому жалобы на «прыгающую» страницу часто приходят только от владельцев айфонов, а CLS в отчёте при этом нулевой.

Лечение: запасной шрифт с метриками настоящего

CSS позволяет объявить «запасной шрифт», который на самом деле системный, но растянут и подогнан по метрикам под настоящий. Тогда текст в нём занимает почти столько же места, и подмена не двигает вёрстку.

@font-face {
  font-family: "Montserrat Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Helvetica"), local("Liberation Sans");
  size-adjust: 115%;        /* отношение ширины настоящего шрифта к запасному */
  ascent-override: 84.2%;   /* метрика настоящего шрифта (96.8%), делённая на size-adjust */
  descent-override: 21.8%;  /* (25.1%) / 1.15 */
  line-gap-override: 0%;
}

body {
  font-family: "Montserrat", "Montserrat Fallback", sans-serif;
}

Главная грабля: ascent и descent делятся на size-adjust. Проценты ascent-override и descent-override применяются к уже увеличенному шрифту. Если подставить метрики настоящего шрифта как есть, запасной окажется выше настоящего: у нас страница стала на 94 px выше, вместо того чтобы перестать прыгать.

Откуда брать числа

  • size-adjust — отношение ширины одной и той же строки в двух шрифтах. Проще всего замерить в браузере через canvas (код ниже).
  • ascent и descent — метрики настоящего шрифта (их показывают fontTools или онлайн-инструменты для генерации запасных шрифтов), делённые на size-adjust.
// во сколько раз настоящий шрифт шире запасного — это и есть size-adjust
const ctx = document.createElement("canvas").getContext("2d");
const text = "Съешь же ещё этих мягких французских булок";
ctx.font = "16px Montserrat";  const real = ctx.measureText(text).width;
ctx.font = "16px Arial";       const fallback = ctx.measureText(text).width;
console.log("size-adjust:", (real / fallback * 100).toFixed(1) + "%");

Как проверить, что помогло

  1. Откройте страницу с заблокированными файлами шрифтов (DevTools → Network → блокировка *.woff2) и запомните высоту документа: document.body.scrollHeight.
  2. Откройте без блокировки и сравните. До правки разница была 142 px, после — должна стать близкой к нулю.
  3. Проверьте на телефоне: в iPhone Safari страница больше не должна сдвигаться при загрузке.

Ограничения

  • Safari понимает size-adjust только с 17-й версии. На старых iPhone эффект будет меньше.
  • font-display: optional эту проблему не решает: перевёрстка при подмене шрифта происходит и с ним. Это мы проверили — и записали в список «не помогает» (четырнадцатая часть).

Страница «ползёт» на iPhone?

Пришлите адрес — замерю, насколько меняется вёрстка при подмене шрифта, и подгоню запасной шрифт.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.