Snackbar & Undo:用「可以反悔」替代「确定吗」
要删除一封邮件,常见做法是先弹一个 confirm:「确定删除吗?」——可几乎没人会回答「不」。这个对话框打断了每一次操作,只为防住极少数的误删。Gmail 的思路相反:直接执行,再从底部滑出一条带「撤销」的 Snackbar,几秒内不点就永久生效。它把确认成本从每次操作前,挪到了真的想反悔时。
Snackbar 的几个细节都能在上面观察到:倒计时进度条给出剩余的反悔时间;悬停暂停让正想点撤销的用户有充裕时间完成操作;同时删多条时 Snackbar 垂直排队而非互相覆盖。真正的「删除」只在倒计时自然走完时才提交给后端,在此之前数据只是视觉上移除,撤销零成本。
删除即软删除,提交推迟到倒计时结束,撤销就是取消那个 timer:
function remove(item) {
const i = list.indexOf(item);
list.splice(i, 1); // 1. 立即软删除 (仅前端)
render();
const timer = setTimeout(() => {
api.delete(item.id); // 3a. 倒计时走完才真正提交
}, UNDO_WINDOW);
showSnackbar('已删除', () => {
clearTimeout(timer); // 3b. 撤销 = 取消提交
list.splice(i, 0, item); // 放回原位
render();
});
}
警示 · 悬停暂停不能只暂停进度条。上面这段用 setTimeout 计时,而进度条通常是一条 CSS 动画;若只给它加 animation-play-state: paused,条会冻住,setTimeout 却照跑不误——进度条停在半路,邮件已经删掉了。两种修法:把提交挂到进度条的
animationend 上(图 0-1 的实现走的这条),让暂停动画自然把提交一并推迟;或者悬停时 clearTimeout 并记下剩余时间,离开时按剩余时间重开。
| 维度 | confirm 确认框 | Snackbar + 撤销 |
|---|---|---|
| 打断时机 | 每次操作前 | 仅在想反悔时,事后 |
| 常规路径成本 | 多一次点击 | 零额外点击 |
| 线程 | 阻塞(原生) | 非阻塞 |
| 误操作可挽回 | 提交前可取消 | 提交后短时间内仍可撤销 |
| 适用 | 不可逆、高危(清空账户) | 可逆的高频操作(删邮件、归档) |
注 · Snackbar 不是万能替代品:对真正不可逆的操作(永久销毁、付款),仍应保留显式确认。它和 Optimistic UI 同源——都信任「操作通常没问题」,把代价推迟到例外发生时。Material Design 把它规范为 Snackbar,iOS 上的对应物则是顶部的 banner 加撤销手势。