Как запретить прокрутку страницы под модальным диалоговым окном

Когда элемент <dialog> открывается как модальное окно с помощью showModal(), браузер перемещает его на верхний слой и помечает остальную часть документа как инертную. Хотя это блокирует клики указателя и фокус клавиатуры на базовой странице, это не предотвращает прокрутку:

  1. Если пользователь прокручивает страницу, когда указатель или жест касания находится над элементом ::backdrop, то прокручивается страница под ним.
  2. Прокрутка внутри <dialog> переходит на основную страницу, когда диалоговое окно достигает границы прокрутки, или сразу, если контент диалогового окна не переполнен.

Согласно спецификации CSS Overscroll Behavior, свойство overscroll-behavior применяется ко всем элементам контейнера прокрутки, включая непрокручиваемые контейнеры прокрутки (элементы с overflow: hidden или overflow: auto, содержимое которых в настоящее время не переполняется). Объединив overscroll-behavior: contain в <dialog> с overflow: hidden и overscroll-behavior: contain в dialog::backdrop, вы можете запретить прокрутку основной страницы, используя только CSS.

Реализация

1. Ограничьте цепочку прокрутки в элементе <dialog>

Таблица стилей агента пользователя устанавливает overflow: auto для <dialog>, делая его контейнером прокрутки по умолчанию. Примените overscroll-behavior: contain (или none) к dialog:modal (или dialog).

2. Как превратить ::backdrop в контейнер прокрутки и ограничить прокрутку

По умолчанию для dialog::backdrop задано значение overflow: visible, то есть это не контейнер прокрутки и overscroll-behavior игнорируется. Вам нужно установить overflow: hidden для dialog::backdrop, чтобы сделать его нескроллящимся контейнером прокрутки, и связать его с overscroll-behavior: contain, чтобы жесты прокрутки, выполняемые над фоном, перехватывались и не достигали документа под ним.

Пример кода

dialog:modal {
  /* MANDATORY: Prevent scroll gestures inside the modal (even when its content
     does not overflow) from chaining to the underlying page. */
  overscroll-behavior: contain;
}

dialog::backdrop {
  /* MANDATORY: ::backdrop defaults to overflow: visible (not a scroll container).
     Set overflow: hidden so it becomes a non-scrollable scroll container that
     honors overscroll-behavior. */
  overflow: hidden;
  overscroll-behavior: contain;
}

Рекомендации и ограничения

  • ОБЯЗАТЕЛЬНО. Задайте overscroll-behavior: contain для самого элемента <dialog> (с помощью селектора dialog:modal) и dialog::backdrop. Без этого объявления жесты прокрутки на фоне или внутри диалогового окна по-прежнему будут привязаны к странице.
  • ОБЯЗАТЕЛЬНО. Задайте для overflow: hidden (или overflow: auto) значение dialog::backdrop, чтобы сделать его контейнером прокрутки. Без этой декларации ::backdrop по умолчанию принимает значение overflow: visible, а overscroll-behavior не действует.
  • НЕ используйте overflow: clip или overflow: visible на dialog::backdrop или dialog. Ни одно из значений не создает контейнер прокрутки, поэтому overscroll-behavior не будет применяться.
  • НЕ применяйте overflow: hidden или position: fixed к <html> или <body>, чтобы заблокировать прокрутку фона при открытии диалогового окна. Изменение переполнения корневого элемента удаляет полосу прокрутки страницы (что приводит к горизонтальному сдвигу макета), может сбросить или изменить позицию прокрутки страницы и нарушить работу элементов position: sticky на базовой странице.

Резервные стратегии

Хотя свойство CSS overscroll-behavior поддерживается во всех основных браузерах для контейнеров прокрутки с переполненным контентом, соблюдение overscroll-behavior для непрокручиваемых контейнеров прокрутки – это более новая спецификация, которую невозможно обнаружить.

НЕ пытайтесь определить поддержку нескроллящихся контейнеров с помощью @supports (overscroll-behavior: contain) или CSS.supports('overscroll-behavior', 'contain'). Все современные браузеры поддерживают синтаксис свойства overscroll-behavior: contain и возвращают true, даже если они применяют его только при переполнении контента.

Вместо этого рассматривайте изоляцию прокрутки в непрокручиваемых контейнерах как прогрессивное улучшение:

  • В браузерах, поддерживающих overscroll-behavior для нескроллящихся контейнеров, прокрутка фоновой страницы полностью блокируется как над dialog::backdrop, так и внутри <dialog> (независимо от того, переполнен ли его контент).
  • В браузерах, которые пока не поддерживают overscroll-behavior для непрокручиваемых контейнеров, overscroll-behavior: contain для dialog по-прежнему предотвращает цепочку прокрутки, когда контент диалогового окна переполняется и его можно прокручивать. При этом showModal() по-прежнему захватывает фокус клавиатуры и помечает фоновый документ как inert.
  • Рекомендуется использовать прогрессивное улучшение, а не устаревшие обходные решения, в которых overflow: hidden или position: fixed применяется к <html> или <body>, поскольку оно не удаляет корневую полосу прокрутки, не вызывает горизонтальные сдвиги макета, не приводит к переходу к позиции прокрутки страницы и не нарушает работу элементов position: sticky на базовой странице.