必须手动设 disabled="true" 才能彻底禁用按钮,点击瞬间执行 disabled + 文案替换;用 ::after 伪元素实现旋转图标防布局跳动;状态恢复必须在 finally 块中统一处理所有分支。
必须手动设 disabled="true",否则按钮永远拦不住重复点击——这是唯一能同时禁用鼠标、键盘(空格/回车)、触屏和表单序列化的原生机制。
别等 AJAX 请求发出去再操作,网络延迟下用户早点了第二下。关键动作必须在事件回调第一行完成:
button.disabled = true 是唯一可靠方式;btn.classList.add('disabled') 只改样式,Enter 键照样触发<i class="bi bi-check"></i>),不能只改 textContent,得用 innerHTML 或提前缓存原始 HTML 字符串disabled 样式支持不一,建议额外加 style="pointer-events: none;" 防穿透直接给按钮加 animation: spin 1s infinite 会触发重排,文字偏移、宽度抖动。稳妥做法是:
position: relative
::after 伪元素绘制固定尺寸旋转体:content: ""、position: absolute、top: 50%、left: 50%、transform: translate(-50%, -50%)
transform-origin: center,否则旧版 Safari 可能绕左上角转无论请求成功、失败还是超时,都要还原按钮。漏掉任何一种情况,按钮就永久卡死:
button.disabled = false 和 aria-disabled="false"
tabIndex="-1"(如果之前加过)spinner-border 元素——若用 DOM 插入方式,记得 removeChild(),别只隐藏display: none 隐藏 spinner,优先用 visibility: hidden 避免布局跳动如果按钮在 <form> 里且没设 type,默认是 type="submit",会导致 JS 事件执行完又触发表单提交,逻辑跑两次:
<button type="button">提交</button>
event.preventDefault()
aria-busy="true" 和 role="button",读屏器才不会误读最难的不是让图标转起来,而是确保所有路径(包括异常、超时、取消)都走到 finally 里的还原逻辑——少一个分支,按钮就废一个。