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 substituam o comportamento padrão de rolagem flutuante do navegador ao chegar à parte de cima/de baixo do conteúdo. Os casos de uso incluem desativar o recurso de puxar a tela para atualizar em dispositivos móveis, remover efeitos de brilho de rolagem e elástico e impedir que o conteúdo da página role quando estiver abaixo de um modal/sobreposição.

Contexto

Limites de rolagem 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 alguns padrões de UX podem ser difíceis de lidar devido aos comportamentos padrão exclusivos do navegador. Por exemplo, considere uma gaveta de apps com um grande número de itens que o usuário pode precisar percorrer. Quando eles alcançam a parte inferior, o contêiner flutuante para de rolar 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 a rolar a tela. O conteúdo por trá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 o conteúdo. Muitas vezes, o padrão é muito bom, mas às vezes não é desejável ou até mesmo inesperado. Alguns apps podem querer fornecer uma experiência do usuário diferente quando ele atingir um limite de rolagem.

O efeito de arrastar para atualizar

Puxe para atualizar é um gesto intuitivo conhecido por apps para dispositivos móveis, como Facebook e Twitter. Arrastar para baixo em um feed de redes sociais e liberar cria um novo espaço para o carregamento de postagens mais recentes. Na verdade, essa UX em particular se tornou tão popular que navegadores para dispositivos móveis, como o Chrome no Android, adotaram o mesmo efeito. Deslizar para baixo no topo da página atualiza toda a página:

Puxar para atualizar personalizado do Twitter
ao atualizar um feed no PWA.
A ação nativa de arrastar para atualizar no Chrome Android
atualiza a página inteira.

Para situações como o PWA do Twitter, pode fazer sentido desativar a ação nativa de arrastar para atualizar. Por quê? Neste app, você provavelmente não quer 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-a mais à marca do site. A parte negativa é que esse tipo de personalização tem sido complicado. Os desenvolvedores acabam escrevendo JavaScript desnecessário, adicionam listeners de toque não passivos (que bloqueiam a rolagem) ou fixam a página inteira em um <div> 100vw/vh (para evitar o excesso de tempo da página). Essas soluções alternativas têm efeitos negativos bem documentados sobre o desempenho de rolagem.

Podemos melhorar!

Conheça o overscroll-behavior

A propriedade overscroll-behavior é um novo recurso de CSS que controla o comportamento do que acontece quando você rola a tela de um contêiner (incluindo a própria página). Ela pode ser usada 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 a performance da página, como as invasões mencionadas na introdução.

A propriedade usa três valores possíveis:

  1. auto: padrão. As rolagens que se originam no elemento podem se propagar para elementos ancestrais.
  2. contain: evita o encadeamento de rolagem. As rolagens não são propagadas para 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 elástico 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 no próprio nó (por exemplo, brilho de rolagem do Android ou elástico do iOS).

Vamos conferir alguns exemplos para saber como usar o overscroll-behavior.

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

O cenário do chatbox

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

Considere uma caixa de chat posicionada fixa na parte inferior da página. A intenção é que o chatbox seja um componente independente e que ele seja rolado separadamente do conteúdo por trás dele. No entanto, devido ao encadeamento de rolagem, o documento começa a rolar assim que o usuário atinge a última mensagem no histórico de chat.

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

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

Basicamente, 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 à parte superior/inferior do histórico de bate-papo. As rolagens que começam na caixa de chat não são propagadas.

O cenário de sobreposição de página

Outra variação do cenário "underscroll" é quando você vê o conteúdo rolando por trás de uma sobreposição de posição fixa. Está tudo pronto para um sorteio overscroll-behavior. O navegador está tentando ser útil, mas acaba fazendo o site parecer bug.

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 do CSS. Basta evitar 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 de puxar a tela duas vezes para atualizar na demonstração da caixa de chat e podemos implementar um efeito personalizado que usa uma animação de carregamento mais nítida. A caixa de entrada inteira também é desfocada quando 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 os efeitos de brilho de rolagem e elástico

Para desativar o efeito de quicar 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: atingir o limite de rolagem mostra um brilho.
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 puxar a tela de cima para baixo para atualizar e impedir que as rolagens escapem do widget da caixa de chat. Isso oferece uma experiência do usuário ideal que teria sido difícil de alcançar sem o overscroll-behavior do CSS.

Confira a demonstração | Origem