Impedir que a página abaixo de uma caixa de diálogo modal seja rolada

Quando um elemento <dialog> é aberto como um modal usando showModal(), o navegador o promove para a camada superior e marca o restante do documento como inerte. Embora isso bloqueie os cliques do ponteiro e o foco do teclado na página, não impede a rolagem:

  1. Rolar enquanto o ponteiro ou o gesto de toque está sobre o ::backdrop rola a página abaixo.
  2. A rolagem dentro do <dialog> é encadeada à página de fundo quando a caixa de diálogo atinge o limite de rolagem ou imediatamente se o conteúdo da caixa de diálogo não transborda.

De acordo com a especificação do comportamento de rolagem esticada do CSS, overscroll-behavior se aplica a todos os elementos de contêiner de rolagem, incluindo contêineres de rolagem não roláveis (elementos com overflow: hidden ou overflow: auto cujo conteúdo não transborda no momento). Ao combinar overscroll-behavior: contain no <dialog> com overflow: hidden e overscroll-behavior: contain no dialog::backdrop, é possível impedir que a página de fundo seja rolada usando CSS puro.

Implementação

1. Contém encadeamento de rolagem no <dialog>

A folha de estilo do user agent define overflow: auto em <dialog>, tornando-o um contêiner de rolagem por padrão. Aplique overscroll-behavior: contain (ou none) a dialog:modal (ou dialog).

2. Transformar ::backdrop em um contêiner de rolagem e conter a rolagem dele

Por padrão, dialog::backdrop tem overflow: visible, o que significa que não é um contêiner de rolagem e ignora overscroll-behavior. É necessário definir overflow: hidden em dialog::backdrop para que ele seja um contêiner de rolagem não rolável e pareá-lo com overscroll-behavior: contain para que os gestos de rolagem realizados no plano de fundo sejam capturados e não alcancem o documento abaixo.

Exemplo de código

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;
}

Práticas recomendadas e restrições

  • OBRIGATÓRIO: defina overscroll-behavior: contain no próprio elemento <dialog> (usando o seletor dialog:modal) e no dialog::backdrop. Sem essa declaração, os gestos de rolagem sobre o plano de fundo ou dentro da caixa de diálogo ainda vão ser encadeados à página.
  • OBRIGATÓRIO: defina overflow: hidden (ou overflow: auto) em dialog::backdrop para que ele se torne um contêiner de rolagem. Sem essa declaração, ::backdrop assume como padrão overflow: visible, e overscroll-behavior não tem efeito.
  • NÃO use overflow: clip ou overflow: visible em dialog::backdrop ou dialog. Nenhum dos valores estabelece um contêiner de rolagem, então overscroll-behavior não será aplicado.
  • NÃO aplique overflow: hidden ou position: fixed a <html> ou <body> para bloquear a rolagem em segundo plano quando uma caixa de diálogo for aberta. A mutação do overflow raiz remove a barra de rolagem da página (causando uma mudança de layout horizontal), pode redefinir ou pular a posição de rolagem da página e pode quebrar elementos position: sticky na página em questão.

Estratégias de fallback

Embora a propriedade CSS overscroll-behavior seja compatível com todos os principais navegadores para contêineres de rolagem com conteúdo transbordante, respeitar overscroll-behavior em contêineres de rolagem não roláveis é uma especificação mais recente que não pode ser detectada por recursos.

NÃO tente detectar o suporte a contêineres não roláveis com @supports (overscroll-behavior: contain) ou CSS.supports('overscroll-behavior', 'contain'). Todos os navegadores mais recentes são compatíveis com a sintaxe da propriedade overscroll-behavior: contain e retornam true, mesmo que só a apliquem quando o conteúdo transborda.

Em vez disso, trate a contenção de rolagem em contêineres não roláveis como um aprimoramento progressivo:

  • Em navegadores que oferecem suporte a overscroll-behavior em contêineres de rolagem não roláveis, a rolagem da página em segundo plano é totalmente impedida tanto em dialog::backdrop quanto dentro de <dialog> (independentemente de o conteúdo transbordar ou não).
  • Em navegadores que ainda não oferecem suporte a overscroll-behavior em contêineres de rolagem não roláveis, overscroll-behavior: contain em dialog ainda impede o encadeamento de rolagem sempre que o conteúdo da caixa de diálogo transborda e é rolável. Enquanto isso, showModal() ainda captura o foco do teclado e marca o documento em segundo plano como inert.
  • É recomendável usar o aprimoramento progressivo em vez de soluções legadas que aplicam overflow: hidden ou position: fixed a <html> ou <body>, porque ele não remove a barra de rolagem raiz, não causa mudanças de layout horizontal, não pula a posição de rolagem da página nem quebra elementos position: sticky na página.