Os padrões de deslizar para revelar permitem que os usuários deslizem uma linha em primeiro plano ou um painel de conteúdo horizontalmente para expor a interface oculta, como botões de ação contextual (Fixar, Silenciar, Arquivar, Excluir) ou um menu lateral secundário, sem ativar automaticamente nenhuma ação. Em vez de realizar uma ação durante o deslize, o contêiner se abre para revelar controles interativos que o usuário pode inspecionar e ativar ou deslizar de volta para fechar.
Para um gesto de deslizar em largura total que confirma e dispensa imediatamente um item após um limite, consulte Deslizar para remover. Para revelar um cabeçalho oculto na parte de cima de um contêiner de rolagem vertical, consulte Puxar para revelar.
Como fazer a implementação
Seja para revelar botões de ação em um item de lista ou um menu lateral ao lado de um painel rolável, a mecânica é idêntica: envolva o conteúdo em primeiro plano e o painel oculto em um contêiner de rolagem horizontal externo configurado com CSS Scroll Snap (scroll-snap-type: x mandatory) e overscroll-behavior: chain. O mecanismo de rolagem nativo do navegador processa o impulso, o ajuste, a rolagem do foco do teclado e o encadeamento de rolagem sem listeners de gestos do JavaScript.
Etapa 1: marcar o wrapper de deslizar, o conteúdo e o painel revelado
Coloque o conteúdo em primeiro plano primeiro e os controles revelados (botões de ação ou um menu lateral) em segundo lugar dentro do contêiner de rolagem .SwipeReveal. Como o conteúdo é o primeiro na ordem do DOM, o contêiner de rolagem começa naturalmente no deslocamento de rolagem 0 com o painel oculto fora da tela à direita.
<!-- Example A: List item with revealable action buttons -->
<div class="SwipeReveal">
<div class="SwipeReveal-content">
<strong>Design review notes</strong>
<p>Updated component specs for the Q3 release.</p>
</div>
<div class="SwipeReveal-panel" role="group" aria-label="Item actions">
<button type="button">Pin</button>
<button type="button">Archive</button>
<button type="button">Delete</button>
</div>
</div>
<!-- Example B: Scrollable content pane with a revealable side menu -->
<div class="SwipeReveal" role="region" aria-label="Document preview with swipeable side menu">
<div class="SwipeReveal-content is-scrollable" tabindex="0">
<h2>Article Preview</h2>
<p>Scroll vertically through this pane, or swipe left to reveal the side menu...</p>
</div>
<nav class="SwipeReveal-panel" aria-label="Contextual menu">
<a href="#share">Share</a>
<a href="#bookmark">Bookmark</a>
<a href="#export">Export PDF</a>
</nav>
</div>
Etapa 2: configurar o contêiner de ajuste horizontal e o overscroll-behavior: chain
Quando você envolve um elemento em um contêiner de rolagem externo apenas para criar um efeito de deslizar para revelar, esse wrapper externo se torna um scroller intermediário dentro do documento (ou outro contêiner de rolagem pai).
As quatro palavras-chave de overscroll-behavior controlam de forma independente se o excesso de rolagem se propaga para um contêiner de rolagem ancestral (encadeamento de rolagem) e se o contêiner mostra recursos de rolagem esticada local, como alongamento elástico ou brilho de borda (efeito de limite local):
| Valor | Encadeamento de rolagem (propaga para o ancestral) | Efeito de limite local (elástico/brilho) | Função em "Deslize para revelar" |
|---|---|---|---|
auto |
Sim | Sim | Comportamento padrão. Mantenha essa opção em roladores de conteúdo interno em que o feedback de retorno normal é desejado, mas evite no wrapper de deslize externo, em que ele faz o menu retornar |
chain |
Sim | Não | Use no rolador externo .SwipeReveal para que o excesso de rolagem seja encadeado no rolador pai (como o documento) sem que o menu ou a faixa de ação saltem |
contain |
Não | Sim | Captura a rolagem dentro do contêiner, mas ainda faz o efeito de mola localmente |
none |
Não | Não | Bloqueia o retorno local e o encadeamento de rolagem para ancestrais |
Transforme .SwipeReveal em uma grade de duas colunas (grid-template-columns: 100% max-content) em que .SwipeReveal-content se ajusta a start e .SwipeReveal-panel se ajusta a end:
.SwipeReveal {
/* Column 1: full-width content. Column 2: auto-sized actions or side menu. */
display: grid;
grid-template-columns: 100% max-content;
overflow-x: auto;
scroll-snap-type: x mandatory;
scrollbar-width: none;
/* Prevent the swipe wrapper and revealed panel from bouncing locally while
still chaining excess scroll into the parent scroller (the document). */
overscroll-behavior: chain;
}
@media (prefers-reduced-motion: no-preference) {
.SwipeReveal {
scroll-behavior: smooth;
}
}
.SwipeReveal-content {
scroll-snap-align: start;
scroll-initial-target: nearest;
background: Canvas;
}
.SwipeReveal-content:focus-visible {
outline: auto;
outline-offset: -2px;
}
.SwipeReveal-content.is-scrollable {
block-size: 20rem;
overflow: auto;
/* Keeps default `overscroll-behavior: auto` so the content pane retains
its normal bounce effect while chaining horizontal swipes to `.SwipeReveal`. */
}
.SwipeReveal-panel {
/* Snapping a trailing panel to `end` (or a leading panel to `start`) brings
all controls fully into view while keeping the adjacent edge of the content visible. */
scroll-snap-align: end;
display: flex;
&:first-child {
scroll-snap-align: start;
}
}
Se você também quiser revelar controles no lado esquerdo (
inline-start) usando três colunas (grid-template-columns: max-content 100% max-content) ou duas colunas (max-content 100%), coloque o.SwipeReveal-panelinicial antes de.SwipeReveal-content. Como.SwipeReveal-contenttemscroll-initial-target: nearest, o contêiner de rolagem inicialmente fica na coluna de conteúdo, e não no painel principal.
Etapa 3: ajustar os painéis de conteúdo focalizáveis de volta à visualização no foco do teclado
Como os botões ou links dentro de .SwipeReveal-panel estão fora da tela em repouso, usar a tecla Tab para acessá-los com um teclado rola automaticamente .SwipeReveal para revelar o controle em foco. No entanto, como .SwipeReveal-content abrange 100% da largura do contêiner externo, a maior parte dele permanece dentro da porta de rolagem, mesmo com o painel lateral aberto. Portanto, pressionar Shift+Tab para mover o foco de volta para .SwipeReveal-content (ou para um elemento focalizável dentro dele) não vai rolar a faixa automaticamente de volta para 0.
Detecte focusin em .SwipeReveal-content e verifique event.target.matches(':focus-visible') para que o foco do teclado no painel ou em qualquer descendente retorne .SwipeReveal-content ao início sem interferir nos cliques do ponteiro ou nos arrastos de toque:
for (const content of document.querySelectorAll('.SwipeReveal-content')) {
content.addEventListener('focusin', (event) => {
if (event.target.matches(':focus-visible')) {
content.scrollIntoView({
block: 'nearest',
inline: 'start',
});
}
});
}
Práticas recomendadas e armadilhas
- RECOMENDADO: use
scroll-snap-type: x mandatoryem vez deproximity. Comproximity, um deslize leve pode deixar os botões de ação ou o menu lateral parcialmente expostos em repouso. - DEFINA
overscroll-behavior: chainno rolador horizontal externo de deslizar para revelar (.SwipeReveal) para que a rolagem possa ser encadeada no rolador pai sem que o menu revelado ou a faixa de ação saltem. - MANTENHA
overscroll-behavior: auto(o padrão) em painéis de conteúdo roláveis internos (.SwipeReveal-content.is-scrollable) em que você quer que o feedback de retorno normal aconteça, mas ainda encadeando deslizes horizontais ao rolador de deslize externo. - RECOMENDADO: implemente ações reveladas e itens do menu lateral como controles DOM reais que podem receber foco (
<button>,<a>). Quando.SwipeReveal-contentpuder receber foco ou tiver controles que podem receber foco, use um listenerfocusincomevent.target.matches(':focus-visible')para chamarscrollIntoView({ block: 'nearest', inline: 'start' }). Assim, ao voltar para o painel de conteúdo, o painel lateral será fechado sem afetar as interações do ponteiro. - NÃO intercepte eventos
wheel,touchmoveoupointermoveem JavaScript para traduzir manualmente o conteúdo ou emular o encadeamento de rolagem. A interceptação de gestos da linha de execução principal bloqueia a rolagem orientada pelo compositor, interrompe o impulso de rolagem nativo e prejudica a capacidade de resposta.
Fallback
Quando um contêiner .SwipeReveal coloca um .SwipeReveal-panel no lado esquerdo (inline-start) antes de .SwipeReveal-content, os navegadores que não oferecem suporte a scroll-initial-target: nearest são renderizados inicialmente no deslocamento de rolagem 0 com o painel principal exposto.
Se o destino do Baseline não for compatível com scroll-initial-target, detecte a compatibilidade com CSS.supports('scroll-initial-target', 'nearest') e chame scrollTo() com behavior: 'instant' em load para que os contêineres com um painel principal pulem direto para .SwipeReveal-content sem rolar a página:
Aprimoramento progressivo
Trate overscroll-behavior: chain e scrollbar-width: none como aprimoramentos progressivos sem substitutos de JavaScript:
- Substituição automática de cascata de CSS: o valor inicial de
overscroll-behavioréauto. Os navegadores que ainda não reconhecem a palavra-chavechainignoramoverscroll-behavior: chainno momento da análise e mantêmauto. Como oautojá permite o encadeamento de rolagem para contêineres de rolagem ancestrais, tanto o gesto de deslizar para revelar quanto o encadeamento de rolagem para o pai funcionam em todos os navegadores sem precisar de configuração. Os navegadores compatíveis simplesmente suprimem o efeito de elástico indesejado no wrapper de deslizar externo. - Substituição de
containounone: se uma folha de estilo base ou classe utilitária definiroverscroll-behavior: containounone, declareoverscroll-behavior: autoimediatamente antes deoverscroll-behavior: chainpara que os navegadores que ainda não oferecem suporte achainainda encadeiem a rolagem para o elemento pai em vez de bloqueá-la. - Ocultação da barra de rolagem (
scrollbar-width: none): ocultar a barra de rolagem horizontal é puramente estético. Se um mecanismo mais antigo ignorarscrollbar-width: none, o contêiner de deslizar para revelar vai continuar totalmente funcional com uma barra de rolagem padrão.