Controle sua rolagem, personalizando efeitos de puxar para atualizar e estourar

Texto longo, leia o resumo

A propriedade CSS overscroll-behavior permite que os desenvolvedores modifiquem o comportamento de rolagem flutuante padrão do navegador ao alcançar a parte de cima/baixo do conteúdo. Os casos de uso incluem desativar o recurso "puxar para atualizar" em dispositivos móveis, remover o brilho de rolagem e os efeitos de borracha e impedir que o conteúdo da página seja rolado quando estiver sob uma modal/sobreposição.

Contexto

Limites e encadeamento de rolagem

Encadeamento de rolagem no Chrome para Android.

A rolagem é uma das maneiras mais fundamentais de interagir com uma página, mas certos padrões de UX podem ser complicados de lidar devido aos comportamentos padrão peculiares do navegador. Por exemplo, vamos usar uma gaveta de apps com um grande número de itens que o usuário pode precisar rolar. Quando eles chegam à parte de baixo, o contêiner flutuante para de rolar a tela porque não há mais conteúdo para consumir. Em outras palavras, o usuário atinge um "limite de rolagem". Mas observe o que acontece se o usuário continuar rolando. O conteúdo atrás da gaveta começa a rolar. A rolagem é assumida pelo contêiner pai, a própria página principal no exemplo.

Esse comportamento é chamado de encadeamento de rolagem, que é o comportamento padrão do navegador ao rolar conteúdo. Muitas vezes, o padrão é muito bom, mas às vezes não é desejável nem inesperado. Alguns apps podem querer oferecer uma experiência diferente quando o usuário atinge um limite de rolagem.

O efeito "puxar para atualizar"

"puxar para atualizar" é um gesto intuitivo e conhecido por apps para dispositivos móveis, como Facebook e Twitter. Puxar para baixo em um feed de redes sociais e liberar cria espaço para que as postagens mais recentes sejam carregadas. Na verdade, essa UX específica se tornou tão conhecida que navegadores para dispositivos móveis, como o Chrome no Android, tiveram o mesmo efeito. Deslizar para baixo no topo da página atualiza toda a página:

Deslizar para atualizar personalizado do Twitter
ao atualizar um feed no PWA.
A ação nativa de "puxar para atualizar" do Chrome para Android
atualiza toda a página.

Para situações como o PWA do Twitter, pode ser útil desativar a ação nativa de "puxar para atualizar". Por quê? Neste app, é provável que você não queira que o usuário atualize a página acidentalmente. Também é possível ver uma animação de atualização dupla. Como alternativa, pode ser melhor personalizar a ação do navegador, alinhando-o mais de perto à marca do site. O lado negativo é que esse tipo de personalização é complicado de fazer. Os desenvolvedores acabam escrevendo JavaScript desnecessário, adicionam listeners de toque não passivos (que bloqueiam a rolagem) ou mantêm a página inteira em um <div> de 100 vw/vh (para evitar que a página estoure). Essas soluções alternativas têm efeitos negativos bem documentados sobre o desempenho da rolagem.

Podemos melhorar!

Conheça o overscroll-behavior

A propriedade overscroll-behavior é um novo recurso CSS que controla o comportamento do que acontece quando você rola a tela acima de um contêiner (incluindo a própria página). É possível usá-lo para cancelar o encadeamento de rolagem, desativar/personalizar a ação de arrastar para atualizar, desativar os efeitos de borracha no iOS (quando o Safari implementa overscroll-behavior) e muito mais. A melhor parte é que usar overscroll-behavior não afeta negativamente o desempenho da página, como as dicas mencionadas na introdução.

A propriedade aceita três valores possíveis:

  1. auto: padrão. Os rolagens que se originam no elemento podem se propagar para elementos ancestrais.
  2. contain: evita o encadeamento de rolagem. As rolagens não se propagam para os ancestrais, mas os efeitos locais dentro do nó são mostrados. Por exemplo, o efeito de brilho de rolagem no Android ou o efeito de borracha no iOS, que notifica o usuário quando ele atinge um limite de rolagem. Observação: o uso de overscroll-behavior: contain no elemento html impede ações de navegação de rolagem.
  3. none: o mesmo que contain, mas também impede efeitos de rolagem dentro do próprio nó (por exemplo, brilho de rolagem do Android ou borracha de borracha do iOS).

Vamos conferir alguns exemplos para aprender a usar overscroll-behavior.

