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 de rolagem de overflow padrão do navegador ao chegar ao topo/parte de baixo do conteúdo. Os casos de uso incluem a desativação do recurso de puxar para atualizar em dispositivos móveis, a remoção de efeitos de brilho e de elasticidade, e a prevenção de rolagem do conteúdo da página quando ele está abaixo de um modal/overlay.

Contexto

Limites e encadeamento de rolagem

Encadeamento de rolagem no Chrome para Android.

O rolagem é uma das maneiras mais importantes de interagir com uma página, mas alguns padrões de UX podem ser difíceis de lidar devido aos comportamentos padrão incomuns do navegador. Como exemplo, considere uma gaveta de apps com um grande número de itens que o usuário pode rolar. Quando eles chegam ao fim, o contêiner de overflow para de rolar porque não há mais conteúdo para consumir. Em outras palavras, o usuário chega a 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 é feita pelo contêiner pai, ou seja, a página principal no exemplo.

Esse comportamento é chamado de encadeamento de rolagem, 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é inesperado. Alguns apps podem querer oferecer uma experiência diferente quando o usuário atinge um limite de rolagem.

O efeito de puxar para atualizar

O gesto de puxar para atualizar é um gesto intuitivo popularizado por apps para dispositivos móveis, como Facebook e Twitter. Deslizar para baixo em um feed de rede social e soltar cria um novo espaço para que postagens mais recentes sejam carregadas. Na verdade, essa UX específica ficou tão popular que navegadores para dispositivos móveis, como o Chrome no Android, adotaram o mesmo efeito. Deslizar para baixo na parte de cima da página atualiza a página inteira:

Atualização personalizada do Twitter
ao atualizar um feed no seu PWA.
A ação nativa de puxar para atualizar do Chrome para Android
refresca a página inteira.

Para situações como o PWA do Twitter, pode ser sensato desativar a ação nativa de puxar para atualizar. Por quê? Nesse app, provavelmente você não quer que o usuário atualize a página acidentalmente. Há também a possibilidade de ver uma animação de atualização dupla. Como alternativa, talvez seja melhor personalizar a ação do navegador, alinhando-a mais de perto à marca do site. O problema é que esse tipo de personalização é difícil de fazer. 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> de 100 vw/vh para evitar que a página transborde. Essas soluções alternativas têm efeitos negativos bem documentados na performance de rolagem.

Podemos fazer melhor!

Conheça o overscroll-behavior

A propriedade overscroll-behavior é um novo recurso do CSS que controla o comportamento do que acontece quando você rola um contêiner (incluindo a própria página). Você pode usá-lo para cancelar a vinculação de rolagem, desativar/personalizar a ação de puxar para atualizar, desativar os efeitos de elasticidade 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 os hacks mencionados na introdução.

A propriedade tem três valores possíveis:

  1. auto: padrão. Rolagens que se originam no elemento podem se propagar para elementos ancestrais.
  2. contain: impede a encadeação de rolagem. Os rolamentos 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 excessiva no Android ou o efeito de elasticidade 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 excessiva.
  3. none: igual a contain, mas também impede efeitos de rolagem excessiva no próprio nó (por exemplo, brilho de rolagem excessiva do Android ou bandas elásticas do iOS).

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

Impedir que a rolagem escape de um elemento de posição fixa

O cenário da caixa de chat

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

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

Para esse app, é mais apropriado que os rolamentos que se originam na caixa de chat permaneçam no chat. Para fazer isso, adicione 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 lugar quando o usuário chega ao topo/parte de baixo do histórico de chat. Os rolamentos que começam na caixa de chat não se propagam.

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

Outra variação do cenário de "scroll" é quando você vê o conteúdo rolando atrás de uma sobreposição de posição fixa. Um presente overscroll-behavior é necessário! O navegador está tentando ajudar, mas acaba fazendo com que o site pareça 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 de puxar para atualizar

Desativar a ação de puxar para atualizar é uma única linha de CSS. Basta impedir a encadeamento de rolagem em todo o elemento de definição da viewport. 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 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 organizada. A caixa de entrada inteira também fica desfocada quando é 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 esticamento

Para desativar o efeito de salto 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: ao tocar no limite de rolagem, aparece um brilho.
Depois: brilho desativado.

Demonstração completa

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

Conferir a demonstração | Fonte