Почему цифры прыгают, когда меняются — таймер дёргается, а текст рядом ездит

Ты сделал таймер обратного отсчёта. Цифры честно меняются: 0:19, 0:18, 0:17… И вся строка мелко дрожит. Кнопка справа ездит туда-сюда на пару пикселей. Каждую секунду.
С кодом всё в порядке. Дело в шрифте, и вот неожиданная часть: в большинстве современных шрифтов единица уже, чем ноль. «1:11» физически короче, чем «0:00». Когда меняется цифра, меняется ширина строки — и всё вокруг сдвигается.
Ниже три причины, от самой частой к редкой. У каждой — как проверить и как починить.
Причина 1. Пропорциональные цифры
Шрифтовые дизайнеры рисуют цифры двумя способами:
- пропорциональные — у каждой своя ширина, как у букв. «1» узкая, «0» широкая. Красиво в тексте: «в 1990 году» не выглядит дырявым;
- табличные — все цифры одной ширины, как клетки. Скучнее в тексте, зато столбики и таймеры стоят как вкопанные.
Многие шрифты по умолчанию дают пропорциональные. Системный шрифт Apple — тоже. Поэтому на айфоне таймер дрожит особенно заметно.
Как проверить. Поставь рядом два блока: <span>111111</span> и <span>000000</span>. Разная длина — значит, цифры пропорциональные.
Как починить. Одна строка CSS:
.timer {
font-variant-numeric: tabular-nums;
}
Она просит у шрифта табличный набор цифр. Шрифт его достаёт, и все цифры становятся одной ширины. Буквы при этом не трогаются — только цифры.
Это стоит ставить везде, где число меняется на глазах или стоит в столбик: таймеры, счётчики, цены в таблице, проценты загрузки, рейтинги, баланс. В тексте статьи — не нужно, там пропорциональные цифры читаются лучше.
В Tailwind это класс tabular-nums. В SwiftUI — модификатор .monospacedDigit(). Поменял стиль, а ничего не изменилось? Сначала глянь «Почему CSS не работает» — возможно, правило просто не применилось.
Причина 2. Меняется количество цифр
Ты поставил tabular-nums, а прыжок всё равно есть. Но только иногда: на 9 → 10, на 99 → 100, на 59:59 → 1:00:00.
Табличные цифры одинаковой ширины. Но две цифры всё равно шире одной. Тут шрифт не поможет.
Как проверить. Прыгает только в момент, когда число «переходит разряд»? Это оно.
Как починить. Три варианта, выбирай по смыслу:
- Ведущие нули. Время пишут как «05:07», а не «5:7». В JS:
String(sec).padStart(2, "0"). Ширина постоянная, и читается привычно. - Минимальная ширина в
ch. Единицаch— это ширина символа «0» в текущем шрифте. С табличными цифрами она равна ширине любой цифры:
.counter {
display: inline-block; /* у обычного span ширина не задаётся */
font-variant-numeric: tabular-nums;
min-width: 4ch;
text-align: right;
}
Теперь до 9999 счётчик не меняет размер.
- Выравнивание по правому краю. Если ширина всё же растёт, пусть растёт влево. Число по центру расширяется в обе стороны и дрожит сильнее всего.
Причина 3. В шрифте нет табличных цифр
Самая коварная. Ты всё сделал правильно, а tabular-nums не дал никакого эффекта. Без ошибок, без предупреждений.
Свойство только просит шрифт. Если в шрифте табличного набора нет — браузер молча оставит как было. Так бывает у декоративных и рукописных шрифтов, у некоторых бесплатных и у шрифтов, где лишние возможности вырезали ради веса.
Как проверить. Тест из причины 1, но уже с tabular-nums: если «111111» и «000000» всё ещё разной длины — табличных цифр в шрифте нет. В DevTools во вкладке Computed внизу видно, каким шрифтом на самом деле отрисован текст. Иногда это вообще запасной шрифт, а не тот, что ты подключал.
Как починить.
- Для чисел возьми другой шрифт — с табличными цифрами. Обычно это указано на странице шрифта как
tnum. Многие вариативные шрифты его поддерживают. - Крайний случай — моноширинный шрифт только для цифр:
font-family: ui-monospace, monospace. Смотрится техничнее, но прыгать перестанет гарантированно.
Чеклист для любого живого числа
Перед тем как показать пользователю меняющееся число, пробеги по списку:
font-variant-numeric: tabular-numsстоит на самом элементе с числом.- Тест «111111 против 000000» проходит — длина одинаковая.
- Ширина рассчитана на самое длинное значение:
min-widthвchили ведущие нули. - Число выровнено вправо, а не по центру.
- Проверено на айфоне — там системный шрифт с пропорциональными цифрами.
Пять пунктов — и таймер стоит, как приклеенный.
Можно просто задать фиксированную ширину в пикселях?
Можно, но хрупко. Поменяется размер шрифта на телефоне или пользователь включит крупный текст — и число вылезет за рамку. Единица ch растёт вместе со шрифтом, пиксели — нет.
А если число анимируется, как в счётчике «0 → 1 000 000»?
Там прыгает всё сразу: и ширина цифр, и количество разрядов, и пробел между тысячами. Ставь tabular-nums, min-width под финальное число в ch и выравнивание вправо. Тогда анимация бежит на месте.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.


