必须手动控制 disabled 属性、替换文本或 html 内容,并在异步操作结束后的 finally 块中统一恢复——bootstrap 本身不提供自动切换逻辑,只提供样式和 spinner 组件。
必须手动控制 disabled 属性、替换文本或 html 内容,并在异步操作结束后的 finally 块中统一恢复——bootstrap 本身不提供自动切换逻辑,只提供样式和 spinner 组件。
为什么直接加 spinner-border 按钮会变高、文字上移
因为 spinner-border 是行内元素,默认无宽高约束,也不参与按钮的盒模型计算。它会额外撑开高度,把原有文字“顶上去”。这不是 bug,是 CSS 渲染机制决定的。
- 给按钮加
position: relative和overflow: hidden - 用
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)居中 spinner - 尺寸别写死(如
2rem),改用1.5em(对应btn-md)或1.2em(对应btn-sm) - 原始文字建议包在
<span class="btn-text"></span>里,加载时设display: none,避免堆叠
怎么安全地禁用 + 替换内容 + 恢复
核心是三步同步:禁用交互、隐藏原文、插入 spinner;且恢复动作必须进 finally,否则失败后按钮永远卡住。
- 用
btn.disabled = true(不是attr('disabled', true)),确保键盘回车也被拦截 - 提前存原始内容:
btn.dataset.originalHtml = btn.innerHTML(保留图标、data-*等结构) - 加载时:设
btn.disabled = true,btn.querySelector('.btn-text').style.display = 'none',再appendChild(spinnerEl) - 恢复时必须在
fetch().finally()或try/catch/finally中执行:btn.disabled = false,移除 spinner,还原innerHTML - 移动端额外加
btn.style.pointerEvents = 'none',防点击穿透
spinner-border 和 spinner-grow 怎么选
不是看哪个“好看”,而是看用户能否在 0.3 秒内无意识识别出“正在加载”——这是交互效率问题。
-
spinner-border有明确旋转方向 + 环形轮廓,识别率高,适合主操作按钮(提交、保存、下单) -
spinner-grow是中心脉冲缩放,动画轻,在弱光屏、小尺寸按钮或色弱场景下容易被忽略,更适合工具栏里的次要动作(如刷新列表) - 两者都必须加
role="status"和aria-hidden="true",否则读屏器会重复读 “loading loading” - 大尺寸不用
spinner-border-lg(不存在),改用内联样式:style="width: 1.5rem; height: 1.5rem; border-width: 0.1875rem;"
最容易被忽略的是:恢复逻辑不在 finally 里,或没清空 spinner 节点导致多次点击后 DOM 越叠越多;还有就是用 a 标签代替 button,disabled 完全无效。











