UI 动效 008:Swipe Actions,为什么列表项一滑就露出按钮
July 2, 2026 · 8:15 AM

UI 动效 008:Swipe Actions,为什么列表项一滑就露出按钮

这期拆解 Swipe Actions / 滑动操作:它适合邮件、待办、收藏列表里的归档、删除等上下文动作,核心是把动作区放在底层,让内容层跟随手势水平位移,并用阈值和速度决定吸附还是回弹。文中用自制 GIF 和 Web 伪代码说明实现方式。

列表里一封邮件向左滑,右侧露出「归档 / 删除」按钮。这个动效看起来像按钮把内容推开,实际更像两张纸叠在一起:动作区一直在底下,真正移动的是上面的行内容。
这就是 Swipe Actions / 滑动操作,也常被叫作 Contextual Swipe。它省空间,但也容易误删、误触,所以实现时不能只盯着「滑得顺不顺」。更重要的是:滑到哪里算露出?什么时候吸附?什么时候回弹?
滑动操作动效示意
滑动操作动效示意
自制 GIF 示意:行内容向左位移后露出底层动作区,并在阈值附近吸附;非真实产品截图。

这个动效叫什么,适合放在哪里

在 iOS / SwiftUI 里,swipeActions 的说明是「给列表行添加自定义滑动动作」;Android Jetpack Compose 的 SwipeToDismissBox 则把它描述为「用户向左或向右滑动一个项目,以删除或更新该项目」。12
它最适合放在「列表行」上:邮件、待办、通知、收藏夹、购物车、下载队列。读者的手指不是在移动整个页面,而是在对某一行说:「我想对这一项做点事。」
场景适合的动作不适合的动作
邮件 / 消息列表归档、标为已读、删除打开复杂编辑页
待办 / 任务列表完成、推迟、删除多步表单操作
收藏 / 下载列表移除、重新下载需要长解释的功能
一个简单判断:如果动作能用一个动词讲清楚,而且只影响这一行,适合藏进滑动操作。需要读说明、选参数、确认多个对象时,放进详情页或更多菜单更稳。

它是怎么动起来的

Swipe Actions 的结构通常有两层。
  1. 底层动作区:红色删除、蓝色归档、绿色完成等按钮一直铺在行的背后。
  2. 上层内容区:头像、标题、摘要组成一张可拖动的卡片。
  3. 手势状态:记录起点、当前横向位移、速度、方向。
  4. 结算动画:松手后根据阈值决定吸附到打开状态,还是回到关闭状态。
Web 端最常见的做法,是用 transform: translateX(...) 移动上层内容。MDN 对 translateX() 的定义就是让元素在二维平面上沿水平方向重新定位;它正好对应「行内容跟着手指横向移动」这个需求。3
.row {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
}

.actions {
  position: absolute;
  inset: 0 0 0 auto;
  width: 96px;
}

.content {
  transform: translateX(var(--x));
  transition: transform 180ms ease-out;
}
这里的 touch-action: pan-y 很关键。touch-action 决定浏览器怎么处理触摸区域,MDN 也提醒,浏览器默认会接管滚动和缩放手势;明确声明后,应用才更容易把横向拖动留给组件自己处理,同时让纵向滚动继续属于页面。4

松手时怎么判断:打开,还是回弹

真正的手感不在拖动中,而在松手那一下。
如果用户只滑了十几像素,组件应该回到原位。这个距离更像误触或试探。如果用户已经滑过动作按钮宽度的一半,或者松手速度很快,组件就可以吸附到打开状态,让按钮停在可点的位置。
const ACTION_WIDTH = 96;
const THRESHOLD = ACTION_WIDTH * 0.55;

function onDragMove(dx) {
  const x = clamp(dx, -ACTION_WIDTH, 0);
  row.style.setProperty('--x', `${x}px`);
}

function onDragEnd(dx, velocityX) {
  const shouldOpen = Math.abs(dx) > THRESHOLD || velocityX < -0.6;
  const target = shouldOpen ? -ACTION_WIDTH : 0;
  row.animateTo(target, { duration: 180, easing: 'ease-out' });
}
有两个细节会明显影响质感。
  • 限制最大位移:不要让内容层无限滑出屏幕。到达动作区宽度后继续拖,可以加一点阻尼,但不要继续暴露空白。
  • 速度也算数:短距离快速一甩,用户的意图可能已经很明确;长距离慢慢拖,反而更像在确认。
  • 只让一个行保持打开:列表里同时露出多个红色按钮,会让界面像坏掉了一样。新行打开时,旧行应自动收起。
Android 的示例也把状态、背景内容和方向拆开处理:SwipeToDismissBoxState 管理滑动状态,backgroundContent 在内容被移开时显示,并能根据滑动方向换成不同图标或颜色。2

为什么这个动效容易出问题

Swipe Actions 的麻烦在于,它把功能藏起来了。NN/g 在分析 Contextual Swipe 时列出过几个常见问题:缺少可见提示、滑动后遮住原内容、同一手势在不同页面含义不一致,以及误删导致数据丢失。5
所以,设计时可以按这四条自查:
  • 保留行内容的识别线索:滑开后至少还能看到标题或头像的一部分,用户要知道自己正在处理哪一行。
  • 少放动作:一个方向最好只放 1-2 个动作。把四五个按钮全藏在背后,发现成本会变高。
  • 危险操作要二次保护:删除可以先露出按钮,点按钮后再删除;如果支持 full swipe 直接删除,就要给明显的撤销入口。
  • 别和页面导航抢手势:列表在可横滑页面、轮播卡片或返回手势边缘里时,优先级要设计清楚。否则用户想翻页,结果把一条记录删了。

做这个动效时,先定三件事

第一,动作区有多宽。常见做法是让按钮宽度固定,比如 80-120px,再把最大位移限制到这个宽度。
第二,打开阈值是多少。可以从 50%-60% 动作区宽度开始调,再根据真实设备上的拇指距离微调。不要用一个很小的阈值诱导误开。
第三,删除是否允许「一滑到底」。如果对象可恢复、频率很高,full swipe 可以提速;如果对象不可恢复,最好只让滑动露出按钮,不让滑动本身直接执行。
Swipe Actions 好用的地方,是把「这一行能做什么」放回这一行旁边。它危险的地方也在这里:按钮离内容太近,手势又太快。做顺滑只是第一步,让用户随时知道自己滑的是哪一项、会发生什么,才是这个动效真正要解决的问题。

Related content

  • Sign in to comment.