Как избавиться от прыгающего скроллбара

Разбираем все CSS-способы устранения сдвига макета при появлении вертикальной полосы прокрутки.

  • css

Появление или исчезновение вертикальной полосы прокрутки — одна из самых распространённых причин неожиданного сдвига интерфейса. Особенно заметно это при открытии модальных окон, переходах между страницами разной высоты или динамической подгрузке контента.

В этой статье рассмотрим основные CSS-подходы, разберём их преимущества и недостатки, а затем посмотрим, какой из них лучше использовать в наиболее распространённых вариантах вёрстки.

Если проект использует сторонние библиотеки для модальных окон (например, Base UI, Radix UI, Mantine и другие), учитывайте, что многие из них уже самостоятельно компенсируют исчезновение полосы прокрутки. При использовании собственных CSS-решений такую компенсацию обычно приходится отключать, иначе возможны двойные отступы и конфликтующее поведение.

Cпособы решения

JavaScript позволяет реализовать наиболее универсальную компенсацию ширины скроллбара и полностью контролировать её поведение. Однако подобное решение сложнее в сопровождении, вмешивается в нативную работу браузера и подходит далеко не каждому проекту. Поэтому далее будут рассмотрены только CSS-подходы.

Фиксация ширины viewport

По умолчанию ширина корневого элемента (100%) не включает вертикальную полосу прокрутки, из-за чего при её появлении ширина страницы уменьшается. Здесь вместо 100% используется 100vw, которое всегда учитывает всю ширину viewport вместе со скроллбаром. В результате полоса прокрутки начинает накладываться поверх содержимого, имитируя поведение большинства мобильных браузеров, где она не влияет на ширину страницы.

Недостатки:

  • Вертикальная полоса прокрутки перекрывает правый край страницы.
  • Элементы, прижатые к правой границе могут оказаться под скроллбаром.

Резервирование места под скроллбар

По умолчанию место под вертикальную полосу прокрутки выделяется только после её появления. Правило scrollbar-gutter: stable заставляет браузер резервировать это пространство заранее, поэтому ширина страницы больше не изменяется.

Недостатки:

  • Не поддерживается старыми браузерами.
  • На страницах без прокрутки справа остаётся пустая область.
  • Центр страницы визуально смещается влево.

Постоянный скроллбар

По умолчанию вертикальная полоса прокрутки отображается только при необходимости. Здесь она отображается постоянно, поэтому браузер всегда рассчитывает страницу с учётом её ширины.

Недостатки:

  • На страницах без прокрутки отображается пустой трек скроллбара.
  • Не устраняет сдвиги при открытии модальных окон, если прокрутка отключается через overflow: hidden.
  • Внешний вид пустого трека зависит от операционной системы и может выбиваться из дизайна сайта.

Компенсация ширины скроллбара

По умолчанию при появлении вертикальной полосы прокрутки центр страницы смещается влево на её ширину. Здесь вычисляется разница между полной шириной viewport (100vw) и текущей шириной документа (100%), после чего она компенсируется левым отступом, сохраняя положение центральной оси.

Недостатки:

  • Смещает элементы, жёстко привязанные к левому краю окна.
  • Обычно требует ограничения через медиазапросы, чтобы не влиять на узкие экраны.

Поведение в классических макетах

Единого универсального решения не существует. Каждый способ имеет свои ограничения и подходит только для определённых типов макетов. Рассмотрим наиболее распространённые сценарии.

Полноширинный заголовок с контейнером контента в центре

Шапка занимает всю ширину окна браузера, а её содержимое ограничено центрированным контейнером.

Лучшее решение: фиксация ширины viewport.

В этом макете горизонтальная прокрутка не используется, поэтому наложение вертикального скроллбара на правый край страницы не создаёт проблем. При этом полноширинный фон шапки остаётся неподвижным независимо от появления или исчезновения полосы прокрутки.

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

Центрированный заголовок

И шапка, и основное содержимое ограничены контейнером фиксированной ширины (например, max-width: 1280px) и всегда располагаются по центру страницы.

Лучшее решение: компенсация ширины скроллбара.

В этом макете важно сохранить положение общей центральной оси контейнера. Компенсация автоматически добавляет слева отступ, равный ширине скроллбара, поэтому центрированные элементы остаются на одном месте независимо от появления или исчезновения полосы прокрутки.

Использовать фиксацию ширины viewport здесь обычно нецелесообразно. Она тоже устраняет скачки, но заставляет скроллбар накладываться поверх правой части страницы, хотя необходимости в этом для такого макета нет.

Сайдбар слева

Сайдбар закреплён у левой границы окна браузера, а прокручивается только основная область контента.

Лучшее решение: фиксация ширины viewport.

Поскольку браузер всегда рассчитывает страницу с учётом ширины полосы прокрутки, положение сайдбара остаётся неизменным. Он продолжает быть жёстко привязанным к левому краю окна независимо от высоты страницы.

Использовать компенсацию ширины скроллбара в этом случае не стоит. Добавляемый слева отступ смещает весь макет, из-за чего сайдбар перестаёт быть прижатым к краю окна как в самом первом примере.

Центрированный сайдбар

Сайдбар и основное содержимое находятся внутри общего центрированного контейнера.

Лучшее решение: компенсация ширины скроллбара.

Способ сохраняет положение центрированного контейнера при изменении высоты страницы. Поскольку сайдбар не привязан к краю окна браузера, дополнительный отступ не нарушает геометрию макета.


Итоги

Универсального способа устранить прыгающий скроллбар не существует. Каждый из рассмотренных подходов решает проблему по-своему и имеет собственные ограничения.

При выборе решения важно учитывать не только отсутствие сдвигов, но и особенности макета, наличие фиксированных элементов, поддержку целевых браузеров и взаимодействие со сторонними библиотеками.

Если архитектура проекта позволяет, стоит подобрать решение один раз и использовать его последовательно на всех страницах. Это поможет избежать конфликтов между разными подходами и обеспечит предсказуемое поведение интерфейса.

Дополнительные материалы по теме: