Pola geser untuk menampilkan memungkinkan pengguna menggeser baris latar depan atau panel konten secara horizontal untuk menampilkan UI tersembunyi—seperti tombol tindakan kontekstual (Sematkan, Bisukan, Arsipkan, Hapus) atau menu samping sekunder—tanpa mengaktifkan tindakan apa pun secara otomatis. Alih-alih melakukan tindakan selama geser, penampung akan terbuka untuk menampilkan kontrol interaktif yang dapat diperiksa dan diaktifkan pengguna, atau geser kembali untuk menutup.
Untuk gestur geser lebar penuh yang langsung menerapkan dan menutup item setelah melewati batas, lihat Geser untuk menghapus. Untuk menampilkan header tersembunyi di bagian atas penampung scroll vertikal, lihat Tarik untuk Menampilkan.
Cara menerapkan
Baik Anda menampilkan tombol tindakan pada item daftar atau menu samping di samping panel yang dapat di-scroll, mekanismenya sama: gabungkan konten latar depan dan panel tersembunyi di dalam penampung scroll horizontal luar yang dikonfigurasi dengan CSS Scroll Snap (scroll-snap-type: x mandatory) dan overscroll-behavior: chain. Mesin scroll native browser menangani momentum, snapping, scroll fokus keyboard, dan perantaian scroll tanpa pemroses gestur JavaScript.
Langkah 1: Beri markup pada wrapper geser, konten, dan panel yang ditampilkan
Tempatkan konten latar depan terlebih dahulu dan kontrol yang ditampilkan (tombol tindakan atau menu samping) kedua di dalam penampung scroll .SwipeReveal. Karena konten berada di urutan pertama dalam DOM, penampung scroll secara alami dimulai pada offset scroll 0 dengan panel yang disembunyikan di luar layar di sebelah kanan.
<!-- 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>
Langkah 2: Konfigurasikan penampung snap horizontal dan overscroll-behavior: chain
Saat Anda menggabungkan elemen dalam penampung scroll luar hanya untuk membuat efek geser untuk menampilkan, wrapper luar tersebut menjadi scroller perantara di dalam dokumen (atau penampung scroll induk lainnya).
Empat kata kunci overscroll-behavior secara independen mengontrol apakah scroll berlebih akan diteruskan ke penampung scroll ancestor (perantaian scroll) dan apakah penampung menampilkan affordance overscroll lokal seperti peregangan karet elastis atau glow tepi (efek batas lokal):
| Nilai | Scroll Chaining (Menyebar ke Elemen Induk) | Efek Batas Lokal (Karet Gelang/Cahaya) | Peran dalam Geser untuk Mengungkapkan |
|---|---|---|---|
auto |
Ya | Ya | Perilaku default; pertahankan ini pada scroller konten dalam yang menginginkan masukan pantulan normal, tetapi hindari pada wrapper geser luar yang menyebabkan menu memantul |
chain |
Ya | Tidak | Gunakan pada scroller .SwipeReveal luar sehingga scroll berlebih akan dirantai ke scroller induk (seperti dokumen) tanpa menu atau strip tindakan memantul |
contain |
Tidak | Ya | Menjebak scroll di dalam penampung sambil tetap memantul secara lokal |
none |
Tidak | Tidak | Memblokir pantulan lokal dan perantaian scroll ke ancestor |
Buat .SwipeReveal menjadi petak dua kolom (grid-template-columns: 100% max-content) dengan .SwipeReveal-content diposisikan ke start dan .SwipeReveal-panel diposisikan ke 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;
}
}
Jika Anda juga ingin menampilkan kontrol di sisi kiri (
inline-start) menggunakan tiga kolom (grid-template-columns: max-content 100% max-content) atau dua kolom (max-content 100%), tempatkan.SwipeReveal-paneldi awal sebelum.SwipeReveal-content. Karena.SwipeReveal-contentmemilikiscroll-initial-target: nearest, penampung scroll awalnya berada di kolom konten, bukan panel utama.
Langkah 3: Posisikan panel konten yang dapat difokuskan kembali ke tampilan saat fokus keyboard
Karena tombol atau link di dalam .SwipeReveal-panel berada di luar layar saat tidak digunakan, menekan tombol tab untuk mengaksesnya dengan keyboard akan otomatis men-scroll .SwipeReveal untuk menampilkan kontrol yang difokuskan. Namun, karena .SwipeReveal-content mencakup 100% lebar penampung luar, sebagian besar tetap berada di dalam scrollport meskipun panel samping terbuka—jadi menekan Shift+Tab untuk memindahkan fokus kembali ke .SwipeReveal-content (atau ke elemen yang dapat difokuskan di dalamnya) tidak akan otomatis men-scroll trek kembali ke 0.
Proses focusin di .SwipeReveal-content dan periksa event.target.matches(':focus-visible') agar fokus keyboard pada panel atau turunan apa pun mengembalikan .SwipeReveal-content ke awal tanpa mengganggu klik pointer atau tarik sentuh:
for (const content of document.querySelectorAll('.SwipeReveal-content')) {
content.addEventListener('focusin', (event) => {
if (event.target.matches(':focus-visible')) {
content.scrollIntoView({
block: 'nearest',
inline: 'start',
});
}
});
}
Praktik terbaik dan kesalahan umum
- LAKUKAN: gunakan
scroll-snap-type: x mandatory, bukanproximity. Denganproximity, geser ringan dapat membuat tombol tindakan atau menu samping terekspos sebagian saat tidak digunakan. - LAKUKAN setelan
overscroll-behavior: chainpada scroller geser horizontal luar untuk menampilkan (.SwipeReveal) sehingga scroll dapat dihubungkan ke scroller induk tanpa menu atau strip tindakan yang ditampilkan memantul. - LAKUKAN biarkan
overscroll-behavior: auto(default) di panel konten dalam yang dapat di-scroll (.SwipeReveal-content.is-scrollable) tempat Anda ingin masukan pantulan normal terjadi sambil tetap merantai geser horizontal ke scroller geser luar. - LAKUKAN penerapan tindakan yang ditampilkan dan item menu samping sebagai kontrol DOM yang dapat difokuskan secara nyata (
<button>,<a>). Jika.SwipeReveal-contentdapat difokuskan atau berisi kontrol yang dapat difokuskan, pasangkan pemrosesfocusindenganevent.target.matches(':focus-visible')untuk memanggilscrollIntoView({ block: 'nearest', inline: 'start' })sehingga menekan tab kembali ke panel konten akan menutup panel samping tanpa memengaruhi interaksi pointer. - JANGAN mengintersepsi peristiwa
wheel,touchmove, ataupointermovedi JavaScript untuk menerjemahkan konten secara manual atau mengemulasikan perantaian scroll. Intersepsi gestur thread utama memblokir scroll yang didorong compositor, merusak momentum scroll native, dan menurunkan responsivitas.
Pengganti
Jika penampung .SwipeReveal menempatkan .SwipeReveal-panel di sisi kiri (inline-start) sebelum .SwipeReveal-content, browser yang tidak mendukung scroll-initial-target: nearest akan merender di offset scroll 0 dengan panel utama terekspos.
Jika target Dasar Pengukuran Anda tidak mendukung scroll-initial-target, deteksi dukungan dengan CSS.supports('scroll-initial-target', 'nearest') dan panggil scrollTo() dengan behavior: 'instant' di load sehingga penampung dengan panel utama langsung melompat ke .SwipeReveal-content tanpa men-scroll halaman:
Progressive enhancement
Perlakukan overscroll-behavior: chain dan scrollbar-width: none sebagai progressive enhancement tanpa penggantian JavaScript:
- Penggantian cascade CSS otomatis: Nilai awal
overscroll-behavioradalahauto. Browser yang belum mengenali kata kuncichainakan mengabaikanoverscroll-behavior: chainpada waktu penguraian dan mempertahankanauto. Karenaautosudah mengizinkan perangkaian scroll ke penampung scroll ancestor, gestur geser untuk menampilkan dan perangkaian scroll ke induk berfungsi di semua browser secara langsung; browser yang mendukung cukup menyembunyikan pantulan karet yang tidak diinginkan pada wrapper geser luar. - Mengganti
containataunone: Jika stylesheet dasar atau class utilitas menetapkanoverscroll-behavior: containataunone, deklarasikanoverscroll-behavior: autotepat sebelumoverscroll-behavior: chainsehingga browser yang belum mendukungchainmasih merantai scroll ke induk, bukan menjebaknya. - Menyembunyikan scrollbar (
scrollbar-width: none): Menyembunyikan jalur scrollbar horizontal murni bersifat kosmetik; jika mesin yang lebih lama mengabaikanscrollbar-width: none, penampung geser untuk menampilkan tetap berfungsi sepenuhnya dengan scrollbar standar.