Жест смахивания позволяет пользователям сдвигать строку или панель контента по горизонтали, чтобы открыть скрытый интерфейс, например кнопки контекстных действий (Закрепить, Отключить звук, Архивировать, Удалить) или дополнительное боковое меню, без автоматической активации какого-либо действия. Вместо выполнения действия при пролистывании контейнер открывается, чтобы показать интерактивные элементы управления, которые пользователь может проверить и активировать или закрыть, проведя пальцем назад.
Если вам нужен жест смахивания на всю ширину, который сразу же фиксирует и отклоняет элемент за пороговым значением, см. раздел Проведите пальцем, чтобы удалить. Чтобы показать скрытый заголовок в верхней части контейнера с вертикальной прокруткой, используйте жест Потянуть, чтобы показать.
Рекомендации по внедрению
Независимо от того, показываете ли вы командные кнопки в элементе списка или боковое меню рядом с прокручиваемой панелью, механика идентична: оберните контент переднего плана и скрытую панель во внешний горизонтальный контейнер прокрутки, настроенный с помощью CSS Scroll Snap (scroll-snap-type: x mandatory) и overscroll-behavior: chain. Встроенный механизм прокрутки браузера обрабатывает инерцию, привязку, прокрутку фокуса клавиатуры и цепочку прокрутки без прослушивателей жестов JavaScript.
Шаг 1. Разметьте контейнер, контент и панель
Разместите контент переднего плана первым, а элементы управления (командные кнопки или боковое меню) – вторыми в контейнере прокрутки .SwipeReveal. Поскольку контент находится в начале DOM, контейнер прокрутки начинается со смещения 0, а панель скрыта за пределами экрана справа.
<!-- 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>
Шаг 2. Настройте горизонтальный контейнер с привязкой и overscroll-behavior: chain
Если вы поместите элемент во внешний контейнер прокрутки только для того, чтобы создать эффект раскрытия при пролистывании, этот внешний контейнер станет промежуточным контейнером прокрутки внутри документа (или другого родительского контейнера прокрутки).
Четыре ключевых слова overscroll-behavior независимо управляют тем, распространяется ли избыточная прокрутка на родительский контейнер прокрутки (цепочка прокрутки) и отображает ли контейнер локальные возможности прокрутки, такие как эластичное растяжение резиновой ленты или свечение краев (эффект локальной границы):
| Значение | Цепочка прокрутки (передается родительскому элементу) | Эффект локальной границы (резиновая лента/свечение) | Роль в Swipe-to-Reveal |
|---|---|---|---|
auto |
Да | Да | Режим работы по умолчанию. Используйте его для внутренних прокручиваемых элементов, где требуется обычный отскок, но не для внешнего контейнера, где он приводит к отскоку меню. |
chain |
Да | Нет | Используйте для внешнего элемента прокрутки .SwipeReveal, чтобы избыточная прокрутка переходила в родительский элемент (например, документ) без скачков меню или панели действий. |
contain |
Нет | Да | Прокрутка внутри контейнера, локальный отскок |
none |
Нет | Нет | Блокирует как локальный отскок, так и цепочку прокрутки к предкам |
Создайте сетку .SwipeReveal из двух столбцов (grid-template-columns: 100% max-content), в которой .SwipeReveal-content привязывается к start, а .SwipeReveal-panel – к 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;
}
}
Если вы также хотите показать элементы управления слева (
inline-start) в три столбца (grid-template-columns: max-content 100% max-content) или два столбца (max-content 100%), поместите ведущий.SwipeReveal-panelперед.SwipeReveal-content. Поскольку у.SwipeReveal-contentестьscroll-initial-target: nearest, контейнер прокрутки изначально находится на столбце контента, а не на ведущей панели.
Шаг 3. Настройте возврат панелей с контентом в область просмотра при фокусе клавиатуры
Поскольку кнопки или ссылки внутри .SwipeReveal-panel находятся за пределами экрана, при переходе к ним с помощью клавиатуры .SwipeReveal автоматически прокручивается, чтобы показать элемент управления, на котором находится фокус. Однако, поскольку .SwipeReveal-content занимает 100% ширины внешнего контейнера, большая его часть остается внутри области прокрутки, даже когда боковая панель открыта. Поэтому нажатие Shift+Tab для перемещения фокуса обратно на .SwipeReveal-content (или на фокусируемый элемент внутри него) не приведет к автоматической прокрутке дорожки обратно к 0.
Прослушивайте focusin на .SwipeReveal-content и проверяйте event.target.matches(':focus-visible'), чтобы фокус клавиатуры на панели или любом дочернем элементе возвращал .SwipeReveal-content к началу, не мешая кликам указателя или перетаскиванию касанием:
for (const content of document.querySelectorAll('.SwipeReveal-content')) {
content.addEventListener('focusin', (event) => {
if (event.target.matches(':focus-visible')) {
content.scrollIntoView({
block: 'nearest',
inline: 'start',
});
}
});
}
Рекомендации и распространенные ошибки
- РЕКОМЕНДУЕТСЯ использовать
scroll-snap-type: x mandatoryвместоproximity. Вproximityпри легком свайпе командные кнопки или боковое меню могут остаться частично видимыми. - РЕКОМЕНДУЕТСЯ задать
overscroll-behavior: chainдля внешнего горизонтального скроллера (.SwipeReveal), чтобы прокрутка могла переходить в родительский скроллер без скачков меню или панели действий. - РЕКОМЕНДУЕТСЯ оставить значение
overscroll-behavior: auto(по умолчанию) для внутренних прокручиваемых панелей контента (.SwipeReveal-content.is-scrollable), где вы хотите, чтобы происходил обычный отскок, но при этом горизонтальные пролистывания были связаны с внешним прокручиваемым элементом. - РЕКОМЕНДУЕТСЯ реализовать действия и пункты бокового меню как реальные элементы управления DOM, на которые можно установить фокус (
<button>,<a>). Если на элемент.SwipeReveal-contentможно установить фокус или он содержит элементы управления, на которые можно установить фокус, свяжите прослушивательfocusinсevent.target.matches(':focus-visible'), чтобы вызватьscrollIntoView({ block: 'nearest', inline: 'start' }). Тогда при возврате фокуса на панель контента боковая панель будет закрываться, не влияя на взаимодействие с указателем. - НЕ перехватывайте события
wheel,touchmoveилиpointermoveв JavaScript, чтобы вручную переводить контент или эмулировать цепочку прокрутки. Перехват жестов в основном потоке блокирует прокрутку, управляемую компоновщиком, нарушает нативный импульс прокрутки и снижает скорость отклика.
Переход в обходной режим
Если контейнер .SwipeReveal размещает .SwipeReveal-panel слева (inline-start) перед .SwipeReveal-content, браузеры, которые не поддерживают scroll-initial-target: nearest, сначала будут отображать страницу со смещением прокрутки 0 и открытой начальной панелью.
Если целевой браузер Baseline не поддерживает scroll-initial-target, определите поддержку с помощью CSS.supports('scroll-initial-target', 'nearest') и вызовите scrollTo() с behavior: 'instant' в load, чтобы контейнеры с ведущей панелью сразу переходили к .SwipeReveal-content без прокрутки страницы:
Прогрессивное улучшение
Используйте overscroll-behavior: chain и scrollbar-width: none как прогрессивные улучшения без резервных вариантов JavaScript.
- Автоматический резервный каскад CSS. Исходное значение
overscroll-behavior–auto. Браузеры, которые пока не распознают ключевое словоchain, игнорируютoverscroll-behavior: chainво время синтаксического анализа и сохраняютauto. Посколькуautoуже позволяет связывать прокрутку с родительскими контейнерами, жест раскрытия и связывание прокрутки с родительским элементом работают во всех браузерах без дополнительной настройки. Поддерживающие браузеры просто подавляют нежелательный отскок на внешнем контейнере. - Переопределение
containилиnone. Если базовая таблица стилей или служебный класс задаетoverscroll-behavior: containилиnone, объявитеoverscroll-behavior: autoнепосредственно передoverscroll-behavior: chain, чтобы браузеры, которые ещё не поддерживаютchain, по-прежнему связывали прокрутку с родительским элементом, а не блокировали ее. - Скрытие полосы прокрутки (
scrollbar-width: none). Скрытие горизонтальной полосы прокрутки – это чисто косметическое изменение. Если старый движок игнорируетscrollbar-width: none, контейнер, который можно открыть с помощью пролистывания, остается полностью функциональным со стандартной полосой прокрутки.