Scorri per vedere

I pattern di scorrimento per mostrare consentono agli utenti di far scorrere orizzontalmente una riga in primo piano o un riquadro dei contenuti per mostrare l'interfaccia utente nascosta, ad esempio i pulsanti di azione contestuali (Fissa, Disattiva audio, Archivia, Elimina) o un menu laterale secondario, senza attivare automaticamente alcuna azione. Anziché eseguire un'azione durante lo scorrimento, il contenitore si apre per mostrare i controlli interattivi che l'utente può ispezionare e attivare o chiudere scorrendo indietro.

Per un gesto di scorrimento a larghezza intera che conferma e chiude immediatamente un elemento oltre una soglia, consulta Scorri per rimuovere. Per mostrare un'intestazione nascosta nella parte superiore di un contenitore a scorrimento verticale, consulta Trascina per mostrare.

Come implementare

Che tu stia mostrando i pulsanti di azione su un elemento dell'elenco o un menu laterale accanto a un riquadro scorrevole, i meccanismi sono identici: racchiudi i contenuti in primo piano e il riquadro nascosto all'interno di un contenitore di scorrimento orizzontale esterno configurato con CSS Scroll Snap (scroll-snap-type: x mandatory) e overscroll-behavior: chain. Il motore di scorrimento nativo del browser gestisce l'inerzia, lo snapping, lo scorrimento della selezione della tastiera e il concatenamento dello scorrimento senza listener di gesti JavaScript.

Passaggio 1: esegui il markup del wrapper di scorrimento, dei contenuti e del pannello mostrato

Posiziona i contenuti in primo piano prima e i controlli mostrati (pulsanti di azione o un menu laterale) dopo all'interno del contenitore di scorrimento .SwipeReveal. Poiché i contenuti sono i primi nell'ordine DOM, il container di scorrimento inizia naturalmente con l'offset di scorrimento 0 con il riquadro nascosto fuori schermo a destra.

<!-- 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>

Passaggio 2: configura il contenitore di aggancio orizzontale e overscroll-behavior: chain

Quando racchiudi un elemento in un contenitore di scorrimento esterno solo per creare un effetto di scorrimento per mostrare, questo wrapper esterno diventa uno scroller intermedio all'interno del documento (o di un altro contenitore di scorrimento principale).

Le quattro parole chiave di overscroll-behavior controllano in modo indipendente se lo scorrimento in eccesso si propaga a un contenitore di scorrimento predecessore (concatenamento dello scorrimento) e se il contenitore mostra affordance di overscroll locali, come l'allungamento elastico o il bagliore dei bordi (effetto di confine locale):