Impedir que as rolagens escapem de um elemento de posição fixa

O cenário da caixa de chat

O conteúdo abaixo da janela do chat também rola :(

Considere uma caixa de chat fixa, posicionada na parte de baixo da página. A intenção é que a caixa de chat seja um componente independente e que ela role separadamente do conteúdo atrás dela. No entanto, devido ao encadeamento de rolagem, o documento começa a rolar assim que o usuário clica na última mensagem no histórico de chat.

Para este app, é mais apropriado que as rolagens originadas na caixa de chat permaneçam dentro do chat. Podemos fazer isso adicionando overscroll-behavior: contain ao elemento que contém as mensagens do chat:

#chat .msgs {
  overflow: auto;
  overscroll-behavior: contain;
  height: 300px;
}

Essencialmente, estamos criando uma separação lógica entre o contexto de rolagem da caixa de chat e a página principal. O resultado final é que a página principal permanece no mesmo lugar quando o usuário chega ao topo/fim do histórico de chat. As rolagens que começam na caixa de chat não são propagadas para fora.

Cenário de sobreposição de página

Outra variação do cenário de "underscroll" é quando há uma rolagem de conteúdo por trás de uma sobreposição de posição fixa. Você acabou de ganhar um sorteio de overscroll-behavior! O navegador está tentando ser útil, mas acaba fazendo o site parecer com bugs.

Exemplo - modal com e sem overscroll-behavior: contain:

Antes: o conteúdo da página rola abaixo da sobreposição.
Depois: o conteúdo da página não rola abaixo da sobreposição.

Como desativar o recurso "puxar para atualizar"

A desativação da ação de arrastar para atualizar é uma única linha de CSS. Basta impedir o encadeamento de rolagem em todo o elemento de definição da janela de visualização. Na maioria dos casos, é <html> ou <body>:

body {
  /* Disables pull-to-refresh but allows overscroll glow effects. */
  overscroll-behavior-y: contain;
}

Com essa adição simples, corrigimos as animações duplas de arrastar para baixo para atualizar na demonstração da caixa de chat e podemos implementar um efeito personalizado que usa uma animação de carregamento mais organizada. A caixa de entrada inteira também é desfocada à medida que ela é atualizada:

Antes
Depois

Confira um snippet do código completo:

<style>
  body.refreshing #inbox {
    filter: blur(1px);
    touch-action: none; /* prevent scrolling */
  }
  body.refreshing .refresher {
    transform: translate3d(0,150%,0) scale(1);
    z-index: 1;
  }
  .refresher {
    --refresh-width: 55px;
    pointer-events: none;
    width: var(--refresh-width);
    height: var(--refresh-width);
    border-radius: 50%;
    position: absolute;
    transition: all 300ms cubic-bezier(0,0,0.2,1);
    will-change: transform, opacity;
    ...
  }
</style>

<div class="refresher">
  <div class="loading-bar"></div>
  <div class="loading-bar"></div>
  <div class="loading-bar"></div>
  <div class="loading-bar"></div>
</div>

<section id="inbox"><!-- msgs --></section>

<script>
  let _startY;
  const inbox = document.querySelector('#inbox');

  inbox.addEventListener('touchstart', e => {
    _startY = e.touches[0].pageY;
  }, {passive: true});

  inbox.addEventListener('touchmove', e => {
    const y = e.touches[0].pageY;
    // Activate custom pull-to-refresh effects when at the top of the container
    // and user is scrolling up.
    if (document.scrollingElement.scrollTop === 0 && y > _startY &&
        !document.body.classList.contains('refreshing')) {
      // refresh inbox.
    }
  }, {passive: true});
</script>

Como desativar o brilho de rolagem e os efeitos de borracha

Para desativar o efeito de rejeição ao atingir um limite de rolagem, use overscroll-behavior-y: none:

body {
  /* Disables pull-to-refresh and overscroll glow effect.
     Still keeps swipe navigations. */
  overscroll-behavior-y: none;
}
Antes: quando você atinge o limite de rolagem, um brilho aparece.
Depois: brilho desativado.

Demonstração completa

Juntando tudo, a demonstração da caixa de chat completa usa overscroll-behavior para criar uma animação personalizada de deslizar para baixo para atualizar e impedir que as rolagens saiam do widget da caixa de chat. Isso oferece uma experiência do usuário ideal que seria difícil de alcançar sem o CSS overscroll-behavior.

Veja a demonstração | Fonte