Toast vs Dialog
这一块的核心是一条判断,不是样式。判据只有一条: 用户不做任何操作,任务能不能继续?能 → toast;不能 → dialog。
Toast — 通知一下,你不需要做任何事
Toast 绝不抢焦点。按上面按钮时,看顶部面板的"焦点所在"——
它一直停在触发按钮上。role="status" 会让屏幕阅读器自动播报,不需要 focus。
Dialog — 必须处理完才能继续
打开后请做三件事:
1. 连按 Tab —— 焦点在两个按钮之间循环,不会跑到背后页面(focus trap)
2. 按 Esc —— 关闭
3. 看顶部面板 —— 关闭后焦点回到了刚才那个按钮上
危险操作的两个特殊处理
本库做法
初始焦点给"取消"按钮(initialFocus:'cancel')。
用户条件反射按 Enter 时不会把文件删掉。
点遮罩不关闭。误点遮罩的代价太大。
常见做法
焦点默认落在"删除"上,用户看清标题之前就按了 Enter。
点遮罩就关闭,手滑一下文件没了。
滚动锁定:容易漏的那个细节
修法:锁之前把滚动条宽度量出来,给
body 加等宽的 padding-right。
弹层"闪"一下的感觉大半来自这里。怎么验证:本页右边有滚动条。点"普通确认",注意页面内容有没有横移。
用错比做丑严重得多
❌ 错用
把"确认删除"做成 toast
用户可能根本没看见就把文件删了,而且没法撤销。
把"已保存"做成 dialog
打断用户没在做别的事,白白多一次点击。
✅ 判据
不做任何操作能继续吗?
能 → toast(4 秒后自己走)
不能 → dialog(困住焦点等你处理)
错误 toast 默认不自动消失
3 秒后悄悄消失 = 用户还没读完就等于没说。
进场动效的克制
scale 只到 0.98,位移只有 8px。
大幅缩放 + 回弹是廉价感的来源 —— 模态是"出现",不是"跳出来"。
遮罩刻意不用 backdrop-filter: blur:旧 WebView 上要么无效要么掉帧,
精简档 尤其要避开。
开了系统"减少动效"的话,toast__timer 进度条会停住——
不假装还在倒计时。