Valore Concatenamento dello scorrimento (propagazione all'antenato) Effetto bordo locale (elastico/bagliore) Ruolo in Swipe-to-Reveal
auto Sì Sì Comportamento predefinito; mantieni questa opzione attiva per gli scroller dei contenuti interni in cui è richiesto il normale feedback di rimbalzo, ma evitala per il wrapper di scorrimento esterno in cui provoca il rimbalzo del menu
chain Sì No Utilizza sullo scroller .SwipeReveal esterno in modo che lo scorrimento in eccesso si concateni allo scroller principale (ad esempio il documento) senza che il menu o la striscia di azioni rimbalzino
contain No Sì Blocca lo scorrimento all'interno del container, pur continuando a rimbalzare localmente
none No No Blocca sia il rimbalzo locale sia il concatenamento dello scorrimento agli elementi precedenti

Trasforma .SwipeReveal in una griglia a due colonne (grid-template-columns: 100% max-content) in cui .SwipeReveal-content si aggancia a start e .SwipeReveal-panel si aggancia 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 vuoi mostrare i controlli anche sul lato sinistro (inline-start) utilizzando tre colonne (grid-template-columns: max-content 100% max-content) o due colonne (max-content 100%), posiziona l'elemento principale .SwipeReveal-panel prima di .SwipeReveal-content. Poiché .SwipeReveal-content ha scroll-initial-target: nearest, il contenitore di scorrimento si trova inizialmente sulla colonna dei contenuti anziché sul pannello principale.

Passaggio 3: ripristina la visualizzazione dei riquadri dei contenuti attivabili quando la selezione della tastiera è attiva

Poiché i pulsanti o i link all'interno di .SwipeReveal-panel non sono visibili sullo schermo quando non sono attivi, se li selezioni con una tastiera, .SwipeReveal scorre automaticamente per mostrare il controllo attivo. Tuttavia, poiché .SwipeReveal-content occupa 100% della larghezza del container esterno, la maggior parte rimane all'interno dello scrollport anche quando il riquadro laterale è aperto, quindi premendo Shift+Tab per spostare lo stato attivo su .SwipeReveal-content (o su un elemento attivabile al suo interno) non verrà eseguito automaticamente lo scorrimento della traccia su 0.

Ascolta focusin su .SwipeReveal-content e controlla event.target.matches(':focus-visible') in modo che la selezione della tastiera sul riquadro o su qualsiasi elemento secondario torni .SwipeReveal-content all'inizio senza interferire con i clic del puntatore o i trascinamenti con il tocco:

for (const content of document.querySelectorAll('.SwipeReveal-content')) {
  content.addEventListener('focusin', (event) => {
    if (event.target.matches(':focus-visible')) {
      content.scrollIntoView({
        block: 'nearest',
        inline: 'start',
      });
    }
  });
}

Best practice e trappole

  • SÌ: utilizza scroll-snap-type: x mandatory anziché proximity. Con proximity, uno scorrimento leggero può lasciare i pulsanti di azione o il menu laterale parzialmente esposti quando non sono in uso.
  • IMPOSTA overscroll-behavior: chain sullo scroller orizzontale esterno con scorrimento per mostrare (.SwipeReveal) in modo che lo scorrimento possa concatenarsi allo scroller principale senza che il menu o la striscia di azioni mostrati rimbalzino.
  • SÌ: lascia overscroll-behavior: auto (il valore predefinito) nei riquadri dei contenuti scorrevoli interni (.SwipeReveal-content.is-scrollable) in cui vuoi che si verifichi il normale feedback di rimbalzo, continuando a concatenare gli scorrimenti orizzontali allo scroller di scorrimento esterno.
  • SÌ: implementa le azioni rivelate e le voci del menu laterale come veri e propri controlli DOM attivabili (<button>, <a>). Quando .SwipeReveal-content è attivabile o contiene controlli attivabili, associa un listener focusin a event.target.matches(':focus-visible') per chiamare scrollIntoView({ block: 'nearest', inline: 'start' }) in modo che il ritorno al riquadro dei contenuti chiuda il riquadro laterale senza influire sulle interazioni con il puntatore.
  • NON intercettare gli eventi wheel, touchmove o pointermove in JavaScript per tradurre manualmente i contenuti o emulare il concatenamento dello scorrimento. L'intercettazione dei gesti nel thread principale blocca lo scorrimento basato sul compositore, interrompe l'inerzia di scorrimento nativa e riduce la reattività.

Fallback

Quando un contenitore .SwipeReveal posiziona un .SwipeReveal-panel sul lato sinistro (inline-start) prima di .SwipeReveal-content, i browser che non supportano scroll-initial-target: nearest eseguiranno inizialmente il rendering con l'offset di scorrimento 0 con il pannello principale esposto.

Se il tuo target Baseline non supporta scroll-initial-target, rileva il supporto con CSS.supports('scroll-initial-target', 'nearest') e chiama scrollTo() con behavior: 'instant' su load in modo che i container con un pannello principale passino direttamente a .SwipeReveal-content senza scorrere la pagina:

Potenziamento progressivo

Tratta overscroll-behavior: chain e scrollbar-width: none come potenziamenti progressivi senza fallback JavaScript:

  • Fallback automatico della cascata CSS: il valore iniziale di overscroll-behavior è auto. I browser che non riconoscono ancora la parola chiave chain ignorano overscroll-behavior: chain al momento dell'analisi e mantengono auto. Poiché auto consente già il concatenamento dello scorrimento ai contenitori di scorrimento predecessori, sia il gesto di scorrimento per mostrare sia il concatenamento dello scorrimento al livello principale funzionano su tutti i browser senza problemi; i browser supportati eliminano semplicemente l'effetto rimbalzo indesiderato sul wrapper di scorrimento esterno.
  • Override di contain o none: se un foglio di stile di base o una classe di utilità imposta overscroll-behavior: contain o none, dichiara overscroll-behavior: auto immediatamente prima di overscroll-behavior: chain in modo che i browser che non supportano ancora chain concatenino comunque lo scorrimento all'elemento principale anziché bloccarlo.
  • Nascondere la barra di scorrimento (scrollbar-width: none): nascondere la barra di scorrimento orizzontale è un'operazione puramente estetica; se un motore precedente ignora scrollbar-width: none, il contenitore con scorrimento per mostrare rimane completamente funzionale con una barra di scorrimento standard